HEX
Server:
System: Linux aac286ea486c 5.14.0-687.15.1.el9_8.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Jun 11 08:51:45 EDT 2026 x86_64
User: root (0)
PHP: 8.2.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,disk_free_space,diskfreespace
Upload Files
File: //usr/local/bin/pickle
#!/usr/bin/env php
<?php
/**
 * Generated by Box.
 *
 * @link https://github.com/herrera-io/php-box/
 */
if (class_exists('Phar')) {
Phar::mapPhar('pickle.phar');
require 'phar://' . __FILE__ . '/bin/pickle';
}
__HALT_COMPILER(); ?>
C��=vendor/symfony/console/Formatter/OutputFormatterInterface.php7Qb�b7?�V��Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.phpZQb�bZs���>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�	Qb�b�	�*���=vendor/symfony/console/Formatter/NullOutputFormatterStyle.php�Qb�b����-�4vendor/symfony/console/Formatter/OutputFormatter.php�Qb�b��|�8vendor/symfony/console/Formatter/NullOutputFormatter.phpYQb�bY��pɤFvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php�Qb�b�}z��9vendor/symfony/console/Formatter/OutputFormatterStyle.phpPQb�bPpbd�.vendor/symfony/console/Command/ListCommand.phpBQb�bBZ$F�.vendor/symfony/console/Command/LazyCommand.php	Qb�b	�?��*vendor/symfony/console/Command/Command.phpPQb�bP���ؤ2vendor/symfony/console/Command/CompleteCommand.php� Qb�b� �}�q�=vendor/symfony/console/Command/SignalableCommandInterface.php�Qb�b�Ge�M�0vendor/symfony/console/Command/LockableTrait.php�Qb�b��?��.vendor/symfony/console/Command/HelpCommand.php(Qb�b(��͚�8vendor/symfony/console/Command/DumpCompletionCommand.php�Qb�b�Cx�T�4vendor/symfony/console/Descriptor/TextDescriptor.phpL1Qb�bL1�ቜ�0vendor/symfony/console/Descriptor/Descriptor.php�Qb�b��k%9�8vendor/symfony/console/Descriptor/MarkdownDescriptor.php�Qb�b���4{�3vendor/symfony/console/Descriptor/XmlDescriptor.php�&Qb�b�&[
�<vendor/symfony/console/Descriptor/ApplicationDescription.php�Qb�b�:n�*�4vendor/symfony/console/Descriptor/JsonDescriptor.php�Qb�b��j�9vendor/symfony/console/Descriptor/DescriptorInterface.php-Qb�b-0�M$�Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php�Qb�b��9�F�6vendor/symfony/console/EventListener/ErrorListener.phpQb�b\q%;�!vendor/symfony/console/Cursor.php"Qb�b"і��2vendor/symfony/console/CI/GithubActionReporter.php?Qb�b?�"l�@vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php�Qb�b�ѧز�-vendor/symfony/console/Tester/TesterTrait.php�Qb�b�z1�W�9vendor/symfony/console/Tester/CommandCompletionTester.phpQb�b�t5N�3vendor/symfony/console/Tester/ApplicationTester.php�Qb�b�K�@�/vendor/symfony/console/Tester/CommandTester.php:	Qb�b:	7)a��8vendor/symfony/console/SignalRegistry/SignalRegistry.php0Qb�b0�6p[�;vendor/symfony/console/Exception/InvalidOptionException.php�Qb�b��;�:vendor/symfony/console/Exception/MissingInputException.php�Qb�b�Q�g;�7vendor/symfony/console/Exception/ExceptionInterface.php�Qb�b��l�3vendor/symfony/console/Exception/LogicException.php�Qb�b�SML��5vendor/symfony/console/Exception/RuntimeException.php�Qb�b��*b�?vendor/symfony/console/Exception/NamespaceNotFoundException.php�Qb�b�BL�H�=vendor/symfony/console/Exception/InvalidArgumentException.php�Qb�b��u i�=vendor/symfony/console/Exception/CommandNotFoundException.php�Qb�b����&vendor/symfony/console/Application.php��Qb�b����mu�4vendor/symfony/console/Event/ConsoleCommandEvent.php�Qb�b����2vendor/symfony/console/Event/ConsoleErrorEvent.php�Qb�b�T��Τ6vendor/symfony/console/Event/ConsoleTerminateEvent.php"Qb�b"�q���-vendor/symfony/console/Event/ConsoleEvent.phptQb�bt:�[&�3vendor/symfony/console/Event/ConsoleSignalEvent.php�Qb�b�b�� vendor/symfony/console/Color.php�Qb�b�*<�!�.vendor/symfony/console/Attribute/AsCommand.php]Qb�b]֚��9vendor/symfony/console/Input/StreamableInputInterface.phpiQb�bi���0vendor/symfony/console/Input/InputDefinition.php�.Qb�b�.���+vendor/symfony/console/Input/ArrayInput.php�Qb�b��.�'�/vendor/symfony/console/Input/InputInterface.phpmQb�bm�Ӿ}�,vendor/symfony/console/Input/StringInput.php�Qb�b�O-#�.vendor/symfony/console/Input/InputArgument.php`
Qb�b`
}64�&vendor/symfony/console/Input/Input.phpQb�b���*vendor/symfony/console/Input/ArgvInput.php}0Qb�b}0K�~U�4vendor/symfony/console/Input/InputAwareInterface.php:Qb�b:� '��,vendor/symfony/console/Input/InputOption.phpUQb�bUT��1vendor/symfony/console/Output/OutputInterface.php\Qb�b\��t��/vendor/symfony/console/Output/ConsoleOutput.php�Qb�b�C��X�5vendor/symfony/console/Output/TrimmedBufferOutput.php;Qb�b;�<�)�6vendor/symfony/console/Output/ConsoleSectionOutput.phpWQb�bW���m�,vendor/symfony/console/Output/NullOutput.php	Qb�b	��Ф0vendor/symfony/console/Output/BufferedOutput.phpUQb�bUBE$ޤ(vendor/symfony/console/Output/Output.php=Qb�b=1��ؤ.vendor/symfony/console/Output/StreamOutput.php�
Qb�b�
�$?.�8vendor/symfony/console/Output/ConsoleOutputInterface.php Qb�b _��,vendor/symfony/console/Style/OutputStyle.php�Qb�b������/vendor/symfony/console/Style/StyleInterface.phpM
Qb�bM
���)�-vendor/symfony/console/Style/SymfonyStyle.php9Qb�b9��G��/vendor/symfony/console/Logger/ConsoleLogger.php�Qb�b����(vendor/symfony/console/ConsoleEvents.php~Qb�b~~6��3vendor/symfony/console/SingleCommandApplication.php�Qb�b��t���;vendor/symfony/console/Completion/CompletionSuggestions.phpNQb�bN��(�Avendor/symfony/console/Completion/Output/BashCompletionOutput.phphQb�bh�	��Fvendor/symfony/console/Completion/Output/CompletionOutputInterface.php�Qb�b�3��O�5vendor/symfony/console/Completion/CompletionInput.php�Qb�b�K�X�0vendor/symfony/console/Completion/Suggestion.php�Qb�b�b�ۋ�1vendor/symfony/console/Helper/HelperInterface.phpMQb�bMd��8�1vendor/symfony/console/Helper/FormatterHelper.phpX	Qb�bX	u�wY�7vendor/symfony/console/Helper/SymfonyQuestionHelper.php�Qb�b��3T�2vendor/symfony/console/Helper/DescriptorHelper.php�	Qb�b�	X^}ߤ/vendor/symfony/console/Helper/ProcessHelper.php�Qb�b����Q�,vendor/symfony/console/Helper/TableStyle.php�1Qb�b�1���o�+vendor/symfony/console/Helper/TableCell.phpQb�b��.�2vendor/symfony/console/Helper/InputAwareHelper.php�Qb�b����+vendor/symfony/console/Helper/TableRows.phpEQb�bE��a��3vendor/symfony/console/Helper/ProgressIndicator.php4Qb�b4	���0vendor/symfony/console/Helper/QuestionHelper.php�KQb�b�K6��Ť+vendor/symfony/console/Helper/HelperSet.php
Qb�b
@����(vendor/symfony/console/Helper/Dumper.php�Qb�b�+'�f�'vendor/symfony/console/Helper/Table.php�pQb�b�pn�^2�0vendor/symfony/console/Helper/TableCellStyle.php�Qb�b�-7��-vendor/symfony/console/Helper/ProgressBar.phpIQb�bI�Nc\�(vendor/symfony/console/Helper/Helper.php�Qb�b���6vendor/symfony/console/Helper/DebugFormatterHelper.phpJ
Qb�bJ
G�(��0vendor/symfony/console/Helper/TableSeparator.phpQb�b&��
�,vendor/symfony/console/Question/Question.phpWQb�bWM?|d�8vendor/symfony/console/Question/ConfirmationQuestion.phpQb�b?u�Ȥ2vendor/symfony/console/Question/ChoiceQuestion.php�Qb�b���vr�#vendor/symfony/console/Terminal.phprQb�brF�(�?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php�Qb�b���0w�?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpMQb�bM��d5�=vendor/symfony/console/CommandLoader/FactoryCommandLoader.phpEQb�bE=H���5vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpg
Qb�bg
��E{�3vendor/symfony/polyfill-intl-grapheme/bootstrap.php�Qb�b����2vendor/symfony/polyfill-intl-grapheme/Grapheme.php�%Qb�b�%WZ�3�3vendor/symfony/service-contracts/ResetInterface.php�Qb�b��v��8vendor/symfony/service-contracts/ServiceLocatorTrait.php�Qb�b�za
�?vendor/symfony/service-contracts/ServiceSubscriberInterface.php�Qb�b��B�6�<vendor/symfony/service-contracts/Test/ServiceLocatorTest.php�Qb�b���3ͤ=vendor/symfony/service-contracts/ServiceProviderInterface.php�Qb�b��mX¤@vendor/symfony/service-contracts/Attribute/SubscribedService.php Qb�b ���7vendor/symfony/service-contracts/Attribute/Required.php�Qb�b�e;Z�;vendor/symfony/service-contracts/ServiceSubscriberTrait.php�Qb�b��:�H�1vendor/symfony/deprecation-contracts/function.php�Qb�b�rg��#vendor/symfony/finder/Gitignore.php�Qb�b��BФ5vendor/symfony/finder/Iterator/PathFilterIterator.php�Qb�b���Z�;vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php�Qb�b�R�:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php�Qb�b�����=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php�Qb�b����<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php�Qb�b�k���9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpqQb�bq`c(��:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php|Qb�b|���<�=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.phpQb�b7.�/vendor/symfony/finder/Iterator/LazyIterator.php�Qb�b��ّ�7vendor/symfony/finder/Iterator/CustomFilterIterator.php#Qb�b#����3vendor/symfony/finder/Iterator/SortableIterator.phpCQb�bC6�L�9vendor/symfony/finder/Iterator/FilenameFilterIterator.php�Qb�b�Ss��;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php�Qb�b��N~�Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.phpm
Qb�bm
��p�9vendor/symfony/finder/Exception/AccessDeniedException.php�Qb�b��cWޤ>vendor/symfony/finder/Exception/DirectoryNotFoundException.php�Qb�b��RI� vendor/symfony/finder/Finder.php�WQb�b�W]�m(�3vendor/symfony/finder/Comparator/DateComparator.phpxQb�bx�]-�/vendor/symfony/finder/Comparator/Comparator.php�Qb�b�v�J��5vendor/symfony/finder/Comparator/NumberComparator.php

Qb�b

ZE���%vendor/symfony/finder/SplFileInfo.phpQb�b�s�vendor/symfony/finder/Glob.php$Qb�b$�t�ڤ'vendor/symfony/polyfill-php73/Php73.phpbQb�bbJ�<�?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.phpQb�b<F��+vendor/symfony/polyfill-php73/bootstrap.php�Qb�b���|�'vendor/symfony/polyfill-ctype/Ctype.php}Qb�b}\�谤-vendor/symfony/polyfill-ctype/bootstrap80.phprQb�br�F)��+vendor/symfony/polyfill-ctype/bootstrap.php@Qb�b@j�Q9�"vendor/symfony/process/Process.phpO�Qb�bO��=�~�&vendor/symfony/process/InputStream.phpb	Qb�bb	J�G�%vendor/symfony/process/PhpProcess.php�	Qb�b�	��R�=vendor/symfony/process/Exception/ProcessTimedOutException.php�Qb�b�{0��;vendor/symfony/process/Exception/ProcessFailedException.php�Qb�b�P��5�7vendor/symfony/process/Exception/ExceptionInterface.php�Qb�b����+�3vendor/symfony/process/Exception/LogicException.php�Qb�b���W�5vendor/symfony/process/Exception/RuntimeException.php�Qb�b�>H���=vendor/symfony/process/Exception/InvalidArgumentException.php�Qb�b�˅��=vendor/symfony/process/Exception/ProcessSignaledException.php�Qb�b�����.vendor/symfony/process/PhpExecutableFinder.php�	Qb�b�	��4�*vendor/symfony/process/Pipes/UnixPipes.phpFQb�bFC��.vendor/symfony/process/Pipes/AbstractPipes.php�Qb�b�vg0�-vendor/symfony/process/Pipes/WindowsPipes.php�Qb�b���k�/vendor/symfony/process/Pipes/PipesInterface.php�Qb�b���'vendor/symfony/process/ProcessUtils.php9Qb�b9���+vendor/symfony/process/ExecutableFinder.php�	Qb�b�	A�R5�Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9Qb�b9>|zK�@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�_Qb�b�_���d�@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php�fQb�b�f�P��-vendor/symfony/polyfill-mbstring/Mbstring.phppqQb�bpq�.Ǥ0vendor/symfony/polyfill-mbstring/bootstrap80.phpP"Qb�bP"�An�.vendor/symfony/polyfill-mbstring/bootstrap.phpEQb�bE�`��'vendor/symfony/polyfill-php80/Php80.php2
Qb�b2
M		D�;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php	Qb�b	&ڃ�<vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php�Qb�b���9�Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php3Qb�b3y4%�<vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php*Qb�b*���*�+vendor/symfony/polyfill-php80/bootstrap.php�Qb�b��.Ĥ-vendor/symfony/string/Resources/functions.php]Qb�b]�ې�<vendor/symfony/string/Resources/data/wcswidth_table_zero.php�8Qb�b�8�5�$�<vendor/symfony/string/Resources/data/wcswidth_table_wide.phpO0Qb�bO0ܰ��)vendor/symfony/string/CodePointString.php{Qb�b{n^t�.vendor/symfony/string/Slugger/AsciiSlugger.php�Qb�b�e���2vendor/symfony/string/Slugger/SluggerInterface.php�Qb�b�"�O��6vendor/symfony/string/Exception/ExceptionInterface.phpQQb�bQ$��դ4vendor/symfony/string/Exception/RuntimeException.phppQb�bp��0ʤ<vendor/symfony/string/Exception/InvalidArgumentException.php�Qb�b�e����/vendor/symfony/string/AbstractUnicodeString.phpkkQb�bkk��I�$vendor/symfony/string/LazyString.php)Qb�b)�I!�'vendor/symfony/string/UnicodeString.phpC2Qb�bC2j8���(vendor/symfony/string/AbstractString.php�NQb�b�No��d�6vendor/symfony/string/Inflector/InflectorInterface.phpCQb�bCQ�cc�4vendor/symfony/string/Inflector/EnglishInflector.php�<Qb�b�<W�;e�3vendor/symfony/string/Inflector/FrenchInflector.php�Qb�b�� i��$vendor/symfony/string/ByteString.php�;Qb�b�;�M-֤Fvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php�Qb�b����%�Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpD5Qb�bD5����Tvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php{�Qb�b{��je��Xvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpoQb�boc�,��Rvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php�DQb�b�D'�CԤ7vendor/symfony/polyfill-intl-normalizer/bootstrap80.php�Qb�b��,��5vendor/symfony/polyfill-intl-normalizer/bootstrap.php�Qb�b�#p��6vendor/symfony/polyfill-intl-normalizer/Normalizer.phpc%Qb�bc%r7x�.vendor/justinrainbow/json-schema/demo/demo.phpQb�b�b���Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php�$Qb�b�$X�h��Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpoQb�bo�n�Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php�Qb�b�?�r@�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.phpQb�b0�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.phpj
Qb�bj
[}x#�Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phprQb�br���M�Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php;Qb�b;���Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php�
Qb�b�
H���Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpNQb�bN��ݠ�Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php\Qb�b\�iR�Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpIQb�bI%|��Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php�Qb�b�٣��^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php�Qb�b�.�mi�Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpaQb�ba`���Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.phpQb�bE�B*�Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpJQb�bJ��-�Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpyQb�by�WΖ�Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpQQb�bQ�J�]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpWQb�bW%*)֤Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpTQb�bT��:�Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php"Qb�b"x&7�Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.phpQb�b5�|[�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php�>Qb�b�>}��Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpFQb�bF:0�/�Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php�Qb�b�N���Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php%Qb�b%��2��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php�"Qb�b�"ͥ#K�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.phpQb�bt�e�Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php�Qb�b��ד�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php�Qb�b�Fgc��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php�Qb�b��fK�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php&Qb�b&ٌ]�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php�
Qb�b�
��9|�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php�Qb�b�~��G�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpqQb�bq�`� �Yvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php+Qb�b+��QܤXvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.phpQb�b�
���\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php�Qb�b�c>�ϤAvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php+Qb�b+��_�Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.phpQb�bI��ѤFvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php>
Qb�b>
�W�;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpvQb�bvx�$��=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php�
Qb�b�
8���%vendor/seld/phar-utils/src/Linter.php�
Qb�b�
'4#��)vendor/seld/phar-utils/src/Timestamps.php'Qb�b'�%v��0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.phpQb�b`��4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.phpQb�b�g��;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php�Qb�b��;��5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php(QQb�b(Q�{�@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.phpRQb�bR�y#�/vendor/psr/container/src/ContainerInterface.phpQb�bj��.�7vendor/psr/container/src/NotFoundExceptionInterface.php�Qb�b�>�悤8vendor/psr/container/src/ContainerExceptionInterface.php�Qb�b�B�x��+vendor/psr/log/Psr/Log/LoggerAwareTrait.php�Qb�b�Q'��#vendor/psr/log/Psr/Log/LogLevel.phpPQb�bP���/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)Qb�b)�j��&vendor/psr/log/Psr/Log/LoggerTrait.phpW
Qb�bW
�W�j�)vendor/psr/log/Psr/Log/Test/DummyTest.php�Qb�b�HTg�*vendor/psr/log/Psr/Log/Test/TestLogger.php�Qb�b�� ��3vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php)Qb�b)I\��)vendor/psr/log/Psr/Log/AbstractLogger.php Qb�b G���3vendor/psr/log/Psr/Log/InvalidArgumentException.php`Qb�b` �X1�%vendor/psr/log/Psr/Log/NullLogger.php�Qb�b����I�*vendor/psr/log/Psr/Log/LoggerInterface.php*Qb�b*1b!q�-vendor/mlocati/composer-patcher/src/Patch.phpgQb�bg7���6vendor/mlocati/composer-patcher/src/PatchCollector.php�+Qb�b�+]�-�1vendor/mlocati/composer-patcher/src/Exception.php�Qb�b�EZ]�/vendor/mlocati/composer-patcher/src/Patcher.php�Qb�b���x�Avendor/mlocati/composer-patcher/src/Exception/PatchNotApplied.phpRQb�bR�8�Evendor/mlocati/composer-patcher/src/Exception/PatchAlreadyApplied.php�Qb�b�@�0�>vendor/mlocati/composer-patcher/src/Exception/PathNotFound.phpQQb�bQ
~Q(�Avendor/mlocati/composer-patcher/src/Exception/PathNotWritable.phpIQb�bI�i�֤@vendor/mlocati/composer-patcher/src/Exception/PathNotCreated.phpNQb�bNV�aD�Avendor/mlocati/composer-patcher/src/Exception/CommandNotFound.phpuQb�buI1n��Rvendor/mlocati/composer-patcher/src/Exception/InvalidPackageConfigurationValue.php�	Qb�b�	���Avendor/mlocati/composer-patcher/src/Exception/PathNotReadable.phpIQb�bI���-vendor/mlocati/composer-patcher/src/Event.phpQb�b;9�c�3vendor/mlocati/composer-patcher/src/Event/Patch.php�Qb�b��1�>vendor/mlocati/composer-patcher/src/Util/VolatileDirectory.php�Qb�b�W�"+�9vendor/mlocati/composer-patcher/src/Util/PathResolver.phpsQb�bs��Bvendor/mlocati/composer-patcher/src/PatchExecutor/PatchPatcher.php�Qb�b�X\�<�@vendor/mlocati/composer-patcher/src/PatchExecutor/GitPatcher.phpY
Qb�bY
�-XA�5vendor/mlocati/composer-patcher/src/PatchExecutor.phpUQb�bU��1��.vendor/mlocati/composer-patcher/src/Plugin.php�7Qb�b�7^Ԏl�7vendor/mlocati/composer-patcher/src/PatchCollection.php�	Qb�b�	����"vendor/composer/platform_check.php�Qb�b�U��Ԥ!vendor/composer/autoload_real.php�Qb�b������.vendor/composer/xdebug-handler/src/Process.php�Qb�b�q;���-vendor/composer/xdebug-handler/src/Status.phpQb�b��f�4vendor/composer/xdebug-handler/src/XdebugHandler.phptIQb�btI�x�0vendor/composer/xdebug-handler/src/PhpConfig.php5Qb�b5j$���'vendor/composer/autoload_namespaces.php�Qb�b��/t�"vendor/composer/autoload_files.phptQb�btg/��%vendor/composer/InstalledVersions.php�:Qb�b�:畎K�2vendor/composer/spdx-licenses/src/SpdxLicenses.phpw&Qb�bw&�N��%vendor/composer/autoload_classmap.php�(Qb�b�(E��H�>vendor/composer/composer/src/Composer/Plugin/PluginManager.php�>Qb�b�>K����8vendor/composer/composer/src/Composer/Plugin/Capable.php�Qb�b�'�}�@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php*Qb�b*u�*֤Cvendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.phpMQb�bMÒ@	�=vendor/composer/composer/src/Composer/Plugin/CommandEvent.php�Qb�b�#� �Evendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php7Qb�b7JD�=vendor/composer/composer/src/Composer/Plugin/PluginEvents.php�Qb�b��@��Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.php�Qb�b�Y9�3�Kvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php_Qb�b_�μ�9vendor/composer/composer/src/Composer/SelfUpdate/Keys.phpjQb�bj��A�=vendor/composer/composer/src/Composer/SelfUpdate/Versions.php,
Qb�b,
*���Fvendor/composer/composer/src/Composer/DependencyResolver/Decisions.php�Qb�b�4౗�Lvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php	Qb�b	��Jc�Jvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php�
Qb�b�
�����Dvendor/composer/composer/src/Composer/DependencyResolver/Problem.php;%Qb�b;%�#�Jvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php�
Qb�b�
M��ɤKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.phpzQb�bzd]_C�Lvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php�Qb�b�����Hvendor/composer/composer/src/Composer/DependencyResolver/Transaction.php-Qb�b-Ȭ�~�Mvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php(9Qb�b(9xJ��Avendor/composer/composer/src/Composer/DependencyResolver/Rule.php�$Qb�b�$n�~ѤDvendor/composer/composer/src/Composer/DependencyResolver/Request.php�Qb�b��~�6�Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php�Qb�b������dvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php�Qb�b���x@�Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php�Qb�b���݊�Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.phpSQb�bS�B�T�Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php#Qb�b#�u��Wvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.phpKQb�bK��Q�bvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php�Qb�b��D��Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php�Qb�b��8|4�Tvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php<Qb�b<2�궤Jvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php�$Qb�b�$�v�¤Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php<Qb�b<���Z�Avendor/composer/composer/src/Composer/DependencyResolver/Pool.phpm8Qb�bm8��$�Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php�Qb�b����/�Cvendor/composer/composer/src/Composer/DependencyResolver/Solver.phpKkQb�bKkߵ/H�Hvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.phpzQb�bzrp�5vendor/composer/composer/src/Composer/IO/BufferIO.php3
Qb�b3
�W��6vendor/composer/composer/src/Composer/IO/ConsoleIO.php�)Qb�b�)A��3vendor/composer/composer/src/Composer/IO/BaseIO.php=!Qb�b=!=��$�3vendor/composer/composer/src/Composer/IO/NullIO.php	Qb�b	����8vendor/composer/composer/src/Composer/IO/IOInterface.php4Qb�b4����Avendor/composer/composer/src/Composer/Command/LicensesCommand.phpQb�b���ߤAvendor/composer/composer/src/Composer/Command/DiagnoseCommand.php�qQb�b�q���~�@vendor/composer/composer/src/Composer/Command/ArchiveCommand.php�Qb�b��]ʤAvendor/composer/composer/src/Composer/Command/SuggestsCommand.php�Qb�b�t��?vendor/composer/composer/src/Composer/Command/StatusCommand.php�!Qb�b�!� ��=vendor/composer/composer/src/Composer/Command/ExecCommand.php(Qb�b(��ʤ@vendor/composer/composer/src/Composer/Command/DependsCommand.phptQb�btz��f�Dvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php�Qb�b��fO��Jvendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpLQb�bL����Gvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php�'Qb�b�'L�p�@vendor/composer/composer/src/Composer/Command/InstallCommand.php�Qb�b�\͠&�Bvendor/composer/composer/src/Composer/Command/ProhibitsCommand.php�Qb�b����٤@vendor/composer/composer/src/Composer/Command/RequireCommand.php{5Qb�b{5QF��?vendor/composer/composer/src/Composer/Command/ConfigCommand.php�uQb�b�u�]
6�=vendor/composer/composer/src/Composer/Command/BaseCommand.php�Qb�b�B�i�?vendor/composer/composer/src/Composer/Command/SearchCommand.php�Qb�b�K�5Ӥ=vendor/composer/composer/src/Composer/Command/FundCommand.php�Qb�b�
�X��>vendor/composer/composer/src/Composer/Command/AboutCommand.php�Qb�b�'�|�?vendor/composer/composer/src/Composer/Command/GlobalCommand.phpDQb�bD%��=�Fvendor/composer/composer/src/Composer/Command/CreateProjectCommand.phpOLQb�bOL�l��Avendor/composer/composer/src/Composer/Command/OutdatedCommand.phpIQb�bI�A�Cvendor/composer/composer/src/Composer/Command/ClearCacheCommand.phpoQb�boJ1OK�?vendor/composer/composer/src/Composer/Command/UpdateCommand.php�+Qb�b�+�J�N�?vendor/composer/composer/src/Composer/Command/RemoveCommand.php�Qb�b��!��Evendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php�Qb�b����=vendor/composer/composer/src/Composer/Command/HomeCommand.phpnQb�bnz����=vendor/composer/composer/src/Composer/Command/InitCommand.php#�Qb�b#�� b�Bvendor/composer/composer/src/Composer/Command/RunScriptCommand.phpnQb�bniX$ڤCvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php�]Qb�b�]f
�l�=vendor/composer/composer/src/Composer/Command/ShowCommand.php��Qb�b���a�Avendor/composer/composer/src/Composer/Command/ValidateCommand.phpcQb�bcs�.��Evendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php
Qb�b
�R�ܤ=vendor/composer/composer/src/Composer/Console/Application.php�QQb�b�Qi"��3vendor/composer/composer/src/Composer/Installer.php~Qb�b~}ѷO�Gvendor/composer/composer/src/Composer/Repository/PlatformRepository.php�3Qb�b�3�	"�Fvendor/composer/composer/src/Composer/Repository/RepositoryFactory.phpJQb�bJ�!�Qvendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.phpCQb�bC#�Bvendor/composer/composer/src/Composer/Repository/VcsRepository.php�@Qb�b�@�}薤Cvendor/composer/composer/src/Composer/Repository/BaseRepository.php�%Qb�b�%
W��Hvendor/composer/composer/src/Composer/Repository/RepositoryInterface.php�Qb�b�􏉤Ovendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php�Qb�b���$�Gvendor/composer/composer/src/Composer/Repository/ArtifactRepository.phphQb�bh����Gvendor/composer/composer/src/Composer/Repository/ComposerRepository.phpT~Qb�bT~�a�y�Jvendor/composer/composer/src/Composer/Repository/VersionCacheInterface.phpzQb�bz�m%ݤDvendor/composer/composer/src/Composer/Repository/ArrayRepository.php�Qb�b���`�Fvendor/composer/composer/src/Composer/Repository/PackageRepository.php\Qb�b\�兤Cvendor/composer/composer/src/Composer/Repository/PearRepository.php'"Qb�b'"뛅ǤTvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php�Qb�b�6{��Lvendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php�Qb�b���d�Pvendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php�Qb�b�<9�Pvendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php�Qb�b�ugb��Cvendor/composer/composer/src/Composer/Repository/PathRepository.php�Qb�b���O��Mvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.phpHQb�bH	+;I�Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php_Qb�b_�>�Bvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.phprQb�br6���Evendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.phpWFQb�bWF�XQ�Evendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php0Qb�b0�{z٤Gvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.phpZQb�bZ�Z�Bvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php^Qb�b^t��l�Kvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.phpM	Qb�bM	h9m��Evendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php�MQb�b�M��-n�Kvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php~Qb�b~����Hvendor/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php�5Qb�b�5��6�Jvendor/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.phpG	Qb�bG	*ڿN�Bvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php�/Qb�b�/(���Fvendor/composer/composer/src/Composer/Repository/RepositoryManager.php�Qb�b��rJ�Evendor/composer/composer/src/Composer/Repository/Pear/ChannelInfo.php�Qb�b��s�Evendor/composer/composer/src/Composer/Repository/Pear/ReleaseInfo.phpQb�b�3&��Kvendor/composer/composer/src/Composer/Repository/Pear/BaseChannelReader.php�	Qb�b�	�HˤEvendor/composer/composer/src/Composer/Repository/Pear/PackageInfo.phpJQb�bJ�r���Qvendor/composer/composer/src/Composer/Repository/Pear/PackageDependencyParser.php�*Qb�b�*�o}��Nvendor/composer/composer/src/Composer/Repository/Pear/DependencyConstraint.php�Qb�b�s��2�Gvendor/composer/composer/src/Composer/Repository/Pear/ChannelReader.php�Qb�b��x��Mvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest11Reader.php�Qb�b�e�ΤMvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest10Reader.php�Qb�b��>o9�Hvendor/composer/composer/src/Composer/Repository/Pear/DependencyInfo.php�Qb�b���-m�Hvendor/composer/composer/src/Composer/Repository/CompositeRepository.phpWQb�bW�2��Rvendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php�Qb�b��Y�Ivendor/composer/composer/src/Composer/Repository/FilesystemRepository.php�	Qb�b�	a�f�7vendor/composer/composer/src/Composer/XdebugHandler.phpMQb�bM.��1vendor/composer/composer/src/Composer/Factory.phpaQb�ba�QhפBvendor/composer/composer/src/Composer/Exception/NoSslException.php�Qb�b��g��0vendor/composer/composer/src/Composer/Config.phpGAQb�bGA���e�Bvendor/composer/composer/src/Composer/Downloader/GitDownloader.php8QQb�b8Q#e�Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.php
$Qb�b
$�v�4�Bvendor/composer/composer/src/Composer/Downloader/RarDownloader.php)Qb�b)-<iN�Bvendor/composer/composer/src/Composer/Downloader/VcsDownloader.php�)Qb�b�)�X6��Rvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.phpQb�b���Avendor/composer/composer/src/Composer/Downloader/XzDownloader.php�Qb�b��-DJ�Hvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php�Qb�b�Rz^�Hvendor/composer/composer/src/Composer/Downloader/FilesystemException.php�Qb�b����Dvendor/composer/composer/src/Composer/Downloader/DownloadManager.phpX)Qb�bX)�J�Evendor/composer/composer/src/Composer/Downloader/FossilDownloader.php�Qb�b��j���Avendor/composer/composer/src/Composer/Downloader/HgDownloader.php�Qb�b�|&��Bvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php�!Qb�b�!U��z�Cvendor/composer/composer/src/Composer/Downloader/FileDownloader.php�,Qb�b�,ə!c�Lvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.phpQb�b�v�(�Bvendor/composer/composer/src/Composer/Downloader/TarDownloader.php�Qb�b� ��f�Gvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.phpQb�b��Ivendor/composer/composer/src/Composer/Downloader/PearPackageExtractor.php�-Qb�b�-W����Gvendor/composer/composer/src/Composer/Downloader/TransportException.php�Qb�b�y�#e�Cvendor/composer/composer/src/Composer/Downloader/PharDownloader.php�Qb�b���x6�Cvendor/composer/composer/src/Composer/Downloader/GzipDownloader.php-
Qb�b-
��G��Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php�Qb�b��jC�Jvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php�Qb�b��)Ÿ�Fvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php$Qb�b$��ͦ�Fvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php�Qb�b�����Avendor/composer/composer/src/Composer/Config/JsonConfigSource.php("Qb�b("B�,�Cvendor/composer/composer/src/Composer/Installer/BinaryInstaller.phpfQb�bf��{�Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php�Qb�b�ډ��Dvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php/
Qb�b/
�#D�@vendor/composer/composer/src/Composer/Installer/PackageEvent.phpTQb�bT[��ڤBvendor/composer/composer/src/Composer/Installer/InstallerEvent.php�Qb�b���Avendor/composer/composer/src/Composer/Installer/PearInstaller.php6Qb�b6x��Avendor/composer/composer/src/Composer/Installer/NoopInstaller.phpnQb�bn1�a��Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php�
Qb�b�
W%��Gvendor/composer/composer/src/Composer/Installer/InstallationManager.php*Qb�b*�nwD�Cvendor/composer/composer/src/Composer/Installer/InstallerEvents.phpQb�b��.!�Fvendor/composer/composer/src/Composer/Installer/InstallerInterface.php�	Qb�b�	l�;��Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php�Qb�b����Avendor/composer/composer/src/Composer/Installer/PackageEvents.php�Qb�b�Z�V��Dvendor/composer/composer/src/Composer/Installer/LibraryInstaller.php�Qb�b�+�_��Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.php
Qb�b
30ˤGvendor/composer/composer/src/Composer/Installer/PearBinaryInstaller.phpTQb�bT�|�9vendor/composer/composer/src/Composer/Util/Filesystem.php4WQb�b4W��zӤ<vendor/composer/composer/src/Composer/Util/PackageSorter.php�	Qb�b�	x-��7vendor/composer/composer/src/Composer/Util/Perforce.phpeDQb�beD3n�C�8vendor/composer/composer/src/Composer/Util/Bitbucket.php�!Qb�b�!�9�=vendor/composer/composer/src/Composer/Util/ComposerMirror.php�Qb�b����Q�9vendor/composer/composer/src/Composer/Util/AuthHelper.php�Qb�b��F�>vendor/composer/composer/src/Composer/Util/ProcessExecutor.php�Qb�b��k�7vendor/composer/composer/src/Composer/Util/Silencer.phpvQb�bv�G��?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.phps�Qb�bs�/bUQ�8vendor/composer/composer/src/Composer/Util/IniHelper.php`Qb�b`���X�5vendor/composer/composer/src/Composer/Util/GitHub.php�Qb�b���.�2vendor/composer/composer/src/Composer/Util/Url.phpXQb�bX��c�>vendor/composer/composer/src/Composer/Util/ConfigValidator.php�!Qb�b�!w�4�1vendor/composer/composer/src/Composer/Util/Hg.phpBQb�bB�vw�2vendor/composer/composer/src/Composer/Util/Svn.php<'Qb�b<'�fd�2vendor/composer/composer/src/Composer/Util/Zip.php�Qb�b�LI!��Cvendor/composer/composer/src/Composer/Util/StreamContextFactory.phpQb�b���2vendor/composer/composer/src/Composer/Util/Git.php�GQb�b�G��eb�=vendor/composer/composer/src/Composer/Util/NoProxyPattern.php�)Qb�b�)t�	֤:vendor/composer/composer/src/Composer/Util/SpdxLicense.php:Qb�b:�R��;vendor/composer/composer/src/Composer/Util/ErrorHandler.phpQb�b�X���7vendor/composer/composer/src/Composer/Util/Platform.php(
Qb�b(
��R�8vendor/composer/composer/src/Composer/Util/TlsHelper.php�Qb�b������5vendor/composer/composer/src/Composer/Util/GitLab.phpQb�b����Ivendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php�[Qb�b�[�3T�?vendor/composer/composer/src/Composer/EventDispatcher/Event.phpfQb�bfKxդRvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.phpQb�b�#g�Rvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php�Qb�b�\!'Ƥ>vendor/composer/composer/src/Composer/Json/JsonManipulator.phpMQb�bM�P��Fvendor/composer/composer/src/Composer/Json/JsonValidationException.php�Qb�b���JȤ<vendor/composer/composer/src/Composer/Json/JsonFormatter.php�Qb�b��|⸤7vendor/composer/composer/src/Composer/Json/JsonFile.phpN'Qb�bN'!/��Dvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.phpg4Qb�bg4V��>vendor/composer/composer/src/Composer/Autoload/ClassLoader.php�4Qb�b�4�s�|�Dvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php�Qb�b��ӓ�/vendor/composer/composer/src/Composer/Cache.phpMQb�bM=5��2vendor/composer/composer/src/Composer/Compiler.php7)Qb�b7)�*�-�2vendor/composer/composer/src/Composer/Composer.php�Qb�b�:���=vendor/composer/composer/src/Composer/Script/PackageEvent.php�Qb�b�����=vendor/composer/composer/src/Composer/Script/ScriptEvents.php�Qb�b�N�ׯ�=vendor/composer/composer/src/Composer/Script/CommandEvent.php�Qb�b����Ǥ6vendor/composer/composer/src/Composer/Script/Event.php�Qb�b�H��Mvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.phpk
Qb�bk
��|F�Dvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.phpsQb�bs�OԸ�6vendor/composer/composer/src/Composer/Package/Link.php�Qb�b�0�V�Cvendor/composer/composer/src/Composer/Package/Comparer/Comparer.php�Qb�b�q����Fvendor/composer/composer/src/Composer/Package/RootPackageInterface.php�Qb�b�'
�f�8vendor/composer/composer/src/Composer/Package/Locker.php�;Qb�b�;u�Э�Avendor/composer/composer/src/Composer/Package/CompletePackage.phpxQb�bx_�l�Cvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php\Qb�b\��pj�Pvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php�Qb�b�IQ�ؤJvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php�(Qb�b�(�;`��Dvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php�-Qb�b�-��PߤNvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php�\Qb�b�\��i��Hvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpBQb�bB*�J��Hvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php�6Qb�b�6��=|�Gvendor/composer/composer/src/Composer/Package/Version/VersionParser.php�	Qb�b�	����Ivendor/composer/composer/src/Composer/Package/Version/VersionSelector.phpgQb�bg�[���Xvendor/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php�Qb�b��䑞�Svendor/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php�Qb�b���<�Pvendor/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php�Qb�b��3��Rvendor/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php�Qb�b�4��O�Pvendor/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php�Qb�b���[5�=vendor/composer/composer/src/Composer/Package/RootPackage.phpD
Qb�bD
�-/�Jvendor/composer/composer/src/Composer/Package/CompletePackageInterface.php�Qb�b��R��>vendor/composer/composer/src/Composer/Package/AliasPackage.php�$Qb�b�$@��9vendor/composer/composer/src/Composer/Package/Package.php1Qb�b1��p(�Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php�Qb�b�Դ=�Pvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpZQb�bZHInw�Lvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php�Qb�b�dά_�Lvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php�Qb�b�lة�Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.phpFQb�bF�L	�Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.phpSQb�bSׂ��Kvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php9Qb�b9�$���Fvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php�	Qb�b�	����Ivendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php<Qb�b<����Jvendor/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php�
Qb�b�
ұS�=vendor/composer/composer/src/Composer/Package/BasePackage.php�Qb�b���n��Bvendor/composer/composer/src/Composer/Package/RootAliasPackage.php=Qb�b=��6�Bvendor/composer/composer/src/Composer/Package/PackageInterface.phpl%Qb�bl%�~��*vendor/composer/composer/src/bootstrap.php�Qb�b�cAl�vendor/composer/installed.php�)Qb�b�)z �ڤvendor/composer/ClassLoader.php�>Qb�b�>�5Ky�*vendor/composer/ca-bundle/src/CaBundle.php�DQb�b�DA�ʤ<vendor/composer/semver/src/Constraint/AbstractConstraint.phpnQb�bn��;�=vendor/composer/semver/src/Constraint/ConstraintInterface.php^Qb�b^)��X�4vendor/composer/semver/src/Constraint/Constraint.phpQb�b��V�9vendor/composer/semver/src/Constraint/EmptyConstraint.php:Qb�b:@M�9vendor/composer/semver/src/Constraint/MultiConstraint.php<
Qb�b<
ª�	�%vendor/composer/semver/src/Semver.php�Qb�b�o�I��)vendor/composer/semver/src/Comparator.php�	Qb�b�	�g3	�,vendor/composer/semver/src/VersionParser.phpKTQb�bKT�v�ʤ!vendor/composer/autoload_psr4.php�Qb�b��'�k�#vendor/composer/autoload_static.phpTYQb�bTY��3�;vendor/padraic/phar-updater/src/Strategy/GithubStrategy.php�Qb�b�L�3��>vendor/padraic/phar-updater/src/Strategy/StrategyInterface.php�Qb�b�Vr��;vendor/padraic/phar-updater/src/Strategy/Sha256Strategy.php$Qb�b$^m���@vendor/padraic/phar-updater/src/Strategy/ShaStrategyAbstract.php�
Qb�b�
��`�8vendor/padraic/phar-updater/src/Strategy/ShaStrategy.phpsQb�bsOAm��@vendor/padraic/phar-updater/src/Exception/ExceptionInterface.php6Qb�b6gQ7��Avendor/padraic/phar-updater/src/Exception/FilesystemException.phpLQb�bL�P��Bvendor/padraic/phar-updater/src/Exception/HttpRequestException.phpMQb�bMD���Bvendor/padraic/phar-updater/src/Exception/NoSignatureException.phpMQb�bMb䢿�Bvendor/padraic/phar-updater/src/Exception/JsonParsingException.phpMQb�bMw��>vendor/padraic/phar-updater/src/Exception/RuntimeException.phphQb�bhy��Q�Fvendor/padraic/phar-updater/src/Exception/InvalidArgumentException.phppQb�bp�3D��+vendor/padraic/phar-updater/src/Updater.php�2Qb�b�2�F� �1vendor/padraic/phar-updater/src/VersionParser.phpgQb�bg�[��4vendor/padraic/humbug_get_contents/src/functions.php�Qb�b���:vendor/padraic/humbug_get_contents/src/FileGetContents.php!-Qb�b!-U�d:�3vendor/padraic/humbug_get_contents/src/function.php_Qb�b_ˌ�vendor/autoload.php�Qb�b�B�|��&src/Console/Command/ConvertCommand.php�Qb�b�a�?�#src/Console/Command/InfoCommand.php�Qb�b���C#�&src/Console/Command/ReleaseCommand.phpzQb�bzY�>�(src/Console/Command/InstallerCommand.php�Qb�b��iP��)src/Console/Command/SelfUpdateCommand.php�Qb�b�uN�r�'src/Console/Command/ValidateCommand.php�Qb�b���V֤src/Console/Application.php
Qb�b
�aꀤ$src/Console/Helper/PackageHelper.phpVQb�bVB�p&�src/Config.php�Qb�b��J+��src/Package.php+
Qb�b+
.�3��!src/Downloader/PECLDownloader.php�Qb�b�vLФ src/Downloader/TGZDownloader.php�Qb�b��^�3src/Base/Abstracts/Console/Command/BuildCommand.phpSQb�bS����src/Base/Abstracts/Package.phpQb�b��;^�!src/Base/Abstracts/Engine/Ini.php�
Qb�b�
q���src/Base/Abstracts/Engine.php�Qb�b��&�B�-src/Base/Abstracts/Package/Convey/Command.php�
Qb�b�
��
��$src/Base/Abstracts/Package/Build.phpaQb�ba��D�src/Base/Archive/PHPZipper.phpvQb�bv���"src/Base/Archive/InfozipZipper.phpXQb�bX��Q:�src/Base/Archive/Factory.php�Qb�b�"�9�src/Base/Archive/PHP.phpQb�b�=|Ťsrc/Base/Archive/Infozip.php�Qb�b���bä src/Base/Archive/PHPUnzipper.php�	Qb�b�	����$src/Base/Archive/InfozipUnzipper.php�Qb�b�NAMZ�src/Base/Util/FileOps.php�Qb�b��p�u�src/Base/Util/GitIgnore.php�
Qb�b�
!JD��src/Base/Util/TmpDir.php�	Qb�b�	v�� src/Base/Pecl/WebsiteFactory.phpHQb�bH���src/Base/Pecl/Website.php�
Qb�b�
��&@�(src/Base/Interfaces/Archive/Unzipper.php	Qb�b	BJ�	�&src/Base/Interfaces/Archive/Zipper.php�Qb�b��{z��src/Base/Interfaces/Package.phpQb�b
V�8�"src/Base/Interfaces/Engine/Ini.php�Qb�b���n�src/Base/Interfaces/Engine.php�Qb�b��D�6src/Base/Interfaces/Package/Convey/DefaultExecutor.php�Qb�b�A�@�.src/Base/Interfaces/Package/Convey/Command.php�Qb�b�;s�Y�(src/Base/Interfaces/Package/Validate.phpJQb�bJ��!�$src/Base/Interfaces/Package/Info.php�Qb�b��d�y�%src/Base/Interfaces/Package/Build.phpQb�bz'Τ'src/Base/Interfaces/Package/Release.phplQb�bl�y`��src/Engine/Ini.php8	Qb�b8	��Τsrc/Engine/PHP.php� Qb�b� 0#���src/Engine/PHP/Ini.phpQb�bc.>��src/Engine.php#Qb�b#ғ"<�"src/Package/Convey/Command/Any.php�Qb�b����%src/Package/Convey/Command/SrcDir.phpe	Qb�be	��㉤.src/Package/Convey/Command/DefaultExecutor.php�	Qb�b�	�FЩ�&src/Package/Convey/Command/Factory.php�	Qb�b�	6n�T�#src/Package/Convey/Command/Type.php�Qb�b����%src/Package/Convey/Command/Pickle.php�Qb�b�A�0(�"src/Package/Convey/Command/Tgz.phpQQb�bQ�{*�"src/Package/Convey/Command/Git.php�Qb�b��:� src/Package/Command/Validate.php�Qb�b�s�F��src/Package/Command/Install.php�	Qb�b�	i=2��src/Package/Command/Convert.php�Qb�b��YIi�src/Package/Command/Info.phpzQb�bz���src/Package/Command/Build.phpl	Qb�bl	�O�1�src/Package/Command/Release.phpg
Qb�bg
AM�x�src/Package/Convey.php�	Qb�b�	c��
�*src/Package/Util/Windows/DependencyLib.php'Qb�b'\5��src/Package/Util/Dumper.phpfQb�bf3��7� src/Package/Util/JSON/Dumper.phpO	Qb�bO	-
�� src/Package/Util/JSON/Loader.php�Qb�b�D���src/Package/Util/Loader.php�Qb�b���!��#src/Package/Util/Header/Version.php
Qb�b
�6古2src/Package/PHP/Convey/Command/DefaultExecutor.php�Qb�b�iE!դ'src/Package/PHP/Convey/Command/Pecl.php�Qb�b�&	��2src/Package/PHP/Command/Install/Windows/Binary.phpK*Qb�bK*i�l�$src/Package/PHP/Command/Validate.php�Qb�b��0�_�#src/Package/PHP/Command/Convert.php	Qb�b	:��� src/Package/PHP/Command/Info.php�Qb�b��K_5�2src/Package/PHP/Command/Release/Windows/Binary.php�"Qb�b�":�笤&src/Package/PHP/Command/Build/Unix.php�Qb�b��¢q�)src/Package/PHP/Command/Build/Windows.php�"Qb�b�"tPH�#src/Package/PHP/Command/Release.php�Qb�b�ېY̤src/Package/PHP/Package.php�(Qb�b�(���)src/Package/PHP/Util/ConvertChangeLog.phprQb�br/��/�#src/Package/PHP/Util/XML/Loader.php�Qb�b��.EZ�$src/Package/PHP/Util/PackageJson.php{Qb�b{�OE�#src/Package/PHP/Util/PackageXml.phpWQb�bW��Τ1vendor/composer/composer/res/composer-schema.json�]Qb�b�]�F��<vendor/composer/composer/res/composer-repository-schema.json�	Qb�b�	G�`�(vendor/composer/ca-bundle/res/cacert.pem�,Qb�b�,�_~��!vendor/composer/ca-bundle/LICENSEQb�b*!^`�'vendor/composer/ca-bundle/composer.json�Qb�b�&ڱ�#vendor/composer/ca-bundle/README.md1Qb�b1>VuĤ
bin/pickle�Qb�b�v��Q�<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     */
    public function setDecorated(bool $decorated);

    /**
     * Whether the output will decorate messages.
     *
     * @return bool
     */
    public function isDecorated();

    /**
     * Sets a new style.
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     *
     * @return bool
     */
    public function hasStyle(string $name);

    /**
     * Gets style options from style with specified name.
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    public function getStyle(string $name);

    /**
     * Formats a message according to the given styles.
     *
     * @return string|null
     */
    public function format(?string $message);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     */
    public function setForeground(string $color = null);

    /**
     * Sets style background color.
     */
    public function setBackground(string $color = null);

    /**
     * Sets some specific style option.
     */
    public function setOption(string $option);

    /**
     * Unsets some specific style option.
     */
    public function unsetOption(string $option);

    /**
     * Sets multiple style options at once.
     */
    public function setOptions(array $options);

    /**
     * Applies the style to a given text.
     *
     * @return string
     */
    public function apply(string $text);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack implements ResetInterface
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private $styles;

    private $emptyStyle;

    public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
    {
        $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
        $this->reset();
    }

    /**
     * Resets stack (ie. empty internal arrays).
     */
    public function reset()
    {
        $this->styles = [];
    }

    /**
     * Pushes a style in the stack.
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    public function pop(OutputFormatterStyleInterface $style = null)
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        if (null === $style) {
            return array_pop($this->styles);
        }

        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = \array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     *
     * @return OutputFormatterStyle
     */
    public function getCurrent()
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        return $this->styles[\count($this->styles) - 1];
    }

    /**
     * @return $this
     */
    public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
    {
        $this->emptyStyle = $emptyStyle;

        return $this;
    }

    /**
     * @return OutputFormatterStyleInterface
     */
    public function getEmptyStyle()
    {
        return $this->emptyStyle;
    }
}
<?php
/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
    /**
     * {@inheritdoc}
     */
    public function apply(string $text): string
    {
        return $text;
    }

    /**
     * {@inheritdoc}
     */
    public function setBackground(string $color = null): void
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function setForeground(string $color = null): void
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function setOption(string $option): void
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options): void
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function unsetOption(string $option): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
class OutputFormatter implements WrappableOutputFormatterInterface
{
    private $decorated;
    private $styles = [];
    private $styleStack;

    public function __clone()
    {
        $this->styleStack = clone $this->styleStack;
        foreach ($this->styles as $key => $value) {
            $this->styles[$key] = clone $value;
        }
    }

    /**
     * Escapes "<" special char in given text.
     *
     * @return string
     */
    public static function escape(string $text)
    {
        $text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);

        return self::escapeTrailingBackslash($text);
    }

    /**
     * Escapes trailing "\" in given text.
     *
     * @internal
     */
    public static function escapeTrailingBackslash(string $text): string
    {
        if (str_ends_with($text, '\\')) {
            $len = \strlen($text);
            $text = rtrim($text, '\\');
            $text = str_replace("\0", '', $text);
            $text .= str_repeat("\0", $len - \strlen($text));
        }

        return $text;
    }

    /**
     * Initializes console output formatter.
     *
     * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(bool $decorated = false, array $styles = [])
    {
        $this->decorated = $decorated;

        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));

        foreach ($styles as $name => $style) {
            $this->setStyle($name, $style);
        }

        $this->styleStack = new OutputFormatterStyleStack();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated)
    {
        $this->decorated = $decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    /**
     * {@inheritdoc}
     */
    public function hasStyle(string $name)
    {
        return isset($this->styles[strtolower($name)]);
    }

    /**
     * {@inheritdoc}
     */
    public function getStyle(string $name)
    {
        if (!$this->hasStyle($name)) {
            throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
        }

        return $this->styles[strtolower($name)];
    }

    /**
     * {@inheritdoc}
     */
    public function format(?string $message)
    {
        return $this->formatAndWrap($message, 0);
    }

    /**
     * {@inheritdoc}
     */
    public function formatAndWrap(?string $message, int $width)
    {
        $offset = 0;
        $output = '';
        $tagRegex = '[a-z][^<>]*+';
        $currentLineLength = 0;
        preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $i => $match) {
            $pos = $match[1];
            $text = $match[0];

            if (0 != $pos && '\\' == $message[$pos - 1]) {
                continue;
            }

            // add the text up to the next tag
            $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
            $offset = $pos + \strlen($text);

            // opening tag?
            if ($open = '/' != $text[1]) {
                $tag = $matches[1][$i][0];
            } else {
                $tag = $matches[3][$i][0] ?? '';
            }

            if (!$open && !$tag) {
                // </>
                $this->styleStack->pop();
            } elseif (null === $style = $this->createStyleFromString($tag)) {
                $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);

        if (str_contains($output, "\0")) {
            return strtr($output, ["\0" => '\\', '\\<' => '<']);
        }

        return str_replace('\\<', '<', $output);
    }

    /**
     * @return OutputFormatterStyleStack
     */
    public function getStyleStack()
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     */
    private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
            return null;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);
            $match[0] = strtolower($match[0]);

            if ('fg' == $match[0]) {
                $style->setForeground(strtolower($match[1]));
            } elseif ('bg' == $match[0]) {
                $style->setBackground(strtolower($match[1]));
            } elseif ('href' === $match[0]) {
                $style->setHref($match[1]);
            } elseif ('options' === $match[0]) {
                preg_match_all('([^,;]+)', strtolower($match[1]), $options);
                $options = array_shift($options);
                foreach ($options as $option) {
                    $style->setOption($option);
                }
            } else {
                return null;
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     */
    private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
    {
        if ('' === $text) {
            return '';
        }

        if (!$width) {
            return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
        }

        if (!$currentLineLength && '' !== $current) {
            $text = ltrim($text);
        }

        if ($currentLineLength) {
            $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
            $text = substr($text, $i);
        } else {
            $prefix = '';
        }

        preg_match('~(\\n)$~', $text, $matches);
        $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
        $text = rtrim($text, "\n").($matches[1] ?? '');

        if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
            $text = "\n".$text;
        }

        $lines = explode("\n", $text);

        foreach ($lines as $line) {
            $currentLineLength += \strlen($line);
            if ($width <= $currentLineLength) {
                $currentLineLength = 0;
            }
        }

        if ($this->isDecorated()) {
            foreach ($lines as $i => $line) {
                $lines[$i] = $this->styleStack->getCurrent()->apply($line);
            }
        }

        return implode("\n", $lines);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatter implements OutputFormatterInterface
{
    private $style;

    /**
     * {@inheritdoc}
     */
    public function format(?string $message): ?string
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getStyle(string $name): OutputFormatterStyleInterface
    {
        // to comply with the interface we must return a OutputFormatterStyleInterface
        return $this->style ?? $this->style = new NullOutputFormatterStyle();
    }

    /**
     * {@inheritdoc}
     */
    public function hasStyle(string $name): bool
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated(): bool
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated): void
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output that supports word wrapping.
 *
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
    /**
     * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
     */
    public function formatAndWrap(?string $message, int $width);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Color;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private $color;
    private $foreground;
    private $background;
    private $options;
    private $href;
    private $handlesHrefGracefully;

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     */
    public function __construct(string $foreground = null, string $background = null, array $options = [])
    {
        $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
    }

    /**
     * {@inheritdoc}
     */
    public function setForeground(string $color = null)
    {
        $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function setBackground(string $color = null)
    {
        $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
    }

    public function setHref(string $url): void
    {
        $this->href = $url;
    }

    /**
     * {@inheritdoc}
     */
    public function setOption(string $option)
    {
        $this->options[] = $option;
        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function unsetOption(string $option)
    {
        $pos = array_search($option, $this->options);
        if (false !== $pos) {
            unset($this->options[$pos]);
        }

        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        $this->color = new Color($this->foreground, $this->background, $this->options = $options);
    }

    /**
     * {@inheritdoc}
     */
    public function apply(string $text)
    {
        if (null === $this->handlesHrefGracefully) {
            $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
                && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
        }

        if (null !== $this->href && $this->handlesHrefGracefully) {
            $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
        }

        return $this->color->apply($text);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ListCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('list')
            ->setDefinition([
                new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
            ])
            ->setDescription('List commands')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

  <info>%command.full_name%</info>

You can also display the commands for a specific namespace:

  <info>%command.full_name% test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>%command.full_name% --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>%command.full_name% --raw</info>
EOF
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
            'short' => $input->getOption('short'),
        ]);

        return 0;
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if ($input->mustSuggestArgumentValuesFor('namespace')) {
            $descriptor = new ApplicationDescription($this->getApplication());
            $suggestions->suggestValues(array_keys($descriptor->getNamespaces()));

            return;
        }

        if ($input->mustSuggestOptionValuesFor('format')) {
            $helper = new DescriptorHelper();
            $suggestions->suggestValues($helper->getFormats());
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
final class LazyCommand extends Command
{
    private $command;
    private $isEnabled;

    public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
    {
        $this->setName($name)
            ->setAliases($aliases)
            ->setHidden($isHidden)
            ->setDescription($description);

        $this->command = $commandFactory;
        $this->isEnabled = $isEnabled;
    }

    public function ignoreValidationErrors(): void
    {
        $this->getCommand()->ignoreValidationErrors();
    }

    public function setApplication(Application $application = null): void
    {
        if ($this->command instanceof parent) {
            $this->command->setApplication($application);
        }

        parent::setApplication($application);
    }

    public function setHelperSet(HelperSet $helperSet): void
    {
        if ($this->command instanceof parent) {
            $this->command->setHelperSet($helperSet);
        }

        parent::setHelperSet($helperSet);
    }

    public function isEnabled(): bool
    {
        return $this->isEnabled ?? $this->getCommand()->isEnabled();
    }

    public function run(InputInterface $input, OutputInterface $output): int
    {
        return $this->getCommand()->run($input, $output);
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $this->getCommand()->complete($input, $suggestions);
    }

    /**
     * @return $this
     */
    public function setCode(callable $code): self
    {
        $this->getCommand()->setCode($code);

        return $this;
    }

    /**
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        $this->getCommand()->mergeApplicationDefinition($mergeArgs);
    }

    /**
     * @return $this
     */
    public function setDefinition($definition): self
    {
        $this->getCommand()->setDefinition($definition);

        return $this;
    }

    public function getDefinition(): InputDefinition
    {
        return $this->getCommand()->getDefinition();
    }

    public function getNativeDefinition(): InputDefinition
    {
        return $this->getCommand()->getNativeDefinition();
    }

    /**
     * @return $this
     */
    public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self
    {
        $this->getCommand()->addArgument($name, $mode, $description, $default);

        return $this;
    }

    /**
     * @return $this
     */
    public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self
    {
        $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default);

        return $this;
    }

    /**
     * @return $this
     */
    public function setProcessTitle(string $title): self
    {
        $this->getCommand()->setProcessTitle($title);

        return $this;
    }

    /**
     * @return $this
     */
    public function setHelp(string $help): self
    {
        $this->getCommand()->setHelp($help);

        return $this;
    }

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

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

    public function getSynopsis(bool $short = false): string
    {
        return $this->getCommand()->getSynopsis($short);
    }

    /**
     * @return $this
     */
    public function addUsage(string $usage): self
    {
        $this->getCommand()->addUsage($usage);

        return $this;
    }

    public function getUsages(): array
    {
        return $this->getCommand()->getUsages();
    }

    /**
     * @return mixed
     */
    public function getHelper(string $name)
    {
        return $this->getCommand()->getHelper($name);
    }

    public function getCommand(): parent
    {
        if (!$this->command instanceof \Closure) {
            return $this->command;
        }

        $command = $this->command = ($this->command)();
        $command->setApplication($this->getApplication());

        if (null !== $this->getHelperSet()) {
            $command->setHelperSet($this->getHelperSet());
        }

        $command->setName($this->getName())
            ->setAliases($this->getAliases())
            ->setHidden($this->isHidden())
            ->setDescription($this->getDescription());

        // Will throw if the command is not correctly initialized.
        $command->getDefinition();

        return $command;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    // see https://tldp.org/LDP/abs/html/exitcodes.html
    public const SUCCESS = 0;
    public const FAILURE = 1;
    public const INVALID = 2;

    /**
     * @var string|null The default command name
     */
    protected static $defaultName;

    /**
     * @var string|null The default command description
     */
    protected static $defaultDescription;

    private $application;
    private $name;
    private $processTitle;
    private $aliases = [];
    private $definition;
    private $hidden = false;
    private $help = '';
    private $description = '';
    private $fullDefinition;
    private $ignoreValidationErrors = false;
    private $code;
    private $synopsis = [];
    private $usages = [];
    private $helperSet;

    /**
     * @return string|null
     */
    public static function getDefaultName()
    {
        $class = static::class;

        if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->name;
        }

        $r = new \ReflectionProperty($class, 'defaultName');

        return $class === $r->class ? static::$defaultName : null;
    }

    public static function getDefaultDescription(): ?string
    {
        $class = static::class;

        if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->description;
        }

        $r = new \ReflectionProperty($class, 'defaultDescription');

        return $class === $r->class ? static::$defaultDescription : null;
    }

    /**
     * @param string|null $name The name of the command; passing null means it must be set in configure()
     *
     * @throws LogicException When the command name is empty
     */
    public function __construct(string $name = null)
    {
        $this->definition = new InputDefinition();

        if (null === $name && null !== $name = static::getDefaultName()) {
            $aliases = explode('|', $name);

            if ('' === $name = array_shift($aliases)) {
                $this->setHidden(true);
                $name = array_shift($aliases);
            }

            $this->setAliases($aliases);
        }

        if (null !== $name) {
            $this->setName($name);
        }

        if ('' === $this->description) {
            $this->setDescription(static::getDefaultDescription() ?? '');
        }

        $this->configure();
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    public function setApplication(Application $application = null)
    {
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }

        $this->fullDefinition = null;
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     *
     * @return HelperSet|null
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     *
     * @return Application|null
     */
    public function getApplication()
    {
        return $this->application;
    }

    /**
     * Checks whether the command is enabled or not in the current environment.
     *
     * Override this to check for x or y and return false if the command cannot
     * run properly under the current conditions.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * Configures the current command.
     */
    protected function configure()
    {
    }

    /**
     * Executes the current command.
     *
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @return int 0 if everything went fine, or an exit code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command after the input has been bound and before the input
     * is validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     *
     * @see InputInterface::bind()
     * @see InputInterface::validate()
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (\function_exists('cli_set_process_title')) {
                if (!@cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === \PHP_OS) {
                        $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
                    } else {
                        cli_set_process_title($this->processTitle);
                    }
                }
            } elseif (\function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
            }
        }

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        // The command name argument is often omitted when a command is executed directly with its run() method.
        // It would fail the validation if we didn't make sure the command argument is present,
        // since it's required by the application.
        if ($input->hasArgument('command') && null === $input->getArgument('command')) {
            $input->setArgument('command', $this->getName());
        }

        $input->validate();

        if ($this->code) {
            $statusCode = ($this->code)($input, $output);
        } else {
            $statusCode = $this->execute($input, $output);

            if (!\is_int($statusCode)) {
                throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
            }
        }

        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
    }

    /**
     * Sets the code to execute when running this command.
     *
     * If this method is used, it overrides the code defined
     * in the execute() method.
     *
     * @param callable $code A callable(InputInterface $input, OutputInterface $output)
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     *
     * @see execute()
     */
    public function setCode(callable $code)
    {
        if ($code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                set_error_handler(static function () {});
                try {
                    if ($c = \Closure::bind($code, $this)) {
                        $code = $c;
                    }
                } finally {
                    restore_error_handler();
                }
            }
        }

        $this->code = $code;

        return $this;
    }

    /**
     * Merges the application definition with the command definition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
     *
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true)
    {
        if (null === $this->application) {
            return;
        }

        $this->fullDefinition = new InputDefinition();
        $this->fullDefinition->setOptions($this->definition->getOptions());
        $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());

        if ($mergeArgs) {
            $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
            $this->fullDefinition->addArguments($this->definition->getArguments());
        } else {
            $this->fullDefinition->setArguments($this->definition->getArguments());
        }
    }

    /**
     * Sets an array of argument and option instances.
     *
     * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
     *
     * @return $this
     */
    public function setDefinition($definition)
    {
        if ($definition instanceof InputDefinition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }

        $this->fullDefinition = null;

        return $this;
    }

    /**
     * Gets the InputDefinition attached to this Command.
     *
     * @return InputDefinition
     */
    public function getDefinition()
    {
        return $this->fullDefinition ?? $this->getNativeDefinition();
    }

    /**
     * Gets the InputDefinition to be used to create representations of this Command.
     *
     * Can be overridden to provide the original command representation when it would otherwise
     * be changed by merging with the application InputDefinition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @return InputDefinition
     */
    public function getNativeDefinition()
    {
        if (null === $this->definition) {
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
        }

        return $this->definition;
    }

    /**
     * Adds an argument.
     *
     * @param int|null $mode    The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param mixed    $default The default value (for InputArgument::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     *
     * @return $this
     */
    public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
    {
        $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
        if (null !== $this->fullDefinition) {
            $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
        }

        return $this;
    }

    /**
     * Adds an option.
     *
     * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null          $mode     The option mode: One of the InputOption::VALUE_* constants
     * @param mixed             $default  The default value (must be null for InputOption::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     *
     * @return $this
     */
    public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
    {
        $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
        if (null !== $this->fullDefinition) {
            $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
        }

        return $this;
    }

    /**
     * Sets the name of the command.
     *
     * This method can set both the namespace and the name if
     * you separate them by a colon (:)
     *
     *     $command->setName('foo:bar');
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    public function setName(string $name)
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Sets the process title of the command.
     *
     * This feature should be used only when creating a long process command,
     * like a daemon.
     *
     * @return $this
     */
    public function setProcessTitle(string $title)
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     *
     * @return string|null
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *                     The default value will be true in Symfony 6.0
     *
     * @return $this
     *
     * @final since Symfony 5.1
     */
    public function setHidden(bool $hidden /*= true*/)
    {
        $this->hidden = $hidden;

        return $this;
    }

    /**
     * @return bool whether the command should be publicly shown or not
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets the description for the command.
     *
     * @return $this
     */
    public function setDescription(string $description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Sets the help for the command.
     *
     * @return $this
     */
    public function setHelp(string $help)
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     *
     * @return string
     */
    public function getHelp()
    {
        return $this->help;
    }

    /**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     *
     * @return string
     */
    public function getProcessedHelp()
    {
        $name = $this->name;
        $isSingleCommand = $this->application && $this->application->isSingleCommand();

        $placeholders = [
            '%command.name%',
            '%command.full_name%',
        ];
        $replacements = [
            $name,
            $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
        ];

        return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param string[] $aliases An array of aliases for the command
     *
     * @return $this
     *
     * @throws InvalidArgumentException When an alias is invalid
     */
    public function setAliases(iterable $aliases)
    {
        $list = [];

        foreach ($aliases as $alias) {
            $this->validateName($alias);
            $list[] = $alias;
        }

        $this->aliases = \is_array($aliases) ? $aliases : $list;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     *
     * @return array
     */
    public function getAliases()
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
     *
     * @return string
     */
    public function getSynopsis(bool $short = false)
    {
        $key = $short ? 'short' : 'long';

        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }

        return $this->synopsis[$key];
    }

    /**
     * Add a command usage example, it'll be prefixed with the command name.
     *
     * @return $this
     */
    public function addUsage(string $usage)
    {
        if (!str_starts_with($usage, $this->name)) {
            $usage = sprintf('%s %s', $this->name, $usage);
        }

        $this->usages[] = $usage;

        return $this;
    }

    /**
     * Returns alternative usages of the command.
     *
     * @return array
     */
    public function getUsages()
    {
        return $this->usages;
    }

    /**
     * Gets a helper instance by name.
     *
     * @return mixed
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function getHelper(string $name)
    {
        if (null === $this->helperSet) {
            throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
        }

        return $this->helperSet->get($name);
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName(string $name)
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Responsible for providing the values to the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompleteCommand extends Command
{
    protected static $defaultName = '|_complete';
    protected static $defaultDescription = 'Internal command to provide shell completion suggestions';

    private $completionOutputs;

    private $isDebug = false;

    /**
     * @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
     */
    public function __construct(array $completionOutputs = [])
    {
        // must be set before the parent constructor, as the property value is used in configure()
        $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class];

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
            ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
            ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
            ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script')
        ;
    }

    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            // uncomment when a bugfix or BC break has been introduced in the shell completion scripts
            //$version = $input->getOption('symfony');
            //if ($version && version_compare($version, 'x.y', '>=')) {
            //    $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version);
            //    $this->log($message);

            //    $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');

            //    return 126;
            //}

            $shell = $input->getOption('shell');
            if (!$shell) {
                throw new \RuntimeException('The "--shell" option must be set.');
            }

            if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
                throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
            }

            $completionInput = $this->createCompletionInput($input);
            $suggestions = new CompletionSuggestions();

            $this->log([
                '',
                '<comment>'.date('Y-m-d H:i:s').'</>',
                '<info>Input:</> <comment>("|" indicates the cursor position)</>',
                '  '.(string) $completionInput,
                '<info>Command:</>',
                '  '.(string) implode(' ', $_SERVER['argv']),
                '<info>Messages:</>',
            ]);

            $command = $this->findCommand($completionInput, $output);
            if (null === $command) {
                $this->log('  No command found, completing using the Application class.');

                $this->getApplication()->complete($completionInput, $suggestions);
            } elseif (
                $completionInput->mustSuggestArgumentValuesFor('command')
                && $command->getName() !== $completionInput->getCompletionValue()
            ) {
                $this->log('  No command found, completing using the Application class.');

                // expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
                $suggestions->suggestValue($command->getName());
            } else {
                $command->mergeApplicationDefinition();
                $completionInput->bind($command->getDefinition());

                if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
                    $this->log('  Completing option names for the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> command.');

                    $suggestions->suggestOptions($command->getDefinition()->getOptions());
                } else {
                    $this->log([
                        '  Completing using the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> class.',
                        '  Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
                    ]);
                    if (null !== $compval = $completionInput->getCompletionValue()) {
                        $this->log('  Current value: <comment>'.$compval.'</>');
                    }

                    $command->complete($completionInput, $suggestions);
                }
            }

            /** @var CompletionOutputInterface $completionOutput */
            $completionOutput = new $completionOutput();

            $this->log('<info>Suggestions:</>');
            if ($options = $suggestions->getOptionSuggestions()) {
                $this->log('  --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options)));
            } elseif ($values = $suggestions->getValueSuggestions()) {
                $this->log('  '.implode(' ', $values));
            } else {
                $this->log('  <comment>No suggestions were provided</>');
            }

            $completionOutput->write($suggestions, $output);
        } catch (\Throwable $e) {
            $this->log([
                '<error>Error!</error>',
                (string) $e,
            ]);

            if ($output->isDebug()) {
                throw $e;
            }

            return self::FAILURE;
        }

        return self::SUCCESS;
    }

    private function createCompletionInput(InputInterface $input): CompletionInput
    {
        $currentIndex = $input->getOption('current');
        if (!$currentIndex || !ctype_digit($currentIndex)) {
            throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
        }

        $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);

        try {
            $completionInput->bind($this->getApplication()->getDefinition());
        } catch (ExceptionInterface $e) {
        }

        return $completionInput;
    }

    private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
    {
        try {
            $inputName = $completionInput->getFirstArgument();
            if (null === $inputName) {
                return null;
            }

            return $this->getApplication()->find($inputName);
        } catch (CommandNotFoundException $e) {
        }

        return null;
    }

    private function log($messages): void
    {
        if (!$this->isDebug) {
            return;
        }

        $commandName = basename($_SERVER['argv'][0]);
        file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

/**
 * Interface for command reacting to signal.
 *
 * @author Grégoire Pineau <lyrixx@lyrix.info>
 */
interface SignalableCommandInterface
{
    /**
     * Returns the list of signals to subscribe.
     */
    public function getSubscribedSignals(): array;

    /**
     * The method will be called when the application is signaled.
     */
    public function handleSignal(int $signal): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <geoffrey.brier@gmail.com>
 */
trait LockableTrait
{
    /** @var LockInterface|null */
    private $lock;

    /**
     * Locks a command.
     */
    private function lock(string $name = null, bool $blocking = false): bool
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
        }

        if (null !== $this->lock) {
            throw new LogicException('A lock is already in place.');
        }

        if (SemaphoreStore::isSupported()) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
        if (!$this->lock->acquire($blocking)) {
            $this->lock = null;

            return false;
        }

        return true;
    }

    /**
     * Releases the command lock if there is one.
     */
    private function release()
    {
        if ($this->lock) {
            $this->lock->release();
            $this->lock = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private $command;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition([
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
            ])
            ->setDescription('Display help for a command')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

  <info>%command.full_name% list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>%command.full_name% --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.
EOF
            )
        ;
    }

    public function setCommand(Command $command)
    {
        $this->command = $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (null === $this->command) {
            $this->command = $this->getApplication()->find($input->getArgument('command_name'));
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ]);

        $this->command = null;

        return 0;
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if ($input->mustSuggestArgumentValuesFor('command_name')) {
            $descriptor = new ApplicationDescription($this->getApplication());
            $suggestions->suggestValues(array_keys($descriptor->getCommands()));

            return;
        }

        if ($input->mustSuggestOptionValuesFor('format')) {
            $helper = new DescriptorHelper();
            $suggestions->suggestValues($helper->getFormats());
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;

/**
 * Dumps the completion script for the current shell.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class DumpCompletionCommand extends Command
{
    protected static $defaultName = 'completion';
    protected static $defaultDescription = 'Dump the shell completion script';

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if ($input->mustSuggestArgumentValuesFor('shell')) {
            $suggestions->suggestValues($this->getSupportedShells());
        }
    }

    protected function configure()
    {
        $fullCommand = $_SERVER['PHP_SELF'];
        $commandName = basename($fullCommand);
        $fullCommand = realpath($fullCommand) ?: $fullCommand;

        $this
            ->setHelp(<<<EOH
The <info>%command.name%</> command dumps the shell completion script required
to use shell autocompletion (currently only bash completion is supported).

<comment>Static installation
-------------------</>

Dump the script to a global completion file and restart your shell:

    <info>%command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName}</>

Or dump the script to a local file and source it:

    <info>%command.full_name% bash > completion.sh</>

    <comment># source the file whenever you use the project</>
    <info>source completion.sh</>

    <comment># or add this line at the end of your "~/.bashrc" file:</>
    <info>source /path/to/completion.sh</>

<comment>Dynamic installation
--------------------</>

Add this add the end of your shell configuration file (e.g. <info>"~/.bashrc"</>):

    <info>eval "$({$fullCommand} completion bash)"</>
EOH
            )
            ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given')
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $commandName = basename($_SERVER['argv'][0]);

        if ($input->getOption('debug')) {
            $this->tailDebugLog($commandName, $output);

            return self::SUCCESS;
        }

        $shell = $input->getArgument('shell') ?? self::guessShell();
        $completionFile = __DIR__.'/../Resources/completion.'.$shell;
        if (!file_exists($completionFile)) {
            $supportedShells = $this->getSupportedShells();

            ($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output)
                ->writeln(sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells)));

            return self::INVALID;
        }

        $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile)));

        return self::SUCCESS;
    }

    private static function guessShell(): string
    {
        return basename($_SERVER['SHELL'] ?? '');
    }

    private function tailDebugLog(string $commandName, OutputInterface $output): void
    {
        $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
        if (!file_exists($debugFile)) {
            touch($debugFile);
        }
        $process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
        $process->run(function (string $type, string $line) use ($output): void {
            $output->write($line);
        });
    }

    /**
     * @return string[]
     */
    private function getSupportedShells(): array
    {
        return array_map(function ($f) {
            return pathinfo($f, \PATHINFO_EXTENSION);
        }, glob(__DIR__.'/../Resources/completion.*'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
        } else {
            $default = '';
        }

        $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
        $spacingWidth = $totalWidth - \strlen($argument->getName());

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s',
            $argument->getName(),
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
            $default
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
        } else {
            $default = '';
        }

        $value = '';
        if ($option->acceptValue()) {
            $value = '='.strtoupper($option->getName());

            if ($option->isValueOptional()) {
                $value = '['.$value.']';
            }
        }

        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
        $synopsis = sprintf('%s%s',
            $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ',
            sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
        );

        $spacingWidth = $totalWidth - Helper::width($synopsis);

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s%s',
            $synopsis,
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::width($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = [];

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (\strlen($option->getShortcut() ?? '') > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $command->mergeApplicationDefinition(false);

        if ($description = $command->getDescription()) {
            $this->writeText('<comment>Description:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.$description);
            $this->writeText("\n\n");
        }

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.OutputFormatter::escape($usage), $options);
        }
        $this->writeText("\n");

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        $help = $command->getProcessedHelp();
        if ($help && $help !== $description) {
            $this->writeText("\n");
            $this->writeText('<comment>Help:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
                $this->writeText("\n");
            }
        } else {
            if ('' != $help = $application->getHelp()) {
                $this->writeText("$help\n\n", $options);
            }

            $this->writeText("<comment>Usage:</comment>\n", $options);
            $this->writeText("  command [options] [arguments]\n\n", $options);

            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);

            $this->writeText("\n");
            $this->writeText("\n");

            $commands = $description->getCommands();
            $namespaces = $description->getNamespaces();
            if ($describedNamespace && $namespaces) {
                // make sure all alias commands are included when describing a specific namespace
                $describedNamespaceInfo = reset($namespaces);
                foreach ($describedNamespaceInfo['commands'] as $name) {
                    $commands[$name] = $description->getCommand($name);
                }
            }

            // calculate max. width based on available commands per namespace
            $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
                return array_intersect($namespace['commands'], array_keys($commands));
            }, array_values($namespaces)))));

            if ($describedNamespace) {
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            foreach ($namespaces as $namespace) {
                $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
                    return isset($commands[$name]);
                });

                if (!$namespace['commands']) {
                    continue;
                }

                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $this->writeText("\n");
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
                }

                foreach ($namespace['commands'] as $name) {
                    $this->writeText("\n");
                    $spacingWidth = $width - Helper::width($name);
                    $command = $commands[$name];
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    private function writeText(string $content, array $options = [])
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats command aliases to show them in the command description.
     */
    private function getCommandAliasesText(Command $command): string
    {
        $text = '';
        $aliases = $command->getAliases();

        if ($aliases) {
            $text = '['.implode('|', $aliases).'] ';
        }

        return $text;
    }

    /**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     */
    private function formatDefaultValue($default): string
    {
        if (\INF === $default) {
            return 'INF';
        }

        if (\is_string($default)) {
            $default = OutputFormatter::escape($default);
        } elseif (\is_array($default)) {
            foreach ($default as $key => $value) {
                if (\is_string($value)) {
                    $default[$key] = OutputFormatter::escape($value);
                }
            }
        }

        return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param array<Command|string> $commands
     */
    private function getColumnWidth(array $commands): int
    {
        $widths = [];

        foreach ($commands as $command) {
            if ($command instanceof Command) {
                $widths[] = Helper::width($command->getName());
                foreach ($command->getAliases() as $alias) {
                    $widths[] = Helper::width($alias);
                }
            } else {
                $widths[] = Helper::width($command);
            }
        }

        return $widths ? max($widths) + 2 : 0;
    }

    /**
     * @param InputOption[] $options
     */
    private function calculateTotalWidthForOptions(array $options): int
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
            if ($option->isNegatable()) {
                $nameLength += 6 + Helper::width($option->getName()); // |--no- + name
            } elseif ($option->acceptValue()) {
                $valueLength = 1 + Helper::width($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, object $object, array $options = [])
    {
        $this->output = $output;

        switch (true) {
            case $object instanceof InputArgument:
                $this->describeInputArgument($object, $options);
                break;
            case $object instanceof InputOption:
                $this->describeInputOption($object, $options);
                break;
            case $object instanceof InputDefinition:
                $this->describeInputDefinition($object, $options);
                break;
            case $object instanceof Command:
                $this->describeCommand($object, $options);
                break;
            case $object instanceof Application:
                $this->describeApplication($object, $options);
                break;
            default:
                throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
        }
    }

    /**
     * Writes content to output.
     */
    protected function write(string $content, bool $decorated = false)
    {
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = []);

    /**
     * Describes an InputOption instance.
     */
    abstract protected function describeInputOption(InputOption $option, array $options = []);

    /**
     * Describes an InputDefinition instance.
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);

    /**
     * Describes a Command instance.
     */
    abstract protected function describeCommand(Command $command, array $options = []);

    /**
     * Describes an Application instance.
     */
    abstract protected function describeApplication(Application $application, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, object $object, array $options = [])
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(string $content, bool $decorated = true)
    {
        parent::write($content, $decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->write(
            '#### `'.($argument->getName() ?: '<none>')."`\n\n"
            .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $name = '--'.$option->getName();
        if ($option->isNegatable()) {
            $name .= '|--no-'.$option->getName();
        }
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
        }

        $this->write(
            '#### `'.$name.'`'."\n\n"
            .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        if ($showArguments = \count($definition->getArguments()) > 0) {
            $this->write('### Arguments');
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                if (null !== $describeInputArgument = $this->describeInputArgument($argument)) {
                    $this->write($describeInputArgument);
                }
            }
        }

        if (\count($definition->getOptions()) > 0) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write('### Options');
            foreach ($definition->getOptions() as $option) {
                $this->write("\n\n");
                if (null !== $describeInputOption = $this->describeInputOption($option)) {
                    $this->write($describeInputOption);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        if ($options['short'] ?? false) {
            $this->write(
                '`'.$command->getName()."`\n"
                .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
                .($command->getDescription() ? $command->getDescription()."\n\n" : '')
                .'### Usage'."\n\n"
                .array_reduce($command->getAliases(), function ($carry, $usage) {
                    return $carry.'* `'.$usage.'`'."\n";
                })
            );

            return;
        }

        $command->mergeApplicationDefinition(false);

        $this->write(
            '`'.$command->getName()."`\n"
            .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            .'### Usage'."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
                return $carry.'* `'.$usage.'`'."\n";
            })
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->write("\n\n");
            $this->describeInputDefinition($definition);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat('=', Helper::width($title)));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $this->write("\n\n");
                $this->write('**'.$namespace['id'].':**');
            }

            $this->write("\n\n");
            $this->write(implode("\n", array_map(function ($commandName) use ($description) {
                return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
            }, $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            if (null !== $describeCommand = $this->describeCommand($command, $options)) {
                $this->write($describeCommand);
            }
        }
    }

    private function getApplicationTitle(Application $application): string
    {
        if ('UNKNOWN' !== $application->getName()) {
            if ('UNKNOWN' !== $application->getVersion()) {
                return sprintf('%s %s', $application->getName(), $application->getVersion());
            }

            return $application->getName();
        }

        return 'Console Tool';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($definitionXML = $dom->createElement('definition'));

        $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
        foreach ($definition->getArguments() as $argument) {
            $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
        }

        return $dom;
    }

    public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));

        $commandXML->setAttribute('id', $command->getName());
        $commandXML->setAttribute('name', $command->getName());
        $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);

        $commandXML->appendChild($usagesXML = $dom->createElement('usages'));

        $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));

        if ($short) {
            foreach ($command->getAliases() as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }
        } else {
            $command->mergeApplicationDefinition(false);

            foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }

            $commandXML->appendChild($helpXML = $dom->createElement('help'));
            $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));

            $definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
            $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
        }

        return $dom;
    }

    public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));

        if ('UNKNOWN' !== $application->getName()) {
            $rootXml->setAttribute('name', $application->getName());
            if ('UNKNOWN' !== $application->getVersion()) {
                $rootXml->setAttribute('version', $application->getVersion());
            }
        }

        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $description = new ApplicationDescription($application, $namespace, true);

        if ($namespace) {
            $commandsXML->setAttribute('namespace', $namespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
        }

        if (!$namespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespaceDescription) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);

                foreach ($namespaceDescription['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $dom;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
    }

    /**
     * Appends document children to parent node.
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     */
    private function writeDocument(\DOMDocument $dom)
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('argument'));
        $objectXML->setAttribute('name', $argument->getName());
        $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    private function getInputOptionDocument(InputOption $option): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('option'));
        $objectXML->setAttribute('name', '--'.$option->getName());
        $pos = strpos($option->getShortcut() ?? '', '|');
        if (false !== $pos) {
            $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
            $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
        } else {
            $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
        }
        $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
            $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));

            if (!empty($defaults)) {
                foreach ($defaults as $default) {
                    $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
                    $defaultXML->appendChild($dom->createTextNode($default));
                }
            }
        }

        if ($option->isNegatable()) {
            $dom->appendChild($objectXML = $dom->createElement('option'));
            $objectXML->setAttribute('name', '--no-'.$option->getName());
            $objectXML->setAttribute('shortcut', '');
            $objectXML->setAttribute('accept_value', 0);
            $objectXML->setAttribute('is_value_required', 0);
            $objectXML->setAttribute('is_multiple', 0);
            $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
            $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
        }

        return $dom;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    public const GLOBAL_NAMESPACE = '_global';

    private $application;
    private $namespace;
    private $showHidden;

    /**
     * @var array
     */
    private $namespaces;

    /**
     * @var array<string, Command>
     */
    private $commands;

    /**
     * @var array<string, Command>
     */
    private $aliases;

    public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
    {
        $this->application = $application;
        $this->namespace = $namespace;
        $this->showHidden = $showHidden;
    }

    public function getNamespaces(): array
    {
        if (null === $this->namespaces) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands(): array
    {
        if (null === $this->commands) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @throws CommandNotFoundException
     */
    public function getCommand(string $name): Command
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        return $this->commands[$name] ?? $this->aliases[$name];
    }

    private function inspectApplication()
    {
        $this->commands = [];
        $this->namespaces = [];

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = [];

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
        }
    }

    private function sortCommands(array $commands): array
    {
        $namespacedCommands = [];
        $globalCommands = [];
        $sortedCommands = [];
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
                $globalCommands[$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }

        if ($globalCommands) {
            ksort($globalCommands);
            $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
        }

        if ($namespacedCommands) {
            ksort($namespacedCommands);
            foreach ($namespacedCommands as $key => $commandsSet) {
                ksort($commandsSet);
                $sortedCommands[$key] = $commandsSet;
            }
        }

        return $sortedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $this->writeData($this->getInputOptionData($option), $options);
        if ($option->isNegatable()) {
            $this->writeData($this->getInputOptionData($option, true), $options);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace, true);
        $commands = [];

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command, $options['short'] ?? false);
        }

        $data = [];
        if ('UNKNOWN' !== $application->getName()) {
            $data['application']['name'] = $application->getName();
            if ('UNKNOWN' !== $application->getVersion()) {
                $data['application']['version'] = $application->getVersion();
            }
        }

        $data['commands'] = $commands;

        if ($describedNamespace) {
            $data['namespace'] = $describedNamespace;
        } else {
            $data['namespaces'] = array_values($description->getNamespaces());
        }

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     */
    private function writeData(array $data, array $options)
    {
        $flags = $options['json_encoding'] ?? 0;

        $this->write(json_encode($data, $flags));
    }

    private function getInputArgumentData(InputArgument $argument): array
    {
        return [
            'name' => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array' => $argument->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
            'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
        ];
    }

    private function getInputOptionData(InputOption $option, bool $negated = false): array
    {
        return $negated ? [
            'name' => '--no-'.$option->getName(),
            'shortcut' => '',
            'accept_value' => false,
            'is_value_required' => false,
            'is_multiple' => false,
            'description' => 'Negate the "--'.$option->getName().'" option',
            'default' => false,
        ] : [
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
            'accept_value' => $option->acceptValue(),
            'is_value_required' => $option->isValueRequired(),
            'is_multiple' => $option->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
            'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
        ];
    }

    private function getInputDefinitionData(InputDefinition $definition): array
    {
        $inputArguments = [];
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = [];
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
            if ($option->isNegatable()) {
                $inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
            }
        }

        return ['arguments' => $inputArguments, 'options' => $inputOptions];
    }

    private function getCommandData(Command $command, bool $short = false): array
    {
        $data = [
            'name' => $command->getName(),
            'description' => $command->getDescription(),
        ];

        if ($short) {
            $data += [
                'usage' => $command->getAliases(),
            ];
        } else {
            $command->mergeApplicationDefinition(false);

            $data += [
                'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
                'help' => $command->getProcessedHelp(),
                'definition' => $this->getInputDefinitionData($command->getDefinition()),
            ];
        }

        $data['hidden'] = $command->isHidden();

        return $data;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    public function describe(OutputInterface $output, object $object, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DependencyInjection;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;

/**
 * Registers console commands.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    private $commandLoaderServiceId;
    private $commandTag;
    private $noPreloadTag;
    private $privateTagName;

    public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private')
    {
        if (0 < \func_num_args()) {
            trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
        }

        $this->commandLoaderServiceId = $commandLoaderServiceId;
        $this->commandTag = $commandTag;
        $this->noPreloadTag = $noPreloadTag;
        $this->privateTagName = $privateTagName;
    }

    public function process(ContainerBuilder $container)
    {
        $commandServices = $container->findTaggedServiceIds($this->commandTag, true);
        $lazyCommandMap = [];
        $lazyCommandRefs = [];
        $serviceIds = [];

        foreach ($commandServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $definition->addTag($this->noPreloadTag);
            $class = $container->getParameterBag()->resolveValue($definition->getClass());

            if (isset($tags[0]['command'])) {
                $aliases = $tags[0]['command'];
            } else {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
                }
                $aliases = $class::getDefaultName();
            }

            $aliases = explode('|', $aliases ?? '');
            $commandName = array_shift($aliases);

            if ($isHidden = '' === $commandName) {
                $commandName = array_shift($aliases);
            }

            if (null === $commandName) {
                if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) {
                    $commandId = 'console.command.public_alias.'.$id;
                    $container->setAlias($commandId, $id)->setPublic(true);
                    $id = $commandId;
                }
                $serviceIds[] = $id;

                continue;
            }

            $description = $tags[0]['description'] ?? null;

            unset($tags[0]);
            $lazyCommandMap[$commandName] = $id;
            $lazyCommandRefs[$id] = new TypedReference($id, $class);

            foreach ($aliases as $alias) {
                $lazyCommandMap[$alias] = $id;
            }

            foreach ($tags as $tag) {
                if (isset($tag['command'])) {
                    $aliases[] = $tag['command'];
                    $lazyCommandMap[$tag['command']] = $id;
                }

                $description = $description ?? $tag['description'] ?? null;
            }

            $definition->addMethodCall('setName', [$commandName]);

            if ($aliases) {
                $definition->addMethodCall('setAliases', [$aliases]);
            }

            if ($isHidden) {
                $definition->addMethodCall('setHidden', [true]);
            }

            if (!$description) {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
                }
                $description = $class::getDefaultDescription();
            }

            if ($description) {
                $definition->addMethodCall('setDescription', [$description]);

                $container->register('.'.$id.'.lazy', LazyCommand::class)
                    ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);

                $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
            }
        }

        $container
            ->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
            ->setPublic(true)
            ->addTag($this->noPreloadTag)
            ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);

        $container->setParameter('console.command.ids', $serviceIds);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @author James Halsall <james.t.halsall@googlemail.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ErrorListener implements EventSubscriberInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    public function onConsoleError(ConsoleErrorEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $error = $event->getError();

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);

            return;
        }

        $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
    }

    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $exitCode = $event->getExitCode();

        if (0 === $exitCode) {
            return;
        }

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);

            return;
        }

        $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
    }

    public static function getSubscribedEvents()
    {
        return [
            ConsoleEvents::ERROR => ['onConsoleError', -128],
            ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
        ];
    }

    private static function getInputString(ConsoleEvent $event): ?string
    {
        $commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
        $input = $event->getInput();

        if (method_exists($input, '__toString')) {
            if ($commandName) {
                return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
            }

            return (string) $input;
        }

        return $commandName;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 */
final class Cursor
{
    private $output;
    private $input;

    /**
     * @param resource|null $input
     */
    public function __construct(OutputInterface $output, $input = null)
    {
        $this->output = $output;
        $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
    }

    /**
     * @return $this
     */
    public function moveUp(int $lines = 1): self
    {
        $this->output->write(sprintf("\x1b[%dA", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveDown(int $lines = 1): self
    {
        $this->output->write(sprintf("\x1b[%dB", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveRight(int $columns = 1): self
    {
        $this->output->write(sprintf("\x1b[%dC", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveLeft(int $columns = 1): self
    {
        $this->output->write(sprintf("\x1b[%dD", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToColumn(int $column): self
    {
        $this->output->write(sprintf("\x1b[%dG", $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToPosition(int $column, int $row): self
    {
        $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function savePosition(): self
    {
        $this->output->write("\x1b7");

        return $this;
    }

    /**
     * @return $this
     */
    public function restorePosition(): self
    {
        $this->output->write("\x1b8");

        return $this;
    }

    /**
     * @return $this
     */
    public function hide(): self
    {
        $this->output->write("\x1b[?25l");

        return $this;
    }

    /**
     * @return $this
     */
    public function show(): self
    {
        $this->output->write("\x1b[?25h\x1b[?0c");

        return $this;
    }

    /**
     * Clears all the output from the current line.
     *
     * @return $this
     */
    public function clearLine(): self
    {
        $this->output->write("\x1b[2K");

        return $this;
    }

    /**
     * Clears all the output from the current line after the current position.
     */
    public function clearLineAfter(): self
    {
        $this->output->write("\x1b[K");

        return $this;
    }

    /**
     * Clears all the output from the cursors' current position to the end of the screen.
     *
     * @return $this
     */
    public function clearOutput(): self
    {
        $this->output->write("\x1b[0J");

        return $this;
    }

    /**
     * Clears the entire screen.
     *
     * @return $this
     */
    public function clearScreen(): self
    {
        $this->output->write("\x1b[2J");

        return $this;
    }

    /**
     * Returns the current cursor position as x,y coordinates.
     */
    public function getCurrentPosition(): array
    {
        static $isTtySupported;

        if (null === $isTtySupported && \function_exists('proc_open')) {
            $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
        }

        if (!$isTtySupported) {
            return [1, 1];
        }

        $sttyMode = shell_exec('stty -g');
        shell_exec('stty -icanon -echo');

        @fwrite($this->input, "\033[6n");

        $code = trim(fread($this->input, 1024));

        shell_exec(sprintf('stty %s', $sttyMode));

        sscanf($code, "\033[%d;%dR", $row, $col);

        return [$col, $row];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CI;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Utility class for Github actions.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class GithubActionReporter
{
    private $output;

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
     */
    private const ESCAPED_DATA = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
    ];

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
     */
    private const ESCAPED_PROPERTIES = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
        ':' => '%3A',
        ',' => '%2C',
    ];

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    public static function isGithubActionEnvironment(): bool
    {
        return false !== getenv('GITHUB_ACTIONS');
    }

    /**
     * Output an error using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
     */
    public function error(string $message, string $file = null, int $line = null, int $col = null): void
    {
        $this->log('error', $message, $file, $line, $col);
    }

    /**
     * Output a warning using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
     */
    public function warning(string $message, string $file = null, int $line = null, int $col = null): void
    {
        $this->log('warning', $message, $file, $line, $col);
    }

    /**
     * Output a debug log using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
     */
    public function debug(string $message, string $file = null, int $line = null, int $col = null): void
    {
        $this->log('debug', $message, $file, $line, $col);
    }

    private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void
    {
        // Some values must be encoded.
        $message = strtr($message, self::ESCAPED_DATA);

        if (!$file) {
            // No file provided, output the message solely:
            $this->output->writeln(sprintf('::%s::%s', $type, $message));

            return;
        }

        $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester\Constraint;

use PHPUnit\Framework\Constraint\Constraint;
use Symfony\Component\Console\Command\Command;

final class CommandIsSuccessful extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function toString(): string
    {
        return 'is successful';
    }

    /**
     * {@inheritdoc}
     */
    protected function matches($other): bool
    {
        return Command::SUCCESS === $other;
    }

    /**
     * {@inheritdoc}
     */
    protected function failureDescription($other): string
    {
        return 'the command '.$this->toString();
    }

    /**
     * {@inheritdoc}
     */
    protected function additionalFailureDescription($other): string
    {
        $mapping = [
            Command::FAILURE => 'Command failed.',
            Command::INVALID => 'Command was invalid.',
        ];

        return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use PHPUnit\Framework\Assert;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful;

/**
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
 */
trait TesterTrait
{
    /** @var StreamOutput */
    private $output;
    private $inputs = [];
    private $captureStreamsIndependently = false;
    /** @var InputInterface */
    private $input;
    /** @var int */
    private $statusCode;

    /**
     * Gets the display returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     *
     * @return string
     */
    public function getDisplay(bool $normalize = false)
    {
        if (null === $this->output) {
            throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
        }

        rewind($this->output->getStream());

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

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the output written to STDERR by the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string
     */
    public function getErrorOutput(bool $normalize = false)
    {
        if (!$this->captureStreamsIndependently) {
            throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
        }

        rewind($this->output->getErrorOutput()->getStream());

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

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command or application.
     *
     * @return InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command or application.
     *
     * @return OutputInterface
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     *
     * @return int
     */
    public function getStatusCode()
    {
        if (null === $this->statusCode) {
            throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
        }

        return $this->statusCode;
    }

    public function assertCommandIsSuccessful(string $message = ''): void
    {
        Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message);
    }

    /**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return $this
     */
    public function setInputs(array $inputs)
    {
        $this->inputs = $inputs;

        return $this;
    }

    /**
     * Initializes the output property.
     *
     * Available options:
     *
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     */
    private function initOutput(array $options)
    {
        $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
        if (!$this->captureStreamsIndependently) {
            $this->output = new StreamOutput(fopen('php://memory', 'w', false));
            if (isset($options['decorated'])) {
                $this->output->setDecorated($options['decorated']);
            }
            if (isset($options['verbosity'])) {
                $this->output->setVerbosity($options['verbosity']);
            }
        } else {
            $this->output = new ConsoleOutput(
                $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL,
                $options['decorated'] ?? null
            );

            $errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
            $errorOutput->setFormatter($this->output->getFormatter());
            $errorOutput->setVerbosity($this->output->getVerbosity());
            $errorOutput->setDecorated($this->output->isDecorated());

            $reflectedOutput = new \ReflectionObject($this->output);
            $strErrProperty = $reflectedOutput->getProperty('stderr');
            $strErrProperty->setAccessible(true);
            $strErrProperty->setValue($this->output, $errorOutput);

            $reflectedParent = $reflectedOutput->getParentClass();
            $streamProperty = $reflectedParent->getProperty('stream');
            $streamProperty->setAccessible(true);
            $streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
        }
    }

    /**
     * @return resource
     */
    private static function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        foreach ($inputs as $input) {
            fwrite($stream, $input.\PHP_EOL);
        }

        rewind($stream);

        return $stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;

/**
 * Eases the testing of command completion.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandCompletionTester
{
    private $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Create completion suggestions from input tokens.
     */
    public function complete(array $input): array
    {
        $currentIndex = \count($input);
        if ('' === end($input)) {
            array_pop($input);
        }
        array_unshift($input, $this->command->getName());

        $completionInput = CompletionInput::fromTokens($input, $currentIndex);
        $completionInput->bind($this->command->getDefinition());
        $suggestions = new CompletionSuggestions();

        $this->command->complete($completionInput, $suggestions);

        $options = [];
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $options[] = '--'.$option->getName();
        }

        return array_map('strval', array_merge($options, $suggestions->getValueSuggestions()));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    use TesterTrait;

    private $application;

    public function __construct(Application $application)
    {
        $this->application = $application;
    }

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @return int The command exit code
     */
    public function run(array $input, array $options = [])
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        if ($this->inputs) {
            $this->input->setStream(self::createStream($this->inputs));
        }

        $this->initOutput($options);

        return $this->statusCode = $this->application->run($this->input, $this->output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class CommandTester
{
    use TesterTrait;

    private $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(array $input, array $options = [])
    {
        // set the command name automatically if the application requires
        // this argument and no command name was passed
        if (!isset($input['command'])
            && (null !== $application = $this->command->getApplication())
            && $application->getDefinition()->hasArgument('command')
        ) {
            $input = array_merge(['command' => $this->command->getName()], $input);
        }

        $this->input = new ArrayInput($input);
        // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
        $this->input->setStream(self::createStream($this->inputs));

        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        if (!isset($options['decorated'])) {
            $options['decorated'] = false;
        }

        $this->initOutput($options);

        return $this->statusCode = $this->command->run($this->input, $this->output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\SignalRegistry;

final class SignalRegistry
{
    private $signalHandlers = [];

    public function __construct()
    {
        if (\function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);
        }
    }

    public function register(int $signal, callable $signalHandler): void
    {
        if (!isset($this->signalHandlers[$signal])) {
            $previousCallback = pcntl_signal_get_handler($signal);

            if (\is_callable($previousCallback)) {
                $this->signalHandlers[$signal][] = $previousCallback;
            }
        }

        $this->signalHandlers[$signal][] = $signalHandler;

        pcntl_signal($signal, [$this, 'handle']);
    }

    public static function isSupported(): bool
    {
        if (!\function_exists('pcntl_signal')) {
            return false;
        }

        if (\in_array('pcntl_signal', explode(',', ini_get('disable_functions')))) {
            return false;
        }

        return true;
    }

    /**
     * @internal
     */
    public function handle(int $signal): void
    {
        $count = \count($this->signalHandlers[$signal]);

        foreach ($this->signalHandlers[$signal] as $i => $signalHandler) {
            $hasNext = $i !== $count - 1;
            $signalHandler($signal, $hasNext);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents failure to read input from stdin.
 *
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect namespace typed in the console.
 *
 * @author Pierre du Plessis <pdples@gmail.com>
 */
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private $alternatives;

    /**
     * @param string          $message      Exception message to throw
     * @param string[]        $alternatives List of similar defined names
     * @param int             $code         Exception code
     * @param \Throwable|null $previous     Previous exception used for the exception chaining
     */
    public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->alternatives = $alternatives;
    }

    /**
     * @return string[]
     */
    public function getAlternatives()
    {
        return $this->alternatives;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\CompleteCommand;
use Symfony\Component\Console\Command\DumpCompletionCommand;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application implements ResetInterface
{
    private $commands = [];
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $commandLoader;
    private $catchExceptions = true;
    private $autoExit = true;
    private $definition;
    private $helperSet;
    private $dispatcher;
    private $terminal;
    private $defaultCommand;
    private $singleCommand = false;
    private $initialized;
    private $signalRegistry;
    private $signalsToDispatchEvent = [];

    public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->terminal = new Terminal();
        $this->defaultCommand = 'list';
        if (\defined('SIGINT') && SignalRegistry::isSupported()) {
            $this->signalRegistry = new SignalRegistry();
            $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
        }
    }

    /**
     * @final
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        $this->commandLoader = $commandLoader;
    }

    public function getSignalRegistry(): SignalRegistry
    {
        if (!$this->signalRegistry) {
            throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
        }

        return $this->signalRegistry;
    }

    public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
    {
        $this->signalsToDispatchEvent = $signalsToDispatchEvent;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        if (\function_exists('putenv')) {
            @putenv('LINES='.$this->terminal->getHeight());
            @putenv('COLUMNS='.$this->terminal->getWidth());
        }

        if (null === $input) {
            $input = new ArgvInput();
        }

        if (null === $output) {
            $output = new ConsoleOutput();
        }

        $renderException = function (\Throwable $e) use ($output) {
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderThrowable($e, $output->getErrorOutput());
            } else {
                $this->renderThrowable($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $errorHandler = true;
            } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($errorHandler);
            }
        }

        $this->configureIO($input, $output);

        try {
            $exitCode = $this->doRun($input, $output);
        } catch (\Exception $e) {
            if (!$this->catchExceptions) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if (0 === $exitCode) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$errorHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--version', '-V'], true)) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['--help', '-h'], true)) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(['command_name' => $this->defaultCommand]);
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                [
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                ]
            ));
        }

        try {
            $this->runningCommand = null;
            // the command name MUST be the first element of the input
            $command = $this->find($name);
        } catch (\Throwable $e) {
            if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
                if (null !== $this->dispatcher) {
                    $event = new ConsoleErrorEvent($input, $output, $e);
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                    if (0 === $event->getExitCode()) {
                        return 0;
                    }

                    $e = $event->getError();
                }

                throw $e;
            }

            $alternative = $alternatives[0];

            $style = new SymfonyStyle($input, $output);
            $style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
            if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
                if (null !== $this->dispatcher) {
                    $event = new ConsoleErrorEvent($input, $output, $e);
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                    return $event->getExitCode();
                }

                return 1;
            }

            $command = $this->find($alternative);
        }

        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    /**
     * {@inheritdoc}
     */
    public function reset()
    {
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     *
     * @return HelperSet
     */
    public function getHelperSet()
    {
        if (!$this->helperSet) {
            $this->helperSet = $this->getDefaultHelperSet();
        }

        return $this->helperSet;
    }

    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     *
     * @return InputDefinition
     */
    public function getDefinition()
    {
        if (!$this->definition) {
            $this->definition = $this->getDefaultInputDefinition();
        }

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if (
            CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && 'command' === $input->getCompletionName()
        ) {
            $suggestions->suggestValues(array_filter(array_map(function (Command $command) {
                return $command->isHidden() ? null : $command->getName();
            }, $this->all())));

            return;
        }

        if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
            $suggestions->suggestOptions($this->getDefinition()->getOptions());

            return;
        }
    }

    /**
     * Gets the help message.
     *
     * @return string
     */
    public function getHelp()
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     *
     * @return bool
     */
    public function areExceptionsCaught()
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     */
    public function setCatchExceptions(bool $boolean)
    {
        $this->catchExceptions = $boolean;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     *
     * @return bool
     */
    public function isAutoExitEnabled()
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     */
    public function setAutoExit(bool $boolean)
    {
        $this->autoExit = $boolean;
    }

    /**
     * Gets the name of the application.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     **/
    public function setName(string $name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     */
    public function setVersion(string $version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName()) {
            if ('UNKNOWN' !== $this->getVersion()) {
                return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return 'Console Tool';
    }

    /**
     * Registers a new command.
     *
     * @return Command
     */
    public function register(string $name)
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * If a Command is not enabled it will not be added.
     *
     * @param Command[] $commands An array of commands
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * Adds a command object.
     *
     * If a command with the same name already exists, it will be overridden.
     * If the command is not enabled it will not be added.
     *
     * @return Command|null
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return null;
        }

        if (!$command instanceof LazyCommand) {
            // Will throw if the command is not correctly initialized.
            $command->getDefinition();
        }

        if (!$command->getName()) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get(string $name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
        }

        // When the command has a different name than the one used at the command loader level
        if (!isset($this->commands[$name])) {
            throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
        }

        $command = $this->commands[$name];

        if ($this->wantHelps) {
            $this->wantHelps = false;

            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand;
        }

        return $command;
    }

    /**
     * Returns true if the command exists, false otherwise.
     *
     * @return bool
     */
    public function has(string $name)
    {
        $this->init();

        return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[]
     */
    public function getNamespaces()
    {
        $namespaces = [];
        foreach ($this->all() as $command) {
            if ($command->isHidden()) {
                continue;
            }

            $namespaces[] = $this->extractAllNamespaces($command->getName());

            foreach ($command->getAliases() as $alias) {
                $namespaces[] = $this->extractAllNamespaces($alias);
            }
        }

        return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
    }

    /**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @return string
     *
     * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
     */
    public function findNamespace(string $namespace)
    {
        $allNamespaces = $this->getNamespaces();
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);

        if (empty($namespaces)) {
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);

            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
                if (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }

                $message .= implode("\n    ", $alternatives);
            }

            throw new NamespaceNotFoundException($message, $alternatives);
        }

        $exact = \in_array($namespace, $namespaces, true);
        if (\count($namespaces) > 1 && !$exact) {
            throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
        }

        return $exact ? $namespace : reset($namespaces);
    }

    /**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    public function find(string $name)
    {
        $this->init();

        $aliases = [];

        foreach ($this->commands as $command) {
            foreach ($command->getAliases() as $alias) {
                if (!$this->has($alias)) {
                    $this->commands[$alias] = $command;
                }
            }
        }

        if ($this->has($name)) {
            return $this->get($name);
        }

        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands)) {
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
        }

        // if no commands matched or we just matched namespaces
        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                // remove hidden commands
                $alternatives = array_filter($alternatives, function ($name) {
                    return !$this->get($name)->isHidden();
                });

                if (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }
                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, array_values($alternatives));
        }

        // filter out aliases for commands which are already on the list
        if (\count($commands) > 1) {
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
                if (!$commandList[$nameOrAlias] instanceof Command) {
                    $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
                }

                $commandName = $commandList[$nameOrAlias]->getName();

                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
            }));
        }

        if (\count($commands) > 1) {
            $usableWidth = $this->terminal->getWidth() - 10;
            $abbrevs = array_values($commands);
            $maxLen = 0;
            foreach ($abbrevs as $abbrev) {
                $maxLen = max(Helper::width($abbrev), $maxLen);
            }
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
                if ($commandList[$cmd]->isHidden()) {
                    unset($commands[array_search($cmd, $commands)]);

                    return false;
                }

                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();

                return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
            }, array_values($commands));

            if (\count($commands) > 1) {
                $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));

                throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
            }
        }

        $command = $this->get(reset($commands));

        if ($command->isHidden()) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
        }

        return $command;
    }

    /**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @return Command[]
     */
    public function all(string $namespace = null)
    {
        $this->init();

        if (null === $namespace) {
            if (!$this->commandLoader) {
                return $this->commands;
            }

            $commands = $this->commands;
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }

            return $commands;
        }

        $commands = [];
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        if ($this->commandLoader) {
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }
        }

        return $commands;
    }

    /**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @return string[][]
     */
    public static function getAbbreviations(array $names)
    {
        $abbrevs = [];
        foreach ($names as $name) {
            for ($len = \strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    public function renderThrowable(\Throwable $e, OutputInterface $output): void
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        $this->doRenderThrowable($e, $output);

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
    {
        do {
            $message = trim($e->getMessage());
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $class = get_debug_type($e);
                $title = sprintf('  [%s%s]  ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
                $len = Helper::width($title);
            } else {
                $len = 0;
            }

            if (str_contains($message, "@anonymous\0")) {
                $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
                    return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
                }, $message);
            }

            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
            $lines = [];
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
                    // pre-format lines to get the right string length
                    $lineLength = Helper::width($line) + 4;
                    $lines[] = [$line, $lineLength];

                    $len = max($lineLength, $len);
                }
            }

            $messages = [];
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
            }
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
            }
            foreach ($lines as $line) {
                $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
            }
            $messages[] = $emptyLine;
            $messages[] = '';

            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();

                array_unshift($trace, [
                    'function' => '',
                    'file' => $e->getFile() ?: 'n/a',
                    'line' => $e->getLine() ?: 'n/a',
                    'args' => [],
                ]);

                for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
                    $class = $trace[$i]['class'] ?? '';
                    $type = $trace[$i]['type'] ?? '';
                    $function = $trace[$i]['function'] ?? '';
                    $file = $trace[$i]['file'] ?? 'n/a';
                    $line = $trace[$i]['line'] ?? 'n/a';

                    $output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--ansi'], true)) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
            $input->setInteractive(false);
        }

        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
            case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
            case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
            case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
            default: $shellVerbosity = 0; break;
        }

        if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $shellVerbosity = -1;
        } else {
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $shellVerbosity = 3;
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                $shellVerbosity = 2;
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                $shellVerbosity = 1;
            }
        }

        if (-1 === $shellVerbosity) {
            $input->setInteractive(false);
        }

        if (\function_exists('putenv')) {
            @putenv('SHELL_VERBOSITY='.$shellVerbosity);
        }
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        if ($command instanceof SignalableCommandInterface && ($this->signalsToDispatchEvent || $command->getSubscribedSignals())) {
            if (!$this->signalRegistry) {
                throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
            }

            if ($this->dispatcher) {
                foreach ($this->signalsToDispatchEvent as $signal) {
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);

                    $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);

                        // No more handlers, we try to simulate PHP default behavior
                        if (!$hasNext) {
                            if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
                                exit(0);
                            }
                        }
                    });
                }
            }

            foreach ($command->getSubscribedSignals() as $signal) {
                $this->signalRegistry->register($signal, [$command, 'handleSignal']);
            }
        }

        if (null === $this->dispatcher) {
            return $command->run($input, $output);
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface $e) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $e = null;

        try {
            $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Throwable $e) {
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
            $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     *
     * @return string|null
     */
    protected function getCommandName(InputInterface $input)
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ]);
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return Command[]
     */
    protected function getDefaultCommands()
    {
        return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     *
     * @return HelperSet
     */
    protected function getDefaultHelperSet()
    {
        return new HelperSet([
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ]);
    }

    /**
     * Returns abbreviated suggestions in string format.
     */
    private function getAbbreviationSuggestions(array $abbrevs): string
    {
        return '    '.implode("\n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @return string
     */
    public function extractNamespace(string $name, int $limit = null)
    {
        $parts = explode(':', $name, -1);

        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @return string[]
     */
    private function findAlternatives(string $name, iterable $collection): array
    {
        $threshold = 1e3;
        $alternatives = [];

        $collectionParts = [];
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        foreach ($collection as $item) {
            $lev = levenshtein($name, $item);
            if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
            }
        }

        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
        ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @return $this
     */
    public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
    {
        $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    /**
     * @internal
     */
    public function isSingleCommand(): bool
    {
        return $this->singleCommand;
    }

    private function splitStringByWidth(string $string, int $width): array
    {
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
        // additionally, array_slice() is not enough as some character has doubled width.
        // we need a function to split string not by character count but by string width
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = [];
        $line = '';

        $offset = 0;
        while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
            $offset += \strlen($m[0]);

            foreach (preg_split('//u', $m[0]) as $char) {
                // test if $char could be appended to current line
                if (mb_strwidth($line.$char, 'utf8') <= $width) {
                    $line .= $char;
                    continue;
                }
                // if not, push current line to array and make new line
                $lines[] = str_pad($line, $width);
                $line = $char;
            }
        }

        $lines[] = \count($lines) ? str_pad($line, $width) : $line;

        mb_convert_variables($encoding, 'utf8', $lines);

        return $lines;
    }

    /**
     * Returns all namespaces of the command name.
     *
     * @return string[]
     */
    private function extractAllNamespaces(string $name): array
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = [];

        foreach ($parts as $part) {
            if (\count($namespaces)) {
                $namespaces[] = end($namespaces).':'.$part;
            } else {
                $namespaces[] = $part;
            }
        }

        return $namespaces;
    }

    private function init()
    {
        if ($this->initialized) {
            return;
        }
        $this->initialized = true;

        foreach ($this->getDefaultCommands() as $command) {
            $this->add($command);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or changing the input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class ConsoleCommandEvent extends ConsoleEvent
{
    /**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    public const RETURN_CODE_DISABLED = 113;

    /**
     * Indicates if the command should be run or skipped.
     */
    private $commandShouldRun = true;

    /**
     * Disables the command, so it won't be run.
     */
    public function disableCommand(): bool
    {
        return $this->commandShouldRun = false;
    }

    public function enableCommand(): bool
    {
        return $this->commandShouldRun = true;
    }

    /**
     * Returns true if the command is runnable, false otherwise.
     */
    public function commandShouldRun(): bool
    {
        return $this->commandShouldRun;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle throwables thrown while running a command.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private $error;
    private $exitCode;

    public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
    {
        parent::__construct($command, $input, $output);

        $this->error = $error;
    }

    public function getError(): \Throwable
    {
        return $this->error;
    }

    public function setError(\Throwable $error): void
    {
        $this->error = $error;
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;

        $r = new \ReflectionProperty($this->error, 'code');
        $r->setAccessible(true);
        $r->setValue($this->error, $this->exitCode);
    }

    public function getExitCode(): int
    {
        return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleTerminateEvent extends ConsoleEvent
{
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setExitCode($exitCode);
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;
    }

    public function getExitCode(): int
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private $input;
    private $output;

    public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     *
     * @return Command|null
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * Gets the input instance.
     *
     * @return InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance.
     *
     * @return OutputInterface
     */
    public function getOutput()
    {
        return $this->output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author marie <marie@users.noreply.github.com>
 */
final class ConsoleSignalEvent extends ConsoleEvent
{
    private $handlingSignal;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
    {
        parent::__construct($command, $input, $output);
        $this->handlingSignal = $handlingSignal;
    }

    public function getHandlingSignal(): int
    {
        return $this->handlingSignal;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class Color
{
    private const COLORS = [
        'black' => 0,
        'red' => 1,
        'green' => 2,
        'yellow' => 3,
        'blue' => 4,
        'magenta' => 5,
        'cyan' => 6,
        'white' => 7,
        'default' => 9,
    ];

    private const BRIGHT_COLORS = [
        'gray' => 0,
        'bright-red' => 1,
        'bright-green' => 2,
        'bright-yellow' => 3,
        'bright-blue' => 4,
        'bright-magenta' => 5,
        'bright-cyan' => 6,
        'bright-white' => 7,
    ];

    private const AVAILABLE_OPTIONS = [
        'bold' => ['set' => 1, 'unset' => 22],
        'underscore' => ['set' => 4, 'unset' => 24],
        'blink' => ['set' => 5, 'unset' => 25],
        'reverse' => ['set' => 7, 'unset' => 27],
        'conceal' => ['set' => 8, 'unset' => 28],
    ];

    private $foreground;
    private $background;
    private $options = [];

    public function __construct(string $foreground = '', string $background = '', array $options = [])
    {
        $this->foreground = $this->parseColor($foreground);
        $this->background = $this->parseColor($background, true);

        foreach ($options as $option) {
            if (!isset(self::AVAILABLE_OPTIONS[$option])) {
                throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
            }

            $this->options[$option] = self::AVAILABLE_OPTIONS[$option];
        }
    }

    public function apply(string $text): string
    {
        return $this->set().$text.$this->unset();
    }

    public function set(): string
    {
        $setCodes = [];
        if ('' !== $this->foreground) {
            $setCodes[] = $this->foreground;
        }
        if ('' !== $this->background) {
            $setCodes[] = $this->background;
        }
        foreach ($this->options as $option) {
            $setCodes[] = $option['set'];
        }
        if (0 === \count($setCodes)) {
            return '';
        }

        return sprintf("\033[%sm", implode(';', $setCodes));
    }

    public function unset(): string
    {
        $unsetCodes = [];
        if ('' !== $this->foreground) {
            $unsetCodes[] = 39;
        }
        if ('' !== $this->background) {
            $unsetCodes[] = 49;
        }
        foreach ($this->options as $option) {
            $unsetCodes[] = $option['unset'];
        }
        if (0 === \count($unsetCodes)) {
            return '';
        }

        return sprintf("\033[%sm", implode(';', $unsetCodes));
    }

    private function parseColor(string $color, bool $background = false): string
    {
        if ('' === $color) {
            return '';
        }

        if ('#' === $color[0]) {
            $color = substr($color, 1);

            if (3 === \strlen($color)) {
                $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];
            }

            if (6 !== \strlen($color)) {
                throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));
            }

            return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color));
        }

        if (isset(self::COLORS[$color])) {
            return ($background ? '4' : '3').self::COLORS[$color];
        }

        if (isset(self::BRIGHT_COLORS[$color])) {
            return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
        }

        throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
    }

    private function convertHexColorToAnsi(int $color): string
    {
        $r = ($color >> 16) & 255;
        $g = ($color >> 8) & 255;
        $b = $color & 255;

        // see https://github.com/termstandard/colors/ for more information about true color support
        if ('truecolor' !== getenv('COLORTERM')) {
            return (string) $this->degradeHexColorToAnsi($r, $g, $b);
        }

        return sprintf('8;2;%d;%d;%d', $r, $g, $b);
    }

    private function degradeHexColorToAnsi(int $r, int $g, int $b): int
    {
        if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
            return 0;
        }

        return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);
    }

    private function getSaturation(int $r, int $g, int $b): int
    {
        $r = $r / 255;
        $g = $g / 255;
        $b = $b / 255;
        $v = max($r, $g, $b);

        if (0 === $diff = $v - min($r, $g, $b)) {
            return 0;
        }

        return (int) $diff * 100 / $v;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Attribute;

/**
 * Service tag to autoconfigure commands.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsCommand
{
    public function __construct(
        public string $name,
        public ?string $description = null,
        array $aliases = [],
        bool $hidden = false,
    ) {
        if (!$hidden && !$aliases) {
            return;
        }

        $name = explode('|', $name);
        $name = array_merge($name, $aliases);

        if ($hidden && '' !== $name[0]) {
            array_unshift($name, '');
        }

        $this->name = implode('|', $name);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * StreamableInputInterface is the interface implemented by all input classes
 * that have an input stream.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface StreamableInputInterface extends InputInterface
{
    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     */
    public function setStream($stream);

    /**
     * Returns the input stream.
     *
     * @return resource|null
     */
    public function getStream();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition([
 *         new InputArgument('name', InputArgument::REQUIRED),
 *         new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ]);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputDefinition
{
    private $arguments;
    private $requiredCount;
    private $lastArrayArgument;
    private $lastOptionalArgument;
    private $options;
    private $negations;
    private $shortcuts;

    /**
     * @param array $definition An array of InputArgument and InputOption instance
     */
    public function __construct(array $definition = [])
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     */
    public function setDefinition(array $definition)
    {
        $arguments = [];
        $options = [];
        foreach ($definition as $item) {
            if ($item instanceof InputOption) {
                $options[] = $item;
            } else {
                $arguments[] = $item;
            }
        }

        $this->setArguments($arguments);
        $this->setOptions($options);
    }

    /**
     * Sets the InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function setArguments(array $arguments = [])
    {
        $this->arguments = [];
        $this->requiredCount = 0;
        $this->lastOptionalArgument = null;
        $this->lastArrayArgument = null;
        $this->addArguments($arguments);
    }

    /**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function addArguments(?array $arguments = [])
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * @throws LogicException When incorrect argument is given
     */
    public function addArgument(InputArgument $argument)
    {
        if (isset($this->arguments[$argument->getName()])) {
            throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
        }

        if (null !== $this->lastArrayArgument) {
            throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName()));
        }

        if ($argument->isRequired() && null !== $this->lastOptionalArgument) {
            throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName()));
        }

        if ($argument->isArray()) {
            $this->lastArrayArgument = $argument;
        }

        if ($argument->isRequired()) {
            ++$this->requiredCount;
        } else {
            $this->lastOptionalArgument = $argument;
        }

        $this->arguments[$argument->getName()] = $argument;
    }

    /**
     * Returns an InputArgument by name or by position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return InputArgument
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument($name)
    {
        if (!$this->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return $arguments[$name];
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool
     */
    public function hasArgument($name)
    {
        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset($arguments[$name]);
    }

    /**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[]
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     *
     * @return int
     */
    public function getArgumentCount()
    {
        return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     *
     * @return int
     */
    public function getArgumentRequiredCount()
    {
        return $this->requiredCount;
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getArgumentDefaults()
    {
        $values = [];
        foreach ($this->arguments as $argument) {
            $values[$argument->getName()] = $argument->getDefault();
        }

        return $values;
    }

    /**
     * Sets the InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function setOptions(array $options = [])
    {
        $this->options = [];
        $this->shortcuts = [];
        $this->negations = [];
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function addOptions(array $options = [])
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * @throws LogicException When option given already exist
     */
    public function addOption(InputOption $option)
    {
        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
            throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
        }
        if (isset($this->negations[$option->getName()])) {
            throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
        }

        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                    throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
                }
            }
        }

        $this->options[$option->getName()] = $option;
        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                $this->shortcuts[$shortcut] = $option->getName();
            }
        }

        if ($option->isNegatable()) {
            $negatedName = 'no-'.$option->getName();
            if (isset($this->options[$negatedName])) {
                throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName));
            }
            $this->negations[$negatedName] = $option->getName();
        }
    }

    /**
     * Returns an InputOption by name.
     *
     * @return InputOption
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name)
    {
        if (!$this->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
        }

        return $this->options[$name];
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * This method can't be used to check if the user included the option when
     * executing the command (use getOption() instead).
     *
     * @return bool
     */
    public function hasOption(string $name)
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[]
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     *
     * @return bool
     */
    public function hasShortcut(string $name)
    {
        return isset($this->shortcuts[$name]);
    }

    /**
     * Returns true if an InputOption object exists by negated name.
     */
    public function hasNegation(string $name): bool
    {
        return isset($this->negations[$name]);
    }

    /**
     * Gets an InputOption by shortcut.
     *
     * @return InputOption
     */
    public function getOptionForShortcut(string $shortcut)
    {
        return $this->getOption($this->shortcutToName($shortcut));
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptionDefaults()
    {
        $values = [];
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function shortcutToName(string $shortcut): string
    {
        if (!isset($this->shortcuts[$shortcut])) {
            throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        return $this->shortcuts[$shortcut];
    }

    /**
     * Returns the InputOption name given a negation.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function negationToName(string $negation): string
    {
        if (!isset($this->negations[$negation])) {
            throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation));
        }

        return $this->negations[$negation];
    }

    /**
     * Gets the synopsis.
     *
     * @return string
     */
    public function getSynopsis(bool $short = false)
    {
        $elements = [];

        if ($short && $this->getOptions()) {
            $elements[] = '[options]';
        } elseif (!$short) {
            foreach ($this->getOptions() as $option) {
                $value = '';
                if ($option->acceptValue()) {
                    $value = sprintf(
                        ' %s%s%s',
                        $option->isValueOptional() ? '[' : '',
                        strtoupper($option->getName()),
                        $option->isValueOptional() ? ']' : ''
                    );
                }

                $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
                $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : '';
                $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation);
            }
        }

        if (\count($elements) && $this->getArguments()) {
            $elements[] = '[--]';
        }

        $tail = '';
        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if ($argument->isArray()) {
                $element .= '...';
            }

            if (!$argument->isRequired()) {
                $element = '['.$element;
                $tail .= ']';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements).$tail;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private $parameters;

    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $param => $value) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                continue;
            }

            return $value;
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, bool $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!\is_int($k)) {
                $v = $k;
            }

            if ($onlyParams && '--' === $v) {
                return false;
            }

            if (\in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, bool $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
                return $default;
            }

            if (\is_int($k)) {
                if (\in_array($v, $values)) {
                    return true;
                }
            } elseif (\in_array($k, $values)) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $params = [];
        foreach ($this->parameters as $param => $val) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                $glue = ('-' === $param[1]) ? '=' : ' ';
                if (\is_array($val)) {
                    foreach ($val as $v) {
                        $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
                    }
                } else {
                    $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
                }
            } else {
                $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if ('--' === $key) {
                return;
            }
            if (str_starts_with($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif (str_starts_with($key, '-')) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption(string $name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isValueOptional()) {
                $value = true;
            }
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @param string|int $name  The argument name
     * @param mixed      $value The value for the argument
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    private function addArgument($name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface InputInterface
{
    /**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string|null
     */
    public function getFirstArgument();

    /**
     * Returns true if the raw parameters (not parsed) contain a value.
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The values to look for in the raw parameters (can be an array)
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return bool
     */
    public function hasParameterOption($values, bool $onlyParams = false);

    /**
     * Returns the value of a raw option (not parsed).
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array                     $values     The value(s) to look for in the raw parameters (can be an array)
     * @param string|bool|int|float|array|null $default    The default value to return if no result is found
     * @param bool                             $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return mixed
     */
    public function getParameterOption($values, $default = false, bool $onlyParams = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @throws RuntimeException
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates the input.
     *
     * @throws RuntimeException When not enough arguments are given
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getArguments();

    /**
     * Returns the argument value for a given argument name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument(string $name);

    /**
     * Sets an argument value by name.
     *
     * @param mixed $value The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function setArgument(string $name, $value);

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @return bool
     */
    public function hasArgument(string $name);

    /**
     * Returns all the given options merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptions();

    /**
     * Returns the option value for a given option name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name);

    /**
     * Sets an option value by name.
     *
     * @param mixed $value The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function setOption(string $name, $value);

    /**
     * Returns true if an InputOption object exists by name.
     *
     * @return bool
     */
    public function hasOption(string $name);

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Sets the input interactivity.
     */
    public function setInteractive(bool $interactive);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string $input A string representing the parameters from the CLI
     */
    public function __construct(string $input)
    {
        parent::__construct([]);

        $this->setTokens($this->tokenize($input));
    }

    /**
     * Tokenizes a string.
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize(string $input): array
    {
        $tokens = [];
        $length = \strlen($input);
        $cursor = 0;
        while ($cursor < $length) {
            if (preg_match('/\s+/A', $input, $match, 0, $cursor)) {
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) {
                $tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
                $tokens[] = stripcslashes(substr($match[0], 1, -1));
            } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, 0, $cursor)) {
                $tokens[] = stripcslashes($match[1]);
            } else {
                // should never happen
                throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
            }

            $cursor += \strlen($match[0]);
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputArgument
{
    public const REQUIRED = 1;
    public const OPTIONAL = 2;
    public const IS_ARRAY = 4;

    private $name;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string                           $name        The argument name
     * @param int|null                         $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string                           $description A description text
     * @param string|bool|int|float|array|null $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct(string $name, int $mode = null, string $description = '', $default = null)
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif ($mode > 7 || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->mode = $mode;
        $this->description = $description;

        $this->setDefault($default);
    }

    /**
     * Returns the argument name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    public function isRequired()
    {
        return self::REQUIRED === (self::REQUIRED & $this->mode);
    }

    /**
     * Returns true if the argument can take multiple values.
     *
     * @return bool true if mode is self::IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @param string|bool|int|float|array|null $default
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault($default = null)
    {
        if (self::REQUIRED === $this->mode && null !== $default) {
            throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array argument must be an array.');
            }
        }

        $this->default = $default;
    }

    /**
     * Returns the default value.
     *
     * @return string|bool|int|float|array|null
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface, StreamableInputInterface
{
    protected $definition;
    protected $stream;
    protected $options = [];
    protected $arguments = [];
    protected $interactive = true;

    public function __construct(InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = [];
        $this->options = [];
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     */
    abstract protected function parse();

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $definition = $this->definition;
        $givenArguments = $this->arguments;

        $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
            return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
        });

        if (\count($missingArguments) > 0) {
            throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isInteractive()
    {
        return $this->interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function setInteractive(bool $interactive)
    {
        $this->interactive = $interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function getArguments()
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    /**
     * {@inheritdoc}
     */
    public function getArgument(string $name)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setArgument(string $name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasArgument(string $name)
    {
        return $this->definition->hasArgument($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function getOption(string $name)
    {
        if ($this->definition->hasNegation($name)) {
            if (null === $value = $this->getOption($this->definition->negationToName($name))) {
                return $value;
            }

            return !$value;
        }

        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setOption(string $name, $value)
    {
        if ($this->definition->hasNegation($name)) {
            $this->options[$this->definition->negationToName($name)] = !$value;

            return;
        } elseif (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        $this->options[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasOption(string $name)
    {
        return $this->definition->hasOption($name) || $this->definition->hasNegation($name);
    }

    /**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     *
     * @return string
     */
    public function escapeToken(string $token)
    {
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
    }

    /**
     * {@inheritdoc}
     */
    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    /**
     * {@inheritdoc}
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private $tokens;
    private $parsed;

    public function __construct(array $argv = null, InputDefinition $definition = null)
    {
        $argv = $argv ?? $_SERVER['argv'] ?? [];

        // strip the application name
        array_shift($argv);

        $this->tokens = $argv;

        parent::__construct($definition);
    }

    protected function setTokens(array $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokens;
        while (null !== $token = array_shift($this->parsed)) {
            $parseOptions = $this->parseToken($token, $parseOptions);
        }
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        if ($parseOptions && '' == $token) {
            $this->parseArgument($token);
        } elseif ($parseOptions && '--' == $token) {
            return false;
        } elseif ($parseOptions && str_starts_with($token, '--')) {
            $this->parseLongOption($token);
        } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
            $this->parseShortOption($token);
        } else {
            $this->parseArgument($token);
        }

        return $parseOptions;
    }

    /**
     * Parses a short option.
     */
    private function parseShortOption(string $token)
    {
        $name = substr($token, 1);

        if (\strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet(string $name)
    {
        $len = \strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                $encoding = mb_detect_encoding($name, null, true);
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            } else {
                $this->addLongOption($option->getName(), null);
            }
        }
    }

    /**
     * Parses a long option.
     */
    private function parseLongOption(string $token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if ('' === $value = substr($name, $pos + 1)) {
                array_unshift($this->parsed, $value);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument(string $token)
    {
        $c = \count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;

        // if last argument isArray(), append token to last argument
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

        // unexpected argument
        } else {
            $all = $this->definition->getArguments();
            $symfonyCommandName = null;
            if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
                $symfonyCommandName = $this->arguments['command'] ?? null;
                unset($all[$key]);
            }

            if (\count($all)) {
                if ($symfonyCommandName) {
                    $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
                } else {
                    $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
                }
            } elseif ($symfonyCommandName) {
                $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
            } else {
                $message = sprintf('No arguments expected, got "%s".', $token);
            }

            throw new RuntimeException($message);
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption(string $name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            if (null !== $value) {
                throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
            }
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
            // if option accepts an optional or mandatory argument
            // let's see if there is one provided
            $next = array_shift($this->parsed);
            if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
                $value = $next;
            } else {
                array_unshift($this->parsed, $next);
            }
        }

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isArray() && !$option->isValueOptional()) {
                $value = true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        $isOption = false;
        foreach ($this->tokens as $i => $token) {
            if ($token && '-' === $token[0]) {
                if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
                    continue;
                }

                // If it's a long option, consider that everything after "--" is the option name.
                // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
                $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
                if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
                    // noop
                } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
                    $isOption = true;
                }

                continue;
            }

            if ($isOption) {
                $isOption = false;
                continue;
            }

            return $token;
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, bool $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            if ($onlyParams && '--' === $token) {
                return false;
            }
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, bool $onlyParams = false)
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < \count($tokens)) {
            $token = array_shift($tokens);
            if ($onlyParams && '--' === $token) {
                return $default;
            }

            foreach ($values as $value) {
                if ($token === $value) {
                    return array_shift($tokens);
                }
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && str_starts_with($token, $leading)) {
                    return substr($token, \strlen($leading));
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $tokens = array_map(function ($token) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$this->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $this->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

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

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    /**
     * Do not accept input for the option (e.g. --yell). This is the default behavior of options.
     */
    public const VALUE_NONE = 1;

    /**
     * A value must be passed when the option is used (e.g. --iterations=5 or -i5).
     */
    public const VALUE_REQUIRED = 2;

    /**
     * The option may or may not have a value (e.g. --yell or --yell=loud).
     */
    public const VALUE_OPTIONAL = 4;

    /**
     * The option accepts multiple values (e.g. --dir=/foo --dir=/bar).
     */
    public const VALUE_IS_ARRAY = 8;

    /**
     * The option may have either positive or negative value (e.g. --ansi or --no-ansi).
     */
    public const VALUE_NEGATABLE = 16;

    private $name;
    private $shortcut;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string|array|null                $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                         $mode     The option mode: One of the VALUE_* constants
     * @param string|bool|int|float|array|null $default  The default value (must be null for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
    {
        if (str_starts_with($name, '--')) {
            $name = substr($name, 2);
        }

        if (empty($name)) {
            throw new InvalidArgumentException('An option name cannot be empty.');
        }

        if (empty($shortcut)) {
            $shortcut = null;
        }

        if (null !== $shortcut) {
            if (\is_array($shortcut)) {
                $shortcut = implode('|', $shortcut);
            }
            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
            $shortcuts = array_filter($shortcuts);
            $shortcut = implode('|', $shortcuts);

            if (empty($shortcut)) {
                throw new InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (null === $mode) {
            $mode = self::VALUE_NONE;
        } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->shortcut = $shortcut;
        $this->mode = $mode;
        $this->description = $description;

        if ($this->isArray() && !$this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
        }
        if ($this->isNegatable() && $this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.');
        }

        $this->setDefault($default);
    }

    /**
     * Returns the option shortcut.
     *
     * @return string|null
     */
    public function getShortcut()
    {
        return $this->shortcut;
    }

    /**
     * Returns the option name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the option accepts a value.
     *
     * @return bool true if value mode is not self::VALUE_NONE, false otherwise
     */
    public function acceptValue()
    {
        return $this->isValueRequired() || $this->isValueOptional();
    }

    /**
     * Returns true if the option requires a value.
     *
     * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
     */
    public function isValueRequired()
    {
        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
    }

    /**
     * Returns true if the option takes an optional value.
     *
     * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
     */
    public function isValueOptional()
    {
        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
    }

    /**
     * Returns true if the option can take multiple values.
     *
     * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
    }

    public function isNegatable(): bool
    {
        return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
    }

    /**
     * @param string|bool|int|float|array|null $default
     */
    public function setDefault($default = null)
    {
        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
            throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array option must be an array.');
            }
        }

        $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false;
    }

    /**
     * Returns the default value.
     *
     * @return string|bool|int|float|array|null
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Checks whether the given option equals this one.
     *
     * @return bool
     */
    public function equals(self $option)
    {
        return $option->getName() === $this->getName()
            && $option->getShortcut() === $this->getShortcut()
            && $option->getDefault() === $this->getDefault()
            && $option->isNegatable() === $this->isNegatable()
            && $option->isArray() === $this->isArray()
            && $option->isValueRequired() === $this->isValueRequired()
            && $option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    public const VERBOSITY_QUIET = 16;
    public const VERBOSITY_NORMAL = 32;
    public const VERBOSITY_VERBOSE = 64;
    public const VERBOSITY_VERY_VERBOSE = 128;
    public const VERBOSITY_DEBUG = 256;

    public const OUTPUT_NORMAL = 1;
    public const OUTPUT_RAW = 2;
    public const OUTPUT_PLAIN = 4;

    /**
     * Writes a message to the output.
     *
     * @param string|iterable $messages The message as an iterable of strings or a single string
     * @param bool            $newline  Whether to add a newline
     * @param int             $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function write($messages, bool $newline = false, int $options = 0);

    /**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param string|iterable $messages The message as an iterable of strings or a single string
     * @param int             $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function writeln($messages, int $options = 0);

    /**
     * Sets the verbosity of the output.
     */
    public function setVerbosity(int $level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return int
     */
    public function getVerbosity();

    /**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool
     */
    public function isQuiet();

    /**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool
     */
    public function isVerbose();

    /**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool
     */
    public function isVeryVerbose();

    /**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool
     */
    public function isDebug();

    /**
     * Sets the decorated flag.
     */
    public function setDecorated(bool $decorated);

    /**
     * Gets the decorated flag.
     *
     * @return bool
     */
    public function isDecorated();

    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     *
     * @return OutputFormatterInterface
     */
    public function getFormatter();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
 *
 * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *     $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private $stderr;
    private $consoleSectionOutputs = [];

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
    {
        parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

        if (null === $formatter) {
            // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
            $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);

            return;
        }

        $actualDecorated = $this->isDecorated();
        $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());

        if (null === $decorated) {
            $this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    /**
     * Creates a new output section.
     */
    public function section(): ConsoleSectionOutput
    {
        return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated)
    {
        parent::setDecorated($decorated);
        $this->stderr->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->stderr->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity(int $level)
    {
        parent::setVerbosity($level);
        $this->stderr->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getErrorOutput()
    {
        return $this->stderr;
    }

    /**
     * {@inheritdoc}
     */
    public function setErrorOutput(OutputInterface $error)
    {
        $this->stderr = $error;
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     *
     * @return bool
     */
    protected function hasStdoutSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDERR.
     *
     * @return bool
     */
    protected function hasStderrSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     */
    private function isRunningOS400(): bool
    {
        $checks = [
            \function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            \PHP_OS,
        ];

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        if (!$this->hasStdoutSupport()) {
            return fopen('php://output', 'w');
        }

        return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * A BufferedOutput that keeps only the last N chars.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
class TrimmedBufferOutput extends Output
{
    private $maxLength;
    private $buffer = '';

    public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
    {
        if ($maxLength <= 0) {
            throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
        }

        parent::__construct($verbosity, $decorated, $formatter);
        $this->maxLength = $maxLength;
    }

    /**
     * Empties buffer and returns its content.
     *
     * @return string
     */
    public function fetch()
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }

        $this->buffer = substr($this->buffer, 0 - $this->maxLength);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class ConsoleSectionOutput extends StreamOutput
{
    private $content = [];
    private $lines = 0;
    private $sections;
    private $terminal;

    /**
     * @param resource               $stream
     * @param ConsoleSectionOutput[] $sections
     */
    public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
    {
        parent::__construct($stream, $verbosity, $decorated, $formatter);
        array_unshift($sections, $this);
        $this->sections = &$sections;
        $this->terminal = new Terminal();
    }

    /**
     * Clears previous output for this section.
     *
     * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
     */
    public function clear(int $lines = null)
    {
        if (empty($this->content) || !$this->isDecorated()) {
            return;
        }

        if ($lines) {
            array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
        } else {
            $lines = $this->lines;
            $this->content = [];
        }

        $this->lines -= $lines;

        parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
    }

    /**
     * Overwrites the previous output with a new message.
     *
     * @param array|string $message
     */
    public function overwrite($message)
    {
        $this->clear();
        $this->writeln($message);
    }

    public function getContent(): string
    {
        return implode('', $this->content);
    }

    /**
     * @internal
     */
    public function addContent(string $input)
    {
        foreach (explode(\PHP_EOL, $input) as $lineContent) {
            $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
            $this->content[] = $lineContent;
            $this->content[] = \PHP_EOL;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite(string $message, bool $newline)
    {
        if (!$this->isDecorated()) {
            parent::doWrite($message, $newline);

            return;
        }

        $erasedContent = $this->popStreamContentUntilCurrentSection();

        $this->addContent($message);

        parent::doWrite($message, true);
        parent::doWrite($erasedContent, false);
    }

    /**
     * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
     * current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
     */
    private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
    {
        $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
        $erasedContent = [];

        foreach ($this->sections as $section) {
            if ($section === $this) {
                break;
            }

            $numberOfLinesToClear += $section->lines;
            $erasedContent[] = $section->getContent();
        }

        if ($numberOfLinesToClear > 0) {
            // move cursor up n lines
            parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
            // erase to end of screen
            parent::doWrite("\x1b[0J", false);
        }

        return implode('', array_reverse($erasedContent));
    }

    private function getDisplayLength(string $text): int
    {
        return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", '        ', $text)));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\NullOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    private $formatter;

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        if ($this->formatter) {
            return $this->formatter;
        }
        // to comply with the interface we must return a OutputFormatterInterface
        return $this->formatter = new NullOutputFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity(int $level)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return self::VERBOSITY_QUIET;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private $buffer = '';

    /**
     * Empties buffer and returns its content.
     *
     * @return string
     */
    public function fetch()
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private $verbosity;
    private $formatter;

    /**
     * @param int|null                      $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool                          $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL;
        $this->formatter = $formatter ?? new OutputFormatter();
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated)
    {
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->formatter->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity(int $level)
    {
        $this->verbosity = $level;
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return self::VERBOSITY_QUIET === $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, int $options = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
        $type = $types & $options ?: self::OUTPUT_NORMAL;

        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;

        if ($verbosity > $this->getVerbosity()) {
            return;
        }

        foreach ($messages as $message) {
            switch ($type) {
                case OutputInterface::OUTPUT_NORMAL:
                    $message = $this->formatter->format($message);
                    break;
                case OutputInterface::OUTPUT_RAW:
                    break;
                case OutputInterface::OUTPUT_PLAIN:
                    $message = strip_tags($this->formatter->format($message));
                    break;
            }

            $this->doWrite($message ?? '', $newline);
        }
    }

    /**
     * Writes a message to the output.
     */
    abstract protected function doWrite(string $message, bool $newline);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 *     $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    private $stream;

    /**
     * @param resource                      $stream    A stream resource
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @throws InvalidArgumentException When first argument is not a real stream
     */
    public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
    {
        if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        if (null === $decorated) {
            $decorated = $this->hasColorSupport();
        }

        parent::__construct($verbosity, $decorated, $formatter);
    }

    /**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite(string $message, bool $newline)
    {
        if ($newline) {
            $message .= \PHP_EOL;
        }

        @fwrite($this->stream, $message);

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * Reference: Composer\XdebugHandler\Process::supportsColor
     * https://github.com/composer/xdebug-handler
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport()
    {
        // Follow https://no-color.org/
        if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
            return false;
        }

        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }

        if (\DIRECTORY_SEPARATOR === '\\') {
            return (\function_exists('sapi_windows_vt100_support')
                && @sapi_windows_vt100_support($this->stream))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        return stream_isatty($this->stream);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr and section output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     *
     * @return OutputInterface
     */
    public function getErrorOutput();

    public function setErrorOutput(OutputInterface $error);

    public function section(): ConsoleSectionOutput;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private $output;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * {@inheritdoc}
     */
    public function newLine(int $count = 1)
    {
        $this->output->write(str_repeat(\PHP_EOL, $count));
    }

    /**
     * @return ProgressBar
     */
    public function createProgressBar(int $max = 0)
    {
        return new ProgressBar($this->output, $max);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->write($messages, $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->writeln($messages, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity(int $level)
    {
        $this->output->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->output->getVerbosity();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated(bool $decorated)
    {
        $this->output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->output->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->output->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->output->getFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return $this->output->isQuiet();
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return $this->output->isVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return $this->output->isVeryVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return $this->output->isDebug();
    }

    protected function getErrorOutput()
    {
        if (!$this->output instanceof ConsoleOutputInterface) {
            return $this->output;
        }

        return $this->output->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     */
    public function title(string $message);

    /**
     * Formats a section title.
     */
    public function section(string $message);

    /**
     * Formats a list.
     */
    public function listing(array $elements);

    /**
     * Formats informational text.
     *
     * @param string|array $message
     */
    public function text($message);

    /**
     * Formats a success result bar.
     *
     * @param string|array $message
     */
    public function success($message);

    /**
     * Formats an error result bar.
     *
     * @param string|array $message
     */
    public function error($message);

    /**
     * Formats an warning result bar.
     *
     * @param string|array $message
     */
    public function warning($message);

    /**
     * Formats a note admonition.
     *
     * @param string|array $message
     */
    public function note($message);

    /**
     * Formats a caution admonition.
     *
     * @param string|array $message
     */
    public function caution($message);

    /**
     * Formats a table.
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     *
     * @return mixed
     */
    public function ask(string $question, string $default = null, callable $validator = null);

    /**
     * Asks a question with the user input hidden.
     *
     * @return mixed
     */
    public function askHidden(string $question, callable $validator = null);

    /**
     * Asks for confirmation.
     *
     * @return bool
     */
    public function confirm(string $question, bool $default = true);

    /**
     * Asks a choice question.
     *
     * @param string|int|null $default
     *
     * @return mixed
     */
    public function choice(string $question, array $choices, $default = null);

    /**
     * Add newline(s).
     */
    public function newLine(int $count = 1);

    /**
     * Starts the progress output.
     */
    public function progressStart(int $max = 0);

    /**
     * Advances the progress output X steps.
     */
    public function progressAdvance(int $step = 1);

    /**
     * Finishes the progress output.
     */
    public function progressFinish();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    public const MAX_LINE_LENGTH = 120;

    private $input;
    private $output;
    private $questionHelper;
    private $progressBar;
    private $lineLength;
    private $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
        // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
        $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
        $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

        parent::__construct($this->output = $output);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     */
    public function block($messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
    {
        $messages = \is_array($messages) ? array_values($messages) : [$messages];

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function title(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function section(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function listing(array $elements)
    {
        $this->autoPrependText();
        $elements = array_map(function ($element) {
            return sprintf(' * %s', $element);
        }, $elements);

        $this->writeln($elements);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function text($message)
    {
        $this->autoPrependText();

        $messages = \is_array($message) ? array_values($message) : [$message];
        foreach ($messages as $message) {
            $this->writeln(sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    public function comment($message)
    {
        $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
    }

    /**
     * {@inheritdoc}
     */
    public function success($message)
    {
        $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message)
    {
        $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message)
    {
        $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function note($message)
    {
        $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
    }

    /**
     * Formats an info message.
     *
     * @param string|array $message
     */
    public function info($message)
    {
        $this->block($message, 'INFO', 'fg=green', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function caution($message)
    {
        $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function table(array $headers, array $rows)
    {
        $this->createTable()
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a horizontal table.
     */
    public function horizontalTable(array $headers, array $rows)
    {
        $this->createTable()
            ->setHorizontal(true)
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a list of key/value horizontally.
     *
     * Each row can be one of:
     * * 'A title'
     * * ['key' => 'value']
     * * new TableSeparator()
     *
     * @param string|array|TableSeparator ...$list
     */
    public function definitionList(...$list)
    {
        $headers = [];
        $row = [];
        foreach ($list as $value) {
            if ($value instanceof TableSeparator) {
                $headers[] = $value;
                $row[] = $value;
                continue;
            }
            if (\is_string($value)) {
                $headers[] = new TableCell($value, ['colspan' => 2]);
                $row[] = null;
                continue;
            }
            if (!\is_array($value)) {
                throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
            }
            $headers[] = key($value);
            $row[] = current($value);
        }

        $this->horizontalTable($headers, [$row]);
    }

    /**
     * {@inheritdoc}
     */
    public function ask(string $question, string $default = null, callable $validator = null)
    {
        $question = new Question($question, $default);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function askHidden(string $question, callable $validator = null)
    {
        $question = new Question($question);

        $question->setHidden(true);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function confirm(string $question, bool $default = true)
    {
        return $this->askQuestion(new ConfirmationQuestion($question, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function choice(string $question, array $choices, $default = null)
    {
        if (null !== $default) {
            $values = array_flip($choices);
            $default = $values[$default] ?? $default;
        }

        return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function progressStart(int $max = 0)
    {
        $this->progressBar = $this->createProgressBar($max);
        $this->progressBar->start();
    }

    /**
     * {@inheritdoc}
     */
    public function progressAdvance(int $step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * {@inheritdoc}
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
        $this->newLine(2);
        $this->progressBar = null;
    }

    /**
     * {@inheritdoc}
     */
    public function createProgressBar(int $max = 0)
    {
        $progressBar = parent::createProgressBar($max);

        if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
            $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('▓'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @see ProgressBar::iterate()
     */
    public function progressIterate(iterable $iterable, int $max = null): iterable
    {
        yield from $this->createProgressBar()->iterate($iterable, $max);

        $this->newLine(2);
    }

    /**
     * @return mixed
     */
    public function askQuestion(Question $question)
    {
        if ($this->input->isInteractive()) {
            $this->autoPrependBlock();
        }

        if (!$this->questionHelper) {
            $this->questionHelper = new SymfonyQuestionHelper();
        }

        $answer = $this->questionHelper->ask($this->input, $this, $question);

        if ($this->input->isInteractive()) {
            $this->newLine();
            $this->bufferedOutput->write("\n");
        }

        return $answer;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::writeln($message, $type);
            $this->writeBuffer($message, true, $type);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::write($message, $newline, $type);
            $this->writeBuffer($message, $newline, $type);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function newLine(int $count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * Returns a new instance which makes use of stderr if available.
     *
     * @return self
     */
    public function getErrorStyle()
    {
        return new self($this->input, $this->getErrorOutput());
    }

    public function createTable(): Table
    {
        $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output;
        $style = clone Table::getStyleDefinition('symfony-style-guide');
        $style->setCellHeaderFormat('<info>%s</info>');

        return (new Table($output))->setStyle($style);
    }

    private function getProgressBar(): ProgressBar
    {
        if (!$this->progressBar) {
            throw new RuntimeException('The ProgressBar is not started.');
        }

        return $this->progressBar;
    }

    private function autoPrependBlock(): void
    {
        $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            $this->newLine(); //empty history, so we should start with a new line.

            return;
        }
        //Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText(): void
    {
        $fetched = $this->bufferedOutput->fetch();
        //Prepend new line if last char isn't EOL:
        if (!str_ends_with($fetched, "\n")) {
            $this->newLine();
        }
    }

    private function writeBuffer(string $message, bool $newLine, int $type): void
    {
        // We need to know if the last chars are PHP_EOL
        $this->bufferedOutput->write($message, $newLine, $type);
    }

    private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
    {
        $indentLength = 0;
        $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix));
        $lines = [];

        if (null !== $type) {
            $type = sprintf('[%s] ', $type);
            $indentLength = \strlen($type);
            $lineIndentation = str_repeat(' ', $indentLength);
        }

        // wrap and add newlines for each element
        foreach ($messages as $key => $message) {
            if ($escape) {
                $message = OutputFormatter::escape($message);
            }

            $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message));
            $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength);
            $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true));
            foreach ($messageLines as $messageLine) {
                $lines[] = $messageLine;
            }

            if (\count($messages) > 1 && $key < \count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0));

            if ($style) {
                $line = sprintf('<%s>%s</>', $style, $line);
            }
        }

        return $lines;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see https://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    public const INFO = 'info';
    public const ERROR = 'error';

    private $output;
    private $verbosityLevelMap = [
        LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
        LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
        LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
    ];
    private $formatLevelMap = [
        LogLevel::EMERGENCY => self::ERROR,
        LogLevel::ALERT => self::ERROR,
        LogLevel::CRITICAL => self::ERROR,
        LogLevel::ERROR => self::ERROR,
        LogLevel::WARNING => self::INFO,
        LogLevel::NOTICE => self::INFO,
        LogLevel::INFO => self::INFO,
        LogLevel::DEBUG => self::INFO,
    ];
    private $errored = false;

    public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    /**
     * {@inheritdoc}
     *
     * @return void
     */
    public function log($level, $message, array $context = [])
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
        }

        $output = $this->output;

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level]) {
            if ($this->output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            $this->errored = true;
        }

        // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
        // We only do it for efficiency here as the message formatting is relatively expensive.
        if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
            $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
        }
    }

    /**
     * Returns true when any messages have been logged at error levels.
     *
     * @return bool
     */
    public function hasErrored()
    {
        return $this->errored;
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     */
    private function interpolate(string $message, array $context): string
    {
        if (!str_contains($message, '{')) {
            return $message;
        }

        $replacements = [];
        foreach ($context as $key => $val) {
            if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
                $replacements["{{$key}}"] = $val;
            } elseif ($val instanceof \DateTimeInterface) {
                $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
            } elseif (\is_object($val)) {
                $replacements["{{$key}}"] = '[object '.\get_class($val).']';
            } else {
                $replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return strtr($message, $replacements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handed to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    public const COMMAND = 'console.command';

    /**
     * The SIGNAL event allows you to perform some actions
     * after the command execution was interrupted.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
     */
    public const SIGNAL = 'console.signal';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    public const TERMINATE = 'console.terminate';

    /**
     * The ERROR event occurs when an uncaught exception or error appears.
     *
     * This event allows you to deal with the exception/error or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
     */
    public const ERROR = 'console.error';

    /**
     * Event aliases.
     *
     * These aliases can be consumed by RegisterListenersPass.
     */
    public const ALIASES = [
        ConsoleCommandEvent::class => self::COMMAND,
        ConsoleErrorEvent::class => self::ERROR,
        ConsoleSignalEvent::class => self::SIGNAL,
        ConsoleTerminateEvent::class => self::TERMINATE,
    ];
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SingleCommandApplication extends Command
{
    private $version = 'UNKNOWN';
    private $autoExit = true;
    private $running = false;

    /**
     * @return $this
     */
    public function setVersion(string $version): self
    {
        $this->version = $version;

        return $this;
    }

    /**
     * @final
     *
     * @return $this
     */
    public function setAutoExit(bool $autoExit): self
    {
        $this->autoExit = $autoExit;

        return $this;
    }

    public function run(InputInterface $input = null, OutputInterface $output = null): int
    {
        if ($this->running) {
            return parent::run($input, $output);
        }

        // We use the command name as the application name
        $application = new Application($this->getName() ?: 'UNKNOWN', $this->version);
        $application->setAutoExit($this->autoExit);
        // Fix the usage of the command displayed with "--help"
        $this->setName($_SERVER['argv'][0]);
        $application->add($this);
        $application->setDefaultCommand($this->getName(), true);

        $this->running = true;
        try {
            $ret = $application->run($input, $output);
        } finally {
            $this->running = false;
        }

        return $ret ?? 1;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Input\InputOption;

/**
 * Stores all completion suggestions for the current input.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionSuggestions
{
    private $valueSuggestions = [];
    private $optionSuggestions = [];

    /**
     * Add a suggested value for an input option or argument.
     *
     * @param string|Suggestion $value
     *
     * @return $this
     */
    public function suggestValue($value): self
    {
        $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;

        return $this;
    }

    /**
     * Add multiple suggested values at once for an input option or argument.
     *
     * @param list<string|Suggestion> $values
     *
     * @return $this
     */
    public function suggestValues(array $values): self
    {
        foreach ($values as $value) {
            $this->suggestValue($value);
        }

        return $this;
    }

    /**
     * Add a suggestion for an input option name.
     *
     * @return $this
     */
    public function suggestOption(InputOption $option): self
    {
        $this->optionSuggestions[] = $option;

        return $this;
    }

    /**
     * Add multiple suggestions for input option names at once.
     *
     * @param InputOption[] $options
     *
     * @return $this
     */
    public function suggestOptions(array $options): self
    {
        foreach ($options as $option) {
            $this->suggestOption($option);
        }

        return $this;
    }

    /**
     * @return InputOption[]
     */
    public function getOptionSuggestions(): array
    {
        return $this->optionSuggestions;
    }

    /**
     * @return Suggestion[]
     */
    public function getValueSuggestions(): array
    {
        return $this->valueSuggestions;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class BashCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = $suggestions->getValueSuggestions();
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName();
        }
        $output->writeln(implode("\n", $values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
interface CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * An input specialized for shell completion.
 *
 * This input allows unfinished option names or values and exposes what kind of
 * completion is expected.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionInput extends ArgvInput
{
    public const TYPE_ARGUMENT_VALUE = 'argument_value';
    public const TYPE_OPTION_VALUE = 'option_value';
    public const TYPE_OPTION_NAME = 'option_name';
    public const TYPE_NONE = 'none';

    private $tokens;
    private $currentIndex;
    private $completionType;
    private $completionName = null;
    private $completionValue = '';

    /**
     * Converts a terminal string into tokens.
     *
     * This is required for shell completions without COMP_WORDS support.
     */
    public static function fromString(string $inputStr, int $currentIndex): self
    {
        preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);

        return self::fromTokens($tokens[0], $currentIndex);
    }

    /**
     * Create an input based on an COMP_WORDS token list.
     *
     * @param string[] $tokens       the set of split tokens (e.g. COMP_WORDS or argv)
     * @param          $currentIndex the index of the cursor (e.g. COMP_CWORD)
     */
    public static function fromTokens(array $tokens, int $currentIndex): self
    {
        $input = new self($tokens);
        $input->tokens = $tokens;
        $input->currentIndex = $currentIndex;

        return $input;
    }

    /**
     * {@inheritdoc}
     */
    public function bind(InputDefinition $definition): void
    {
        parent::bind($definition);

        $relevantToken = $this->getRelevantToken();
        if ('-' === $relevantToken[0]) {
            // the current token is an input option: complete either option name or option value
            [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];

            $option = $this->getOptionFromToken($optionToken);
            if (null === $option && !$this->isCursorFree()) {
                $this->completionType = self::TYPE_OPTION_NAME;
                $this->completionValue = $relevantToken;

                return;
            }

            if (null !== $option && $option->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $option->getName();
                $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');

                return;
            }
        }

        $previousToken = $this->tokens[$this->currentIndex - 1];
        if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
            // check if previous option accepted a value
            $previousOption = $this->getOptionFromToken($previousToken);
            if (null !== $previousOption && $previousOption->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $previousOption->getName();
                $this->completionValue = $relevantToken;

                return;
            }
        }

        // complete argument value
        $this->completionType = self::TYPE_ARGUMENT_VALUE;

        $arguments = $this->getArguments();
        foreach ($arguments as $argumentName => $argumentValue) {
            if (null === $argumentValue) {
                break;
            }

            $this->completionName = $argumentName;
            if (\is_array($argumentValue)) {
                $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
            } else {
                $this->completionValue = $argumentValue;
            }
        }

        if ($this->currentIndex >= \count($this->tokens)) {
            if (null === $arguments[$argumentName] || $this->definition->getArgument($argumentName)->isArray()) {
                $this->completionName = $argumentName;
                $this->completionValue = '';
            } else {
                // we've reached the end
                $this->completionType = self::TYPE_NONE;
                $this->completionName = null;
                $this->completionValue = '';
            }
        }
    }

    /**
     * Returns the type of completion required.
     *
     * TYPE_ARGUMENT_VALUE when completing the value of an input argument
     * TYPE_OPTION_VALUE   when completing the value of an input option
     * TYPE_OPTION_NAME    when completing the name of an input option
     * TYPE_NONE           when nothing should be completed
     *
     * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component
     */
    public function getCompletionType(): string
    {
        return $this->completionType;
    }

    /**
     * The name of the input option or argument when completing a value.
     *
     * @return string|null returns null when completing an option name
     */
    public function getCompletionName(): ?string
    {
        return $this->completionName;
    }

    /**
     * The value already typed by the user (or empty string).
     */
    public function getCompletionValue(): string
    {
        return $this->completionValue;
    }

    public function mustSuggestOptionValuesFor(string $optionName): bool
    {
        return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
    }

    public function mustSuggestArgumentValuesFor(string $argumentName): bool
    {
        return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        try {
            return parent::parseToken($token, $parseOptions);
        } catch (RuntimeException $e) {
            // suppress errors, completed input is almost never valid
        }

        return $parseOptions;
    }

    private function getOptionFromToken(string $optionToken): ?InputOption
    {
        $optionName = ltrim($optionToken, '-');
        if (!$optionName) {
            return null;
        }

        if ('-' === ($optionToken[1] ?? ' ')) {
            // long option name
            return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
        }

        // short option name
        return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
    }

    /**
     * The token of the cursor, or the last token if the cursor is at the end of the input.
     */
    private function getRelevantToken(): string
    {
        return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
    }

    /**
     * Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
     */
    private function isCursorFree(): bool
    {
        $nrOfTokens = \count($this->tokens);
        if ($this->currentIndex > $nrOfTokens) {
            throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
        }

        return $this->currentIndex >= $nrOfTokens;
    }

    public function __toString()
    {
        $str = '';
        foreach ($this->tokens as $i => $token) {
            $str .= $token;

            if ($this->currentIndex === $i) {
                $str .= '|';
            }

            $str .= ' ';
        }

        if ($this->currentIndex > $i) {
            $str .= '|';
        }

        return rtrim($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

/**
 * Represents a single suggested value.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class Suggestion
{
    private $value;

    public function __construct(string $value)
    {
        $this->value = $value;
    }

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

    public function __toString(): string
    {
        return $this->getValue();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     */
    public function setHelperSet(HelperSet $helperSet = null);

    /**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet|null
     */
    public function getHelperSet();

    /**
     * Returns the canonical name of this helper.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     *
     * @return string
     */
    public function formatSection(string $section, string $message, string $style = 'info')
    {
        return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     *
     * @return string
     */
    public function formatBlock($messages, string $style, bool $large = false)
    {
        if (!\is_array($messages)) {
            $messages = [$messages];
        }

        $len = 0;
        $lines = [];
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max(self::width($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? [str_repeat(' ', $len)] : [];
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i]));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        for ($i = 0; isset($messages[$i]); ++$i) {
            $messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
        }

        return implode("\n", $messages);
    }

    /**
     * Truncates a message to the given length.
     *
     * @return string
     */
    public function truncate(string $message, int $length, string $suffix = '...')
    {
        $computedLength = $length - self::width($suffix);

        if ($computedLength > self::width($message)) {
            return $message;
        }

        return self::substr($message, 0, $length).$suffix;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * {@inheritdoc}
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        $default = $question->getDefault();

        if ($question->isMultiline()) {
            $text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
        }

        switch (true) {
            case null === $default:
                $text = sprintf(' <info>%s</info>:', $text);

                break;

            case $question instanceof ConfirmationQuestion:
                $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');

                break;

            case $question instanceof ChoiceQuestion && $question->isMultiselect():
                $choices = $question->getChoices();
                $default = explode(',', $default);

                foreach ($default as $key => $value) {
                    $default[$key] = $choices[trim($value)];
                }

                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));

                break;

            default:
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        $prompt = ' > ';

        if ($question instanceof ChoiceQuestion) {
            $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));

            $prompt = $question->getPrompt();
        }

        $output->write($prompt);
    }

    /**
     * {@inheritdoc}
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if ($output instanceof SymfonyStyle) {
            $output->newLine();
            $output->error($error->getMessage());

            return;
        }

        parent::writeError($output, $error);
    }

    private function getEofShortcut(): string
    {
        if ('Windows' === \PHP_OS_FAMILY) {
            return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
        }

        return '<comment>Ctrl+D</comment>';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private $descriptors = [];

    public function __construct()
    {
        $this
            ->register('txt', new TextDescriptor())
            ->register('xml', new XmlDescriptor())
            ->register('json', new JsonDescriptor())
            ->register('md', new MarkdownDescriptor())
        ;
    }

    /**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, ?object $object, array $options = [])
    {
        $options = array_merge([
            'raw_text' => false,
            'format' => 'txt',
        ], $options);

        if (!isset($this->descriptors[$options['format']])) {
            throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];
        $descriptor->describe($output, $object, $options);
    }

    /**
     * Registers a descriptor.
     *
     * @return $this
     */
    public function register(string $format, DescriptorInterface $descriptor)
    {
        $this->descriptors[$format] = $descriptor;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'descriptor';
    }

    public function getFormats(): array
    {
        return array_keys($this->descriptors);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param array|Process $cmd      An instance of Process or an array of the command and arguments
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return Process
     */
    public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
    {
        if (!class_exists(Process::class)) {
            throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
        }

        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if ($cmd instanceof Process) {
            $cmd = [$cmd];
        }

        if (!\is_array($cmd)) {
            throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
        }

        if (\is_string($cmd[0] ?? null)) {
            $process = new Process($cmd);
            $cmd = [];
        } elseif (($cmd[0] ?? null) instanceof Process) {
            $process = $cmd[0];
            unset($cmd[0]);
        } else {
            throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
        }

        if ($verbosity <= $output->getVerbosity()) {
            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
        }

        if ($output->isDebug()) {
            $callback = $this->wrapCallback($output, $process, $callback);
        }

        $process->run($callback, $cmd);

        if ($verbosity <= $output->getVerbosity()) {
            $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
        }

        if (!$process->isSuccessful() && null !== $error) {
            $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
        }

        return $process;
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @param array|Process $cmd      An instance of Process or a command to run
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return Process
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
    {
        $process = $this->run($output, $cmd, $error, $callback);

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return $process;
    }

    /**
     * Wraps a Process callback to add debugging output.
     */
    public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                $callback($type, $buffer);
            }
        };
    }

    private function escapeString(string $str): string
    {
        return str_replace('<', '\\<', $str);
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return 'process';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

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

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class TableStyle
{
    private $paddingChar = ' ';
    private $horizontalOutsideBorderChar = '-';
    private $horizontalInsideBorderChar = '-';
    private $verticalOutsideBorderChar = '|';
    private $verticalInsideBorderChar = '|';
    private $crossingChar = '+';
    private $crossingTopRightChar = '+';
    private $crossingTopMidChar = '+';
    private $crossingTopLeftChar = '+';
    private $crossingMidRightChar = '+';
    private $crossingBottomRightChar = '+';
    private $crossingBottomMidChar = '+';
    private $crossingBottomLeftChar = '+';
    private $crossingMidLeftChar = '+';
    private $crossingTopLeftBottomChar = '+';
    private $crossingTopMidBottomChar = '+';
    private $crossingTopRightBottomChar = '+';
    private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private $cellHeaderFormat = '<info>%s</info>';
    private $cellRowFormat = '%s';
    private $cellRowContentFormat = ' %s ';
    private $borderFormat = '%s';
    private $padType = \STR_PAD_RIGHT;

    /**
     * Sets padding character, used for cell padding.
     *
     * @return $this
     */
    public function setPaddingChar(string $paddingChar)
    {
        if (!$paddingChar) {
            throw new LogicException('The padding char must not be empty.');
        }

        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Gets padding character, used for cell padding.
     *
     * @return string
     */
    public function getPaddingChar()
    {
        return $this->paddingChar;
    }

    /**
     * Sets horizontal border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * 1 ISBN          2 Title                    │ Author           ║
     * ╠═══════════════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setHorizontalBorderChars(string $outside, string $inside = null): self
    {
        $this->horizontalOutsideBorderChar = $outside;
        $this->horizontalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Sets vertical border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * ║ ISBN          │ Title                    │ Author           ║
     * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ╟───────2───────┼──────────────────────────┼──────────────────╢
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setVerticalBorderChars(string $outside, string $inside = null): self
    {
        $this->verticalOutsideBorderChar = $outside;
        $this->verticalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Gets border characters.
     *
     * @internal
     */
    public function getBorderChars(): array
    {
        return [
            $this->horizontalOutsideBorderChar,
            $this->verticalOutsideBorderChar,
            $this->horizontalInsideBorderChar,
            $this->verticalInsideBorderChar,
        ];
    }

    /**
     * Sets crossing characters.
     *
     * Example:
     * <code>
     * 1═══════════════2══════════════════════════2══════════════════3
     * ║ ISBN          │ Title                    │ Author           ║
     * 8'══════════════0'═════════════════════════0'═════════════════4'
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * 8───────────────0──────────────────────────0──────────────────4
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * 7═══════════════6══════════════════════════6══════════════════5
     * </code>
     *
     * @param string      $cross          Crossing char (see #0 of example)
     * @param string      $topLeft        Top left char (see #1 of example)
     * @param string      $topMid         Top mid char (see #2 of example)
     * @param string      $topRight       Top right char (see #3 of example)
     * @param string      $midRight       Mid right char (see #4 of example)
     * @param string      $bottomRight    Bottom right char (see #5 of example)
     * @param string      $bottomMid      Bottom mid char (see #6 of example)
     * @param string      $bottomLeft     Bottom left char (see #7 of example)
     * @param string      $midLeft        Mid left char (see #8 of example)
     * @param string|null $topLeftBottom  Top left bottom char (see #8' of example), equals to $midLeft if null
     * @param string|null $topMidBottom   Top mid bottom char (see #0' of example), equals to $cross if null
     * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
     *
     * @return $this
     */
    public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
    {
        $this->crossingChar = $cross;
        $this->crossingTopLeftChar = $topLeft;
        $this->crossingTopMidChar = $topMid;
        $this->crossingTopRightChar = $topRight;
        $this->crossingMidRightChar = $midRight;
        $this->crossingBottomRightChar = $bottomRight;
        $this->crossingBottomMidChar = $bottomMid;
        $this->crossingBottomLeftChar = $bottomLeft;
        $this->crossingMidLeftChar = $midLeft;
        $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
        $this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
        $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;

        return $this;
    }

    /**
     * Sets default crossing character used for each cross.
     *
     * @see {@link setCrossingChars()} for setting each crossing individually.
     */
    public function setDefaultCrossingChar(string $char): self
    {
        return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
    }

    /**
     * Gets crossing character.
     *
     * @return string
     */
    public function getCrossingChar()
    {
        return $this->crossingChar;
    }

    /**
     * Gets crossing characters.
     *
     * @internal
     */
    public function getCrossingChars(): array
    {
        return [
            $this->crossingChar,
            $this->crossingTopLeftChar,
            $this->crossingTopMidChar,
            $this->crossingTopRightChar,
            $this->crossingMidRightChar,
            $this->crossingBottomRightChar,
            $this->crossingBottomMidChar,
            $this->crossingBottomLeftChar,
            $this->crossingMidLeftChar,
            $this->crossingTopLeftBottomChar,
            $this->crossingTopMidBottomChar,
            $this->crossingTopRightBottomChar,
        ];
    }

    /**
     * Sets header cell format.
     *
     * @return $this
     */
    public function setCellHeaderFormat(string $cellHeaderFormat)
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Gets header cell format.
     *
     * @return string
     */
    public function getCellHeaderFormat()
    {
        return $this->cellHeaderFormat;
    }

    /**
     * Sets row cell format.
     *
     * @return $this
     */
    public function setCellRowFormat(string $cellRowFormat)
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Gets row cell format.
     *
     * @return string
     */
    public function getCellRowFormat()
    {
        return $this->cellRowFormat;
    }

    /**
     * Sets row cell content format.
     *
     * @return $this
     */
    public function setCellRowContentFormat(string $cellRowContentFormat)
    {
        $this->cellRowContentFormat = $cellRowContentFormat;

        return $this;
    }

    /**
     * Gets row cell content format.
     *
     * @return string
     */
    public function getCellRowContentFormat()
    {
        return $this->cellRowContentFormat;
    }

    /**
     * Sets table border format.
     *
     * @return $this
     */
    public function setBorderFormat(string $borderFormat)
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Gets table border format.
     *
     * @return string
     */
    public function getBorderFormat()
    {
        return $this->borderFormat;
    }

    /**
     * Sets cell padding type.
     *
     * @return $this
     */
    public function setPadType(int $padType)
    {
        if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
            throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
        }

        $this->padType = $padType;

        return $this;
    }

    /**
     * Gets cell padding type.
     *
     * @return int
     */
    public function getPadType()
    {
        return $this->padType;
    }

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

    /**
     * @return $this
     */
    public function setHeaderTitleFormat(string $format): self
    {
        $this->headerTitleFormat = $format;

        return $this;
    }

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

    /**
     * @return $this
     */
    public function setFooterTitleFormat(string $format): self
    {
        $this->footerTitleFormat = $format;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private $value;
    private $options = [
        'rowspan' => 1,
        'colspan' => 1,
        'style' => null,
    ];

    public function __construct(string $value = '', array $options = [])
    {
        $this->value = $value;

        // check option names
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
            throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
        }

        $this->options = array_merge($this->options, $options);
    }

    /**
     * Returns the cell value.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Gets number of colspan.
     *
     * @return int
     */
    public function getColspan()
    {
        return (int) $this->options['colspan'];
    }

    /**
     * Gets number of rowspan.
     *
     * @return int
     */
    public function getRowspan()
    {
        return (int) $this->options['rowspan'];
    }

    public function getStyle(): ?TableCellStyle
    {
        return $this->options['style'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * @internal
 */
class TableRows implements \IteratorAggregate
{
    private $generator;

    public function __construct(\Closure $generator)
    {
        $this->generator = $generator;
    }

    public function getIterator(): \Traversable
    {
        return ($this->generator)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private const FORMATS = [
        'normal' => ' %indicator% %message%',
        'normal_no_ansi' => ' %message%',

        'verbose' => ' %indicator% %message% (%elapsed:6s%)',
        'verbose_no_ansi' => ' %message% (%elapsed:6s%)',

        'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
        'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
    ];

    private $output;
    private $startTime;
    private $format;
    private $message;
    private $indicatorValues;
    private $indicatorCurrent;
    private $indicatorChangeInterval;
    private $indicatorUpdateTime;
    private $started = false;

    /**
     * @var array<string, callable>
     */
    private static $formatters;

    /**
     * @param int        $indicatorChangeInterval Change interval in milliseconds
     * @param array|null $indicatorValues         Animated indicator characters
     */
    public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
    {
        $this->output = $output;

        if (null === $format) {
            $format = $this->determineBestFormat();
        }

        if (null === $indicatorValues) {
            $indicatorValues = ['-', '\\', '|', '/'];
        }

        $indicatorValues = array_values($indicatorValues);

        if (2 > \count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     */
    public function setMessage(?string $message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Starts the indicator output.
     */
    public function start(string $message)
    {
        if ($this->started) {
            throw new LogicException('Progress indicator already started.');
        }

        $this->message = $message;
        $this->started = true;
        $this->startTime = time();
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
        $this->indicatorCurrent = 0;

        $this->display();
    }

    /**
     * Advances the indicator.
     */
    public function advance()
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        if (!$this->output->isDecorated()) {
            return;
        }

        $currentTime = $this->getCurrentTimeInMilliseconds();

        if ($currentTime < $this->indicatorUpdateTime) {
            return;
        }

        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
        ++$this->indicatorCurrent;

        $this->display();
    }

    /**
     * Finish the indicator with message.
     *
     * @param $message
     */
    public function finish(string $message)
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        $this->message = $message;
        $this->display();
        $this->output->writeln('');
        $this->started = false;
    }

    /**
     * Gets the format for a given name.
     *
     * @return string|null
     */
    public static function getFormatDefinition(string $name)
    {
        return self::FORMATS[$name] ?? null;
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name (including the delimiter char like %).
     *
     * @return callable|null
     */
    public static function getPlaceholderFormatterDefinition(string $name)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return self::$formatters[$name] ?? null;
    }

    private function display()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
                return $formatter($this);
            }

            return $matches[0];
        }, $this->format ?? ''));
    }

    private function determineBestFormat(): string
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
            default:
                return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
        }
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message)
    {
        if ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds(): float
    {
        return round(microtime(true) * 1000);
    }

    private static function initPlaceholderFormatters(): array
    {
        return [
            'indicator' => function (self $indicator) {
                return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
            },
            'message' => function (self $indicator) {
                return $indicator->message;
            },
            'elapsed' => function (self $indicator) {
                return Helper::formatTime(time() - $indicator->startTime);
            },
            'memory' => function () {
                return Helper::formatMemory(memory_get_usage(true));
            },
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
use function Symfony\Component\String\s;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    /**
     * @var resource|null
     */
    private $inputStream;

    private static $stty = true;
    private static $stdinIsInteractive;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            return $this->getDefaultAnswer($question);
        }

        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
            $this->inputStream = $stream;
        }

        try {
            if (!$question->getValidator()) {
                return $this->doAsk($output, $question);
            }

            $interviewer = function () use ($output, $question) {
                return $this->doAsk($output, $question);
            };

            return $this->validateAttempts($interviewer, $output, $question);
        } catch (MissingInputException $exception) {
            $input->setInteractive(false);

            if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
                throw $exception;
            }

            return $fallbackOutput;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'question';
    }

    /**
     * Prevents usage of stty.
     */
    public static function disableStty()
    {
        self::$stty = false;
    }

    /**
     * Asks the question to the user.
     *
     * @return mixed
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function doAsk(OutputInterface $output, Question $question)
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: \STDIN;
        $autocomplete = $question->getAutocompleterCallback();

        if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
                    $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $ret = $this->readInput($inputStream, $question);
                if (false === $ret) {
                    throw new MissingInputException('Aborted.');
                }
                if ($question->isTrimmable()) {
                    $ret = trim($ret);
                }
            }
        } else {
            $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
            $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
        }

        if ($output instanceof ConsoleSectionOutput) {
            $output->addContent($ret);
        }

        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    /**
     * @return mixed
     */
    private function getDefaultAnswer(Question $question)
    {
        $default = $question->getDefault();

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

        if ($validator = $question->getValidator()) {
            return \call_user_func($question->getValidator(), $default);
        } elseif ($question instanceof ChoiceQuestion) {
            $choices = $question->getChoices();

            if (!$question->isMultiselect()) {
                return $choices[$default] ?? $default;
            }

            $default = explode(',', $default);
            foreach ($default as $k => $v) {
                $v = $question->isTrimmable() ? trim($v) : $v;
                $default[$k] = $choices[$v] ?? $v;
            }
        }

        return $default;
    }

    /**
     * Outputs the question prompt.
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $output->writeln(array_merge([
                $question->getQuestion(),
            ], $this->formatChoiceQuestionChoices($question, 'info')));

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * @return string[]
     */
    protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
    {
        $messages = [];

        $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));

        foreach ($choices as $key => $value) {
            $padding = str_repeat(' ', $maxWidth - self::width($key));

            $messages[] = sprintf("  [<$tag>%s$padding</$tag>] %s", $key, $value);
        }

        return $messages;
    }

    /**
     * Outputs an error message.
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
        } else {
            $message = '<error>'.$error->getMessage().'</error>';
        }

        $output->writeln($message);
    }

    /**
     * Autocompletes a question.
     *
     * @param resource $inputStream
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
    {
        $cursor = new Cursor($output, $inputStream);

        $fullChoice = '';
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete($ret);
        $numMatches = \count($matches);

        $sttyMode = shell_exec('stty -g');

        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
        shell_exec('stty -icanon -echo');

        // Add highlighted text style
        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));

        // Read a keypress
        while (!feof($inputStream)) {
            $c = fread($inputStream, 1);

            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
            if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
                shell_exec(sprintf('stty %s', $sttyMode));
                throw new MissingInputException('Aborted.');
            } elseif ("\177" === $c) { // Backspace Character
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));

                    $fullChoice = self::substr($fullChoice, 0, $i);
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete($ret);
                    $numMatches = \count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = self::substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif (\ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = (string) $matches[$ofs];
                        // Echo out remaining chars for current match
                        $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
                        $output->write($remainingCharacters);
                        $fullChoice .= $remainingCharacters;
                        $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);

                        $matches = array_filter(
                            $autocomplete($ret),
                            function ($match) use ($ret) {
                                return '' === $ret || str_starts_with($match, $ret);
                            }
                        );
                        $numMatches = \count($matches);
                        $ofs = -1;
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                if ("\x80" <= $c) {
                    $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
                }

                $output->write($c);
                $ret .= $c;
                $fullChoice .= $c;
                ++$i;

                $tempRet = $ret;

                if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
                    $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
                }

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete($ret) as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (str_starts_with($value, $tempRet)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            $cursor->clearLineAfter();

            if ($numMatches > 0 && -1 !== $ofs) {
                $cursor->savePosition();
                // Write highlighted text, complete the partially entered response
                $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
                $cursor->restorePosition();
            }
        }

        // Reset stty so it behaves normally again
        shell_exec(sprintf('stty %s', $sttyMode));

        return $fullChoice;
    }

    private function mostRecentlyEnteredValue(string $entered): string
    {
        // Determine the most recent value that the user entered
        if (!str_contains($entered, ',')) {
            return $entered;
        }

        $choices = explode(',', $entered);
        if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
            return $lastChoice;
        }

        return $entered;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param resource $inputStream The handler resource
     * @param bool     $trimmable   Is the answer trimmable
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if ('phar:' === substr(__FILE__, 0, 5)) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $sExec = shell_exec('"'.$exe.'"');
            $value = $trimmable ? rtrim($sExec) : $sExec;
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if (self::$stty && Terminal::hasSttyAvailable()) {
            $sttyMode = shell_exec('stty -g');
            shell_exec('stty -echo');
        } elseif ($this->isInteractiveInput($inputStream)) {
            throw new RuntimeException('Unable to hide the response.');
        }

        $value = fgets($inputStream, 4096);

        if (self::$stty && Terminal::hasSttyAvailable()) {
            shell_exec(sprintf('stty %s', $sttyMode));
        }

        if (false === $value) {
            throw new MissingInputException('Aborted.');
        }
        if ($trimmable) {
            $value = trim($value);
        }
        $output->writeln('');

        return $value;
    }

    /**
     * Validates an attempt.
     *
     * @param callable $interviewer A callable that will ask for a question and return the result
     *
     * @return mixed The validated response
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
    {
        $error = null;
        $attempts = $question->getMaxAttempts();

        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return $question->getValidator()($interviewer());
            } catch (RuntimeException $e) {
                throw $e;
            } catch (\Exception $error) {
            }
        }

        throw $error;
    }

    private function isInteractiveInput($inputStream): bool
    {
        if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
            return false;
        }

        if (null !== self::$stdinIsInteractive) {
            return self::$stdinIsInteractive;
        }

        if (\function_exists('stream_isatty')) {
            return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
        }

        if (\function_exists('posix_isatty')) {
            return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
        }

        if (!\function_exists('exec')) {
            return self::$stdinIsInteractive = true;
        }

        exec('stty 2> /dev/null', $output, $status);

        return self::$stdinIsInteractive = 1 !== $status;
    }

    /**
     * Reads one or more lines of input and returns what is read.
     *
     * @param resource $inputStream The handler resource
     * @param Question $question    The question being asked
     *
     * @return string|false The input received, false in case input could not be read
     */
    private function readInput($inputStream, Question $question)
    {
        if (!$question->isMultiline()) {
            $cp = $this->setIOCodepage();
            $ret = fgets($inputStream, 4096);

            return $this->resetIOCodepage($cp, $ret);
        }

        $multiLineStreamReader = $this->cloneInputStream($inputStream);
        if (null === $multiLineStreamReader) {
            return false;
        }

        $ret = '';
        $cp = $this->setIOCodepage();
        while (false !== ($char = fgetc($multiLineStreamReader))) {
            if (\PHP_EOL === "{$ret}{$char}") {
                break;
            }
            $ret .= $char;
        }

        return $this->resetIOCodepage($cp, $ret);
    }

    /**
     * Sets console I/O to the host code page.
     *
     * @return int Previous code page in IBM/EBCDIC format
     */
    private function setIOCodepage(): int
    {
        if (\function_exists('sapi_windows_cp_set')) {
            $cp = sapi_windows_cp_get();
            sapi_windows_cp_set(sapi_windows_cp_get('oem'));

            return $cp;
        }

        return 0;
    }

    /**
     * Sets console I/O to the specified code page and converts the user input.
     *
     * @param string|false $input
     *
     * @return string|false
     */
    private function resetIOCodepage(int $cp, $input)
    {
        if (0 !== $cp) {
            sapi_windows_cp_set($cp);

            if (false !== $input && '' !== $input) {
                $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
            }
        }

        return $input;
    }

    /**
     * Clones an input stream in order to act on one instance of the same
     * stream without affecting the other instance.
     *
     * @param resource $inputStream The handler resource
     *
     * @return resource|null The cloned resource, null in case it could not be cloned
     */
    private function cloneInputStream($inputStream)
    {
        $streamMetaData = stream_get_meta_data($inputStream);
        $seekable = $streamMetaData['seekable'] ?? false;
        $mode = $streamMetaData['mode'] ?? 'rb';
        $uri = $streamMetaData['uri'] ?? null;

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

        $cloneStream = fopen($uri, $mode);

        // For seekable and writable streams, add all the same data to the
        // cloned stream and then seek to the same offset.
        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
            $offset = ftell($inputStream);
            rewind($inputStream);
            stream_copy_to_stream($inputStream, $cloneStream);
            fseek($inputStream, $offset);
            fseek($cloneStream, $offset);
        }

        return $cloneStream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

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

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, Helper>
 */
class HelperSet implements \IteratorAggregate
{
    /** @var array<string, Helper> */
    private $helpers = [];
    private $command;

    /**
     * @param Helper[] $helpers An array of helper
     */
    public function __construct(array $helpers = [])
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, \is_int($alias) ? null : $alias);
        }
    }

    public function set(HelperInterface $helper, string $alias = null)
    {
        $this->helpers[$helper->getName()] = $helper;
        if (null !== $alias) {
            $this->helpers[$alias] = $helper;
        }

        $helper->setHelperSet($this);
    }

    /**
     * Returns true if the helper if defined.
     *
     * @return bool
     */
    public function has(string $name)
    {
        return isset($this->helpers[$name]);
    }

    /**
     * Gets a helper value.
     *
     * @return HelperInterface
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function get(string $name)
    {
        if (!$this->has($name)) {
            throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
        }

        return $this->helpers[$name];
    }

    /**
     * @deprecated since Symfony 5.4
     */
    public function setCommand(Command $command = null)
    {
        trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__);

        $this->command = $command;
    }

    /**
     * Gets the command associated with this helper set.
     *
     * @return Command
     *
     * @deprecated since Symfony 5.4
     */
    public function getCommand()
    {
        trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__);

        return $this->command;
    }

    /**
     * @return \Traversable<string, Helper>
     */
    #[\ReturnTypeWillChange]
    public function getIterator()
    {
        return new \ArrayIterator($this->helpers);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
final class Dumper
{
    private $output;
    private $dumper;
    private $cloner;
    private $handler;

    public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
    {
        $this->output = $output;
        $this->dumper = $dumper;
        $this->cloner = $cloner;

        if (class_exists(CliDumper::class)) {
            $this->handler = function ($var): string {
                $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
                $dumper->setColors($this->output->isDecorated());

                return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
            };
        } else {
            $this->handler = function ($var): string {
                switch (true) {
                    case null === $var:
                        return 'null';
                    case true === $var:
                        return 'true';
                    case false === $var:
                        return 'false';
                    case \is_string($var):
                        return '"'.$var.'"';
                    default:
                        return rtrim(print_r($var, true));
                }
            };
        }
    }

    public function __invoke($var): string
    {
        return ($this->handler)($var);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class Table
{
    private const SEPARATOR_TOP = 0;
    private const SEPARATOR_TOP_BOTTOM = 1;
    private const SEPARATOR_MID = 2;
    private const SEPARATOR_BOTTOM = 3;
    private const BORDER_OUTSIDE = 0;
    private const BORDER_INSIDE = 1;

    private $headerTitle;
    private $footerTitle;

    /**
     * Table headers.
     */
    private $headers = [];

    /**
     * Table rows.
     */
    private $rows = [];
    private $horizontal = false;

    /**
     * Column widths cache.
     */
    private $effectiveColumnWidths = [];

    /**
     * Number of columns cache.
     *
     * @var int
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var TableStyle
     */
    private $style;

    /**
     * @var array
     */
    private $columnStyles = [];

    /**
     * User set column widths.
     *
     * @var array
     */
    private $columnWidths = [];
    private $columnMaxWidths = [];

    /**
     * @var array<string, TableStyle>|null
     */
    private static $styles;

    private $rendered = false;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;

        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        $this->setStyle('default');
    }

    /**
     * Sets a style definition.
     */
    public static function setStyleDefinition(string $name, TableStyle $style)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        self::$styles[$name] = $style;
    }

    /**
     * Gets a style definition by name.
     *
     * @return TableStyle
     */
    public static function getStyleDefinition(string $name)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }

    /**
     * Sets table style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setStyle($name)
    {
        $this->style = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current table style.
     *
     * @return TableStyle
     */
    public function getStyle()
    {
        return $this->style;
    }

    /**
     * Sets table column style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setColumnStyle(int $columnIndex, $name)
    {
        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current style for a column.
     *
     * If style was not set, it returns the global table style.
     *
     * @return TableStyle
     */
    public function getColumnStyle(int $columnIndex)
    {
        return $this->columnStyles[$columnIndex] ?? $this->getStyle();
    }

    /**
     * Sets the minimum width of a column.
     *
     * @return $this
     */
    public function setColumnWidth(int $columnIndex, int $width)
    {
        $this->columnWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * Sets the minimum width of all columns.
     *
     * @return $this
     */
    public function setColumnWidths(array $widths)
    {
        $this->columnWidths = [];
        foreach ($widths as $index => $width) {
            $this->setColumnWidth($index, $width);
        }

        return $this;
    }

    /**
     * Sets the maximum width of a column.
     *
     * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
     * formatted strings are preserved.
     *
     * @return $this
     */
    public function setColumnMaxWidth(int $columnIndex, int $width): self
    {
        if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
            throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
        }

        $this->columnMaxWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaders(array $headers)
    {
        $headers = array_values($headers);
        if (!empty($headers) && !\is_array($headers[0])) {
            $headers = [$headers];
        }

        $this->headers = $headers;

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->rows = [];

        return $this->addRows($rows);
    }

    /**
     * @return $this
     */
    public function addRows(array $rows)
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function addRow($row)
    {
        if ($row instanceof TableSeparator) {
            $this->rows[] = $row;

            return $this;
        }

        if (!\is_array($row)) {
            throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
        }

        $this->rows[] = array_values($row);

        return $this;
    }

    /**
     * Adds a row to the table, and re-renders the table.
     *
     * @return $this
     */
    public function appendRow($row): self
    {
        if (!$this->output instanceof ConsoleSectionOutput) {
            throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
        }

        if ($this->rendered) {
            $this->output->clear($this->calculateRowCount());
        }

        $this->addRow($row);
        $this->render();

        return $this;
    }

    /**
     * @return $this
     */
    public function setRow($column, array $row)
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaderTitle(?string $title): self
    {
        $this->headerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setFooterTitle(?string $title): self
    {
        $this->footerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHorizontal(bool $horizontal = true): self
    {
        $this->horizontal = $horizontal;

        return $this;
    }

    /**
     * Renders table to output.
     *
     * Example:
     *
     *     +---------------+-----------------------+------------------+
     *     | ISBN          | Title                 | Author           |
     *     +---------------+-----------------------+------------------+
     *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
     *     +---------------+-----------------------+------------------+
     */
    public function render()
    {
        $divider = new TableSeparator();
        if ($this->horizontal) {
            $rows = [];
            foreach ($this->headers[0] ?? [] as $i => $header) {
                $rows[$i] = [$header];
                foreach ($this->rows as $row) {
                    if ($row instanceof TableSeparator) {
                        continue;
                    }
                    if (isset($row[$i])) {
                        $rows[$i][] = $row[$i];
                    } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
                        // Noop, there is a "title"
                    } else {
                        $rows[$i][] = null;
                    }
                }
            }
        } else {
            $rows = array_merge($this->headers, [$divider], $this->rows);
        }

        $this->calculateNumberOfColumns($rows);

        $rows = $this->buildTableRows($rows);
        $this->calculateColumnsWidth($rows);

        $isHeader = !$this->horizontal;
        $isFirstRow = $this->horizontal;
        $hasTitle = (bool) $this->headerTitle;
        foreach ($rows as $row) {
            if ($divider === $row) {
                $isHeader = false;
                $isFirstRow = true;

                continue;
            }
            if ($row instanceof TableSeparator) {
                $this->renderRowSeparator();

                continue;
            }
            if (!$row) {
                continue;
            }

            if ($isHeader || $isFirstRow) {
                $this->renderRowSeparator(
                    $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
                    $hasTitle ? $this->headerTitle : null,
                    $hasTitle ? $this->style->getHeaderTitleFormat() : null
                );
                $isFirstRow = false;
                $hasTitle = false;
            }
            if ($this->horizontal) {
                $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
            } else {
                $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
            }
        }
        $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());

        $this->cleanup();
        $this->rendered = true;
    }

    /**
     * Renders horizontal header separator.
     *
     * Example:
     *
     *     +-----+-----------+-------+
     */
    private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
    {
        if (0 === $count = $this->numberOfColumns) {
            return;
        }

        $borders = $this->style->getBorderChars();
        if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
            return;
        }

        $crossings = $this->style->getCrossingChars();
        if (self::SEPARATOR_MID === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
        } elseif (self::SEPARATOR_TOP === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
        } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
        } else {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
        }

        $markup = $leftChar;
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
            $markup .= $column === $count - 1 ? $rightChar : $midChar;
        }

        if (null !== $title) {
            $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
            $markupLength = Helper::width($markup);
            if ($titleLength > $limit = $markupLength - 4) {
                $titleLength = $limit;
                $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
                $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
            }

            $titleStart = intdiv($markupLength - $titleLength, 2);
            if (false === mb_detect_encoding($markup, null, true)) {
                $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
            } else {
                $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
            }
        }

        $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
    {
        $borders = $this->style->getBorderChars();

        return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
    }

    /**
     * Renders table row.
     *
     * Example:
     *
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     */
    private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
    {
        $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
        $columns = $this->getRowColumns($row);
        $last = \count($columns) - 1;
        foreach ($columns as $i => $column) {
            if ($firstCellFormat && 0 === $i) {
                $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
            } else {
                $rowContent .= $this->renderCell($row, $column, $cellFormat);
            }
            $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     */
    private function renderCell(array $row, int $column, string $cellFormat): string
    {
        $cell = $row[$column] ?? '';
        $width = $this->effectiveColumnWidths[$column];
        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
            // add the width of the following columns(numbers of colspan).
            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
            $width += \strlen($cell) - mb_strwidth($cell, $encoding);
        }

        $style = $this->getColumnStyle($column);

        if ($cell instanceof TableSeparator) {
            return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
        }

        $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
        $content = sprintf($style->getCellRowContentFormat(), $cell);

        $padType = $style->getPadType();
        if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
            $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
            if ($isNotStyledByTag) {
                $cellFormat = $cell->getStyle()->getCellFormat();
                if (!\is_string($cellFormat)) {
                    $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
                    $cellFormat = '<'.$tag.'>%s</>';
                }

                if (strstr($content, '</>')) {
                    $content = str_replace('</>', '', $content);
                    $width -= 3;
                }
                if (strstr($content, '<fg=default;bg=default>')) {
                    $content = str_replace('<fg=default;bg=default>', '', $content);
                    $width -= \strlen('<fg=default;bg=default>');
                }
            }

            $padType = $cell->getStyle()->getPadByAlign();
        }

        return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns(array $rows)
    {
        $columns = [0];
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows(array $rows): TableRows
    {
        /** @var WrappableOutputFormatterInterface $formatter */
        $formatter = $this->output->getFormatter();
        $unmergedRows = [];
        for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
            $rows = $this->fillNextRows($rows, $rowKey);

            // Remove any new line breaks and replace it with a new line
            foreach ($rows[$rowKey] as $column => $cell) {
                $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;

                if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
                    $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
                }
                if (!strstr($cell ?? '', "\n")) {
                    continue;
                }
                $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
                $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
                $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($colspan > 1) {
                        $line = new TableCell($line, ['colspan' => $colspan]);
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
                            $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
                        }
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
            foreach ($rows as $rowKey => $row) {
                yield $row instanceof TableSeparator ? $row : $this->fillCells($row);

                if (isset($unmergedRows[$rowKey])) {
                    foreach ($unmergedRows[$rowKey] as $row) {
                        yield $row instanceof TableSeparator ? $row : $this->fillCells($row);
                    }
                }
            }
        });
    }

    private function calculateRowCount(): int
    {
        $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));

        if ($this->headers) {
            ++$numberOfRows; // Add row for header separator
        }

        if (\count($this->rows) > 0) {
            ++$numberOfRows; // Add row for footer separator
        }

        return $numberOfRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @throws InvalidArgumentException
     */
    private function fillNextRows(array $rows, int $line): array
    {
        $unmergedRows = [];
        foreach ($rows[$line] as $column => $cell) {
            if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
                throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
            }
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = [$cell];
                if (strstr($cell, "\n")) {
                    $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                    $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;

                    $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = $lines[$unmergedRowKey - $line] ?? '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    if ($nbLines === $unmergedRowKey - $line) {
                        break;
                    }
                }
            }
        }

        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
            // we need to know if $unmergedRow will be merged or inserted into $rows
            if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
                foreach ($unmergedRow as $cellKey => $cell) {
                    // insert cell into row at cellKey position
                    array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, [$row]);
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     */
    private function fillCells(iterable $row)
    {
        $newRow = [];

        foreach ($row as $column => $cell) {
            $newRow[] = $cell;
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
                    // insert empty value at column position
                    $newRow[] = '';
                }
            }
        }

        return $newRow ?: $row;
    }

    private function copyRow(array $rows, int $line): array
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     */
    private function getNumberOfColumns(array $row): int
    {
        $columns = \count($row);
        foreach ($row as $column) {
            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
        }

        return $columns;
    }

    /**
     * Gets list of columns for the given row.
     */
    private function getRowColumns(array $row): array
    {
        $columns = range(0, $this->numberOfColumns - 1);
        foreach ($row as $cellKey => $cell) {
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                // exclude grouped columns.
                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     */
    private function calculateColumnsWidth(iterable $rows)
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = [];
            foreach ($rows as $row) {
                if ($row instanceof TableSeparator) {
                    continue;
                }

                foreach ($row as $i => $cell) {
                    if ($cell instanceof TableCell) {
                        $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
                        $textLength = Helper::width($textContent);
                        if ($textLength > 0) {
                            $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
                            foreach ($contentColumns as $position => $content) {
                                $row[$i + $position] = $content;
                            }
                        }
                    }
                }

                $lengths[] = $this->getCellWidth($row, $column);
            }

            $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
        }
    }

    private function getColumnSeparatorWidth(): int
    {
        return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
    }

    private function getCellWidth(array $row, int $column): int
    {
        $cellWidth = 0;

        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
        }

        $columnWidth = $this->columnWidths[$column] ?? 0;
        $cellWidth = max($cellWidth, $columnWidth);

        return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->effectiveColumnWidths = [];
        $this->numberOfColumns = null;
    }

    /**
     * @return array<string, TableStyle>
     */
    private static function initStyles(): array
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChars('=')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChars('')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar('')
            ->setCellRowContentFormat('%s')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChars('-')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        $box = (new TableStyle())
            ->setHorizontalBorderChars('─')
            ->setVerticalBorderChars('│')
            ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
        ;

        $boxDouble = (new TableStyle())
            ->setHorizontalBorderChars('═', '─')
            ->setVerticalBorderChars('║', '│')
            ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
        ;

        return [
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
            'box' => $box,
            'box-double' => $boxDouble,
        ];
    }

    private function resolveStyle($name): TableStyle
    {
        if ($name instanceof TableStyle) {
            return $name;
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
 */
class TableCellStyle
{
    public const DEFAULT_ALIGN = 'left';

    private const TAG_OPTIONS = [
        'fg',
        'bg',
        'options',
    ];

    private const ALIGN_MAP = [
        'left' => \STR_PAD_RIGHT,
        'center' => \STR_PAD_BOTH,
        'right' => \STR_PAD_LEFT,
    ];

    private $options = [
        'fg' => 'default',
        'bg' => 'default',
        'options' => null,
        'align' => self::DEFAULT_ALIGN,
        'cellFormat' => null,
    ];

    public function __construct(array $options = [])
    {
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
            throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
        }

        $this->options = array_merge($this->options, $options);
    }

    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Gets options we need for tag for example fg, bg.
     *
     * @return string[]
     */
    public function getTagOptions()
    {
        return array_filter(
            $this->getOptions(),
            function ($key) {
                return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]);
            },
            \ARRAY_FILTER_USE_KEY
        );
    }

    /**
     * @return int
     */
    public function getPadByAlign()
    {
        return self::ALIGN_MAP[$this->getOptions()['align']];
    }

    public function getCellFormat(): ?string
    {
        return $this->getOptions()['cellFormat'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
final class ProgressBar
{
    public const FORMAT_VERBOSE = 'verbose';
    public const FORMAT_VERY_VERBOSE = 'very_verbose';
    public const FORMAT_DEBUG = 'debug';
    public const FORMAT_NORMAL = 'normal';

    private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
    private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
    private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
    private const FORMAT_NORMAL_NOMAX = 'normal_nomax';

    private $barWidth = 28;
    private $barChar;
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format;
    private $internalFormat;
    private $redrawFreq = 1;
    private $writeCount;
    private $lastWriteTime;
    private $minSecondsBetweenRedraws = 0;
    private $maxSecondsBetweenRedraws = 1;
    private $output;
    private $step = 0;
    private $max;
    private $startTime;
    private $stepWidth;
    private $percent = 0.0;
    private $formatLineCount;
    private $messages = [];
    private $overwrite = true;
    private $terminal;
    private $previousMessage;
    private $cursor;

    private static $formatters;
    private static $formats;

    /**
     * @param int $max Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);
        $this->terminal = new Terminal();

        if (0 < $minSecondsBetweenRedraws) {
            $this->redrawFreq = null;
            $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
        }

        if (!$this->output->isDecorated()) {
            // disable overwrite when output does not support ANSI codes.
            $this->overwrite = false;

            // set a reasonable redraw frequency so output isn't flooded
            $this->redrawFreq = null;
        }

        $this->startTime = time();
        $this->cursor = new Cursor($output);
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     */
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return self::$formatters[$name] ?? null;
    }

    /**
     * Sets a format for a given name.
     *
     * This method also allow you to override an existing format.
     *
     * @param string $name   The format name
     * @param string $format A format string
     */
    public static function setFormatDefinition(string $name, string $format): void
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        self::$formats[$name] = $format;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     */
    public static function getFormatDefinition(string $name): ?string
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        return self::$formats[$name] ?? null;
    }

    /**
     * Associates a text with a named placeholder.
     *
     * The text is displayed when the progress bar is rendered but only
     * when the corresponding placeholder is part of the custom format line
     * (by wrapping the name with %).
     *
     * @param string $message The text to associate with the placeholder
     * @param string $name    The name of the placeholder
     */
    public function setMessage(string $message, string $name = 'message')
    {
        $this->messages[$name] = $message;
    }

    public function getMessage(string $name = 'message')
    {
        return $this->messages[$name];
    }

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

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

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

    private function getStepWidth(): int
    {
        return $this->stepWidth;
    }

    public function getProgressPercent(): float
    {
        return $this->percent;
    }

    public function getBarOffset(): float
    {
        return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
    }

    public function getEstimated(): float
    {
        if (!$this->step) {
            return 0;
        }

        return round((time() - $this->startTime) / $this->step * $this->max);
    }

    public function getRemaining(): float
    {
        if (!$this->step) {
            return 0;
        }

        return round((time() - $this->startTime) / $this->step * ($this->max - $this->step));
    }

    public function setBarWidth(int $size)
    {
        $this->barWidth = max(1, $size);
    }

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

    public function setBarCharacter(string $char)
    {
        $this->barChar = $char;
    }

    public function getBarCharacter(): string
    {
        return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
    }

    public function setEmptyBarCharacter(string $char)
    {
        $this->emptyBarChar = $char;
    }

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

    public function setProgressCharacter(string $char)
    {
        $this->progressChar = $char;
    }

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

    public function setFormat(string $format)
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|null $freq The frequency in steps
     */
    public function setRedrawFrequency(?int $freq)
    {
        $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
    }

    public function minSecondsBetweenRedraws(float $seconds): void
    {
        $this->minSecondsBetweenRedraws = $seconds;
    }

    public function maxSecondsBetweenRedraws(float $seconds): void
    {
        $this->maxSecondsBetweenRedraws = $seconds;
    }

    /**
     * Returns an iterator that will automatically update the progress bar when iterated.
     *
     * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
     */
    public function iterate(iterable $iterable, int $max = null): iterable
    {
        $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));

        foreach ($iterable as $key => $value) {
            yield $key => $value;

            $this->advance();
        }

        $this->finish();
    }

    /**
     * Starts the progress output.
     *
     * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     */
    public function start(int $max = null)
    {
        $this->startTime = time();
        $this->step = 0;
        $this->percent = 0.0;

        if (null !== $max) {
            $this->setMaxSteps($max);
        }

        $this->display();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function advance(int $step = 1)
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     */
    public function setOverwrite(bool $overwrite)
    {
        $this->overwrite = $overwrite;
    }

    public function setProgress(int $step)
    {
        if ($this->max && $step > $this->max) {
            $this->max = $step;
        } elseif ($step < 0) {
            $step = 0;
        }

        $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
        $prevPeriod = (int) ($this->step / $redrawFreq);
        $currPeriod = (int) ($step / $redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        $timeInterval = microtime(true) - $this->lastWriteTime;

        // Draw regardless of other limits
        if ($this->max === $step) {
            $this->display();

            return;
        }

        // Throttling
        if ($timeInterval < $this->minSecondsBetweenRedraws) {
            return;
        }

        // Draw each step period, but not too late
        if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
            $this->display();
        }
    }

    public function setMaxSteps(int $max)
    {
        $this->format = null;
        $this->max = max(0, $max);
        $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4;
    }

    /**
     * Finishes the progress output.
     */
    public function finish(): void
    {
        if (!$this->max) {
            $this->max = $this->step;
        }

        if ($this->step === $this->max && !$this->overwrite) {
            // prevent double 100% output
            return;
        }

        $this->setProgress($this->max);
    }

    /**
     * Outputs the current progress string.
     */
    public function display(): void
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite($this->buildLine());
    }

    /**
     * Removes the progress bar from the current line.
     *
     * This is useful if you wish to write some output
     * while a progress bar is running.
     * Call display() to show the progress bar again.
     */
    public function clear(): void
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    private function setRealFormat(string $format)
    {
        // try to use the _nomax variant if available
        if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
            $this->format = self::getFormatDefinition($format.'_nomax');
        } elseif (null !== self::getFormatDefinition($format)) {
            $this->format = self::getFormatDefinition($format);
        } else {
            $this->format = $format;
        }

        $this->formatLineCount = substr_count($this->format, "\n");
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message): void
    {
        if ($this->previousMessage === $message) {
            return;
        }

        $originalMessage = $message;

        if ($this->overwrite) {
            if (null !== $this->previousMessage) {
                if ($this->output instanceof ConsoleSectionOutput) {
                    $messageLines = explode("\n", $message);
                    $lineCount = \count($messageLines);
                    foreach ($messageLines as $messageLine) {
                        $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
                        if ($messageLineLength > $this->terminal->getWidth()) {
                            $lineCount += floor($messageLineLength / $this->terminal->getWidth());
                        }
                    }
                    $this->output->clear($lineCount);
                } else {
                    for ($i = 0; $i < $this->formatLineCount; ++$i) {
                        $this->cursor->moveToColumn(1);
                        $this->cursor->clearLine();
                        $this->cursor->moveUp();
                    }

                    $this->cursor->moveToColumn(1);
                    $this->cursor->clearLine();
                }
            }
        } elseif ($this->step > 0) {
            $message = \PHP_EOL.$message;
        }

        $this->previousMessage = $originalMessage;
        $this->lastWriteTime = microtime(true);

        $this->output->write($message);
        ++$this->writeCount;
    }

    private function determineBestFormat(): string
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX;
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
                return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX;
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX;
            default:
                return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX;
        }
    }

    private static function initPlaceholderFormatters(): array
    {
        return [
            'bar' => function (self $bar, OutputInterface $output) {
                $completeBars = $bar->getBarOffset();
                $display = str_repeat($bar->getBarCharacter(), $completeBars);
                if ($completeBars < $bar->getBarWidth()) {
                    $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
                    $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return $display;
            },
            'elapsed' => function (self $bar) {
                return Helper::formatTime(time() - $bar->getStartTime());
            },
            'remaining' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getRemaining());
            },
            'estimated' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getEstimated());
            },
            'memory' => function (self $bar) {
                return Helper::formatMemory(memory_get_usage(true));
            },
            'current' => function (self $bar) {
                return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
            },
            'max' => function (self $bar) {
                return $bar->getMaxSteps();
            },
            'percent' => function (self $bar) {
                return floor($bar->getProgressPercent() * 100);
            },
        ];
    }

    private static function initFormats(): array
    {
        return [
            self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
            self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',

            self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        ];
    }

    private function buildLine(): string
    {
        $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        $callback = function ($matches) {
            if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
                $text = $formatter($this, $this->output);
            } elseif (isset($this->messages[$matches[1]])) {
                $text = $this->messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = sprintf('%'.$matches[2], $text);
            }

            return $text;
        };
        $line = preg_replace_callback($regex, $callback, $this->format);

        // gets string length for each sub line with multiline format
        $linesLength = array_map(function ($subLine) {
            return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r")));
        }, explode("\n", $line));

        $linesWidth = max($linesLength);

        $terminalWidth = $this->terminal->getWidth();
        if ($linesWidth <= $terminalWidth) {
            return $line;
        }

        $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);

        return preg_replace_callback($regex, $callback, $this->format);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\String\UnicodeString;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Helper implements HelperInterface
{
    protected $helperSet = null;

    /**
     * {@inheritdoc}
     */
    public function setHelperSet(HelperSet $helperSet = null)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * {@inheritdoc}
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Returns the length of a string, using mb_strwidth if it is available.
     *
     * @deprecated since Symfony 5.3
     *
     * @return int
     */
    public static function strlen(?string $string)
    {
        trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__);

        return self::width($string);
    }

    /**
     * Returns the width of a string, using mb_strwidth if it is available.
     * The width is how many characters positions the string will use.
     */
    public static function width(?string $string): int
    {
        $string ?? $string = '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->width(false);
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Returns the length of a string, using mb_strlen if it is available.
     * The length is related to how many bytes the string will use.
     */
    public static function length(?string $string): int
    {
        $string ?? $string = '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->length();
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strlen($string, $encoding);
    }

    /**
     * Returns the subset of a string, using mb_substr if it is available.
     *
     * @return string
     */
    public static function substr(?string $string, int $from, int $length = null)
    {
        $string ?? $string = '';

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return substr($string, $from, $length);
        }

        return mb_substr($string, $from, $length, $encoding);
    }

    public static function formatTime($secs)
    {
        static $timeFormats = [
            [0, '< 1 sec'],
            [1, '1 sec'],
            [2, 'secs', 1],
            [60, '1 min'],
            [120, 'mins', 60],
            [3600, '1 hr'],
            [7200, 'hrs', 3600],
            [86400, '1 day'],
            [172800, 'days', 86400],
        ];

        foreach ($timeFormats as $index => $format) {
            if ($secs >= $format[0]) {
                if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
                    || $index == \count($timeFormats) - 1
                ) {
                    if (2 == \count($format)) {
                        return $format[1];
                    }

                    return floor($secs / $format[2]).' '.$format[1];
                }
            }
        }
    }

    public static function formatMemory(int $memory)
    {
        if ($memory >= 1024 * 1024 * 1024) {
            return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
        }

        if ($memory >= 1024 * 1024) {
            return sprintf('%.1f MiB', $memory / 1024 / 1024);
        }

        if ($memory >= 1024) {
            return sprintf('%d KiB', $memory / 1024);
        }

        return sprintf('%d B', $memory);
    }

    /**
     * @deprecated since Symfony 5.3
     */
    public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string)
    {
        trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__);

        return self::width(self::removeDecoration($formatter, $string));
    }

    public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
    {
        $isDecorated = $formatter->isDecorated();
        $formatter->setDecorated(false);
        // remove <...> formatting
        $string = $formatter->format($string ?? '');
        // remove already formatted characters
        $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
        $formatter->setDecorated($isDecorated);

        return $string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
    private $started = [];
    private $count = -1;

    /**
     * Starts a debug formatting session.
     *
     * @return string
     */
    public function start(string $id, string $message, string $prefix = 'RUN')
    {
        $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];

        return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
    }

    /**
     * Adds progress to a formatting session.
     *
     * @return string
     */
    public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR')
    {
        $message = '';

        if ($error) {
            if (isset($this->started[$id]['out'])) {
                $message .= "\n";
                unset($this->started[$id]['out']);
            }
            if (!isset($this->started[$id]['err'])) {
                $message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
                $this->started[$id]['err'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
        } else {
            if (isset($this->started[$id]['err'])) {
                $message .= "\n";
                unset($this->started[$id]['err']);
            }
            if (!isset($this->started[$id]['out'])) {
                $message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
                $this->started[$id]['out'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
        }

        return $message;
    }

    /**
     * Stops a formatting session.
     *
     * @return string
     */
    public function stop(string $id, string $message, bool $successful, string $prefix = 'RES')
    {
        $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';

        if ($successful) {
            return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
        }

        $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);

        unset($this->started[$id]['out'], $this->started[$id]['err']);

        return $message;
    }

    private function getBorder(string $id): string
    {
        return sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'debug_formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = [])
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

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

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private $question;
    private $attempts;
    private $hidden = false;
    private $hiddenFallback = true;
    private $autocompleterCallback;
    private $validator;
    private $default;
    private $normalizer;
    private $trimmable = true;
    private $multiline = false;

    /**
     * @param string                     $question The question to ask to the user
     * @param string|bool|int|float|null $default  The default answer to return if the user enters nothing
     */
    public function __construct(string $question, $default = null)
    {
        $this->question = $question;
        $this->default = $default;
    }

    /**
     * Returns the question.
     *
     * @return string
     */
    public function getQuestion()
    {
        return $this->question;
    }

    /**
     * Returns the default answer.
     *
     * @return string|bool|int|float|null
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns whether the user response accepts newline characters.
     */
    public function isMultiline(): bool
    {
        return $this->multiline;
    }

    /**
     * Sets whether the user response should accept newline characters.
     *
     * @return $this
     */
    public function setMultiline(bool $multiline): self
    {
        $this->multiline = $multiline;

        return $this;
    }

    /**
     * Returns whether the user response must be hidden.
     *
     * @return bool
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets whether the user response must be hidden or not.
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    public function setHidden(bool $hidden)
    {
        if ($this->autocompleterCallback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->hidden = $hidden;

        return $this;
    }

    /**
     * In case the response cannot be hidden, whether to fallback on non-hidden question or not.
     *
     * @return bool
     */
    public function isHiddenFallback()
    {
        return $this->hiddenFallback;
    }

    /**
     * Sets whether to fallback on non-hidden question if the response cannot be hidden.
     *
     * @return $this
     */
    public function setHiddenFallback(bool $fallback)
    {
        $this->hiddenFallback = $fallback;

        return $this;
    }

    /**
     * Gets values for the autocompleter.
     *
     * @return iterable|null
     */
    public function getAutocompleterValues()
    {
        $callback = $this->getAutocompleterCallback();

        return $callback ? $callback('') : null;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @return $this
     *
     * @throws LogicException
     */
    public function setAutocompleterValues(?iterable $values)
    {
        if (\is_array($values)) {
            $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);

            $callback = static function () use ($values) {
                return $values;
            };
        } elseif ($values instanceof \Traversable) {
            $valueCache = null;
            $callback = static function () use ($values, &$valueCache) {
                return $valueCache ?? $valueCache = iterator_to_array($values, false);
            };
        } else {
            $callback = null;
        }

        return $this->setAutocompleterCallback($callback);
    }

    /**
     * Gets the callback function used for the autocompleter.
     */
    public function getAutocompleterCallback(): ?callable
    {
        return $this->autocompleterCallback;
    }

    /**
     * Sets the callback function used for the autocompleter.
     *
     * The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
     *
     * @return $this
     */
    public function setAutocompleterCallback(callable $callback = null): self
    {
        if ($this->hidden && null !== $callback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterCallback = $callback;

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @return $this
     */
    public function setValidator(callable $validator = null)
    {
        $this->validator = $validator;

        return $this;
    }

    /**
     * Gets the validator for the question.
     *
     * @return callable|null
     */
    public function getValidator()
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    public function setMaxAttempts(?int $attempts)
    {
        if (null !== $attempts && $attempts < 1) {
            throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
        }

        $this->attempts = $attempts;

        return $this;
    }

    /**
     * Gets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return int|null
     */
    public function getMaxAttempts()
    {
        return $this->attempts;
    }

    /**
     * Sets a normalizer for the response.
     *
     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
     *
     * @return $this
     */
    public function setNormalizer(callable $normalizer)
    {
        $this->normalizer = $normalizer;

        return $this;
    }

    /**
     * Gets the normalizer for the response.
     *
     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
     *
     * @return callable|null
     */
    public function getNormalizer()
    {
        return $this->normalizer;
    }

    protected function isAssoc(array $array)
    {
        return (bool) \count(array_filter(array_keys($array), 'is_string'));
    }

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

    /**
     * @return $this
     */
    public function setTrimmable(bool $trimmable): self
    {
        $this->trimmable = $trimmable;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private $trueAnswerRegex;

    /**
     * @param string $question        The question to ask to the user
     * @param bool   $default         The default answer to return, true or false
     * @param string $trueAnswerRegex A regex to match the "yes" answer
     */
    public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     */
    private function getDefaultNormalizer(): callable
    {
        $default = $this->getDefault();
        $regex = $this->trueAnswerRegex;

        return function ($answer) use ($default, $regex) {
            if (\is_bool($answer)) {
                return $answer;
            }

            $answerIsTrue = (bool) preg_match($regex, $answer);
            if (false === $default) {
                return $answer && $answerIsTrue;
            }

            return '' === $answer || $answerIsTrue;
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private $choices;
    private $multiselect = false;
    private $prompt = ' > ';
    private $errorMessage = 'Value "%s" is invalid';

    /**
     * @param string $question The question to ask to the user
     * @param array  $choices  The list of available choices
     * @param mixed  $default  The default answer to return
     */
    public function __construct(string $question, array $choices, $default = null)
    {
        if (!$choices) {
            throw new \LogicException('Choice question must have at least 1 choice available.');
        }

        parent::__construct($question, $default);

        $this->choices = $choices;
        $this->setValidator($this->getDefaultValidator());
        $this->setAutocompleterValues($choices);
    }

    /**
     * Returns available choices.
     *
     * @return array
     */
    public function getChoices()
    {
        return $this->choices;
    }

    /**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @return $this
     */
    public function setMultiselect(bool $multiselect)
    {
        $this->multiselect = $multiselect;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns whether the choices are multiselect.
     *
     * @return bool
     */
    public function isMultiselect()
    {
        return $this->multiselect;
    }

    /**
     * Gets the prompt for choices.
     *
     * @return string
     */
    public function getPrompt()
    {
        return $this->prompt;
    }

    /**
     * Sets the prompt for choices.
     *
     * @return $this
     */
    public function setPrompt(string $prompt)
    {
        $this->prompt = $prompt;

        return $this;
    }

    /**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @return $this
     */
    public function setErrorMessage(string $errorMessage)
    {
        $this->errorMessage = $errorMessage;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    private function getDefaultValidator(): callable
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
                }

                $selectedChoices = explode(',', $selected);
            } else {
                $selectedChoices = [$selected];
            }

            if ($this->isTrimmable()) {
                foreach ($selectedChoices as $k => $v) {
                    $selectedChoices[$k] = trim($v);
                }
            }

            $multiselectChoices = [];
            foreach ($selectedChoices as $value) {
                $results = [];
                foreach ($choices as $key => $choice) {
                    if ($choice === $value) {
                        $results[] = $key;
                    }
                }

                if (\count($results) > 1) {
                    throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
                }

                $result = array_search($value, $choices);

                if (!$isAssoc) {
                    if (false !== $result) {
                        $result = $choices[$result];
                    } elseif (isset($choices[$value])) {
                        $result = $choices[$value];
                    }
                } elseif (false === $result && isset($choices[$value])) {
                    $result = $value;
                }

                if (false === $result) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
                }

                // For associative choices, consistently return the key as string:
                $multiselectChoices[] = $isAssoc ? (string) $result : $result;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return current($multiselectChoices);
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

class Terminal
{
    private static $width;
    private static $height;
    private static $stty;

    /**
     * Gets the terminal width.
     *
     * @return int
     */
    public function getWidth()
    {
        $width = getenv('COLUMNS');
        if (false !== $width) {
            return (int) trim($width);
        }

        if (null === self::$width) {
            self::initDimensions();
        }

        return self::$width ?: 80;
    }

    /**
     * Gets the terminal height.
     *
     * @return int
     */
    public function getHeight()
    {
        $height = getenv('LINES');
        if (false !== $height) {
            return (int) trim($height);
        }

        if (null === self::$height) {
            self::initDimensions();
        }

        return self::$height ?: 50;
    }

    /**
     * @internal
     */
    public static function hasSttyAvailable(): bool
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        // skip check if exec function is disabled
        if (!\function_exists('exec')) {
            return false;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }

    private static function initDimensions()
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
                // extract [w, H] from "wxh (WxH)"
                // or [w, h] from "wxh"
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
                self::initDimensionsUsingStty();
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                // extract [w, h] from "wxh"
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } else {
            self::initDimensionsUsingStty();
        }
    }

    /**
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
     */
    private static function hasVt100Support(): bool
    {
        return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
    }

    /**
     * Initializes dimensions using the output of an stty columns line.
     */
    private static function initDimensionsUsingStty()
    {
        if ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                // extract [w, h] from "rows h; columns w;"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                // extract [w, h] from "; h rows; w columns"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
     */
    private static function getConsoleMode(): ?array
    {
        $info = self::readFromProcess('mode CON');

        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
            return null;
        }

        return [(int) $matches[2], (int) $matches[1]];
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     */
    private static function getSttyColumns(): ?string
    {
        return self::readFromProcess('stty -a | grep columns');
    }

    private static function readFromProcess(string $command): ?string
    {
        if (!\function_exists('proc_open')) {
            return null;
        }

        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];

        $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
        if (!\is_resource($process)) {
            return null;
        }

        $info = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        proc_close($process);

        return $info;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private $container;
    private $commandMap;

    /**
     * @param array $commandMap An array with command names as keys and service ids as values
     */
    public function __construct(ContainerInterface $container, array $commandMap)
    {
        $this->container = $container;
        $this->commandMap = $commandMap;
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $name)
    {
        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        return $this->container->get($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function has(string $name)
    {
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->commandMap);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface CommandLoaderInterface
{
    /**
     * Loads a command.
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function get(string $name);

    /**
     * Checks if a command exists.
     *
     * @return bool
     */
    public function has(string $name);

    /**
     * @return string[]
     */
    public function getNames();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private $factories;

    /**
     * @param callable[] $factories Indexed by command names
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    /**
     * {@inheritdoc}
     */
    public function has(string $name)
    {
        return isset($this->factories[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $name)
    {
        if (!isset($this->factories[$name])) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        $factory = $this->factories[$name];

        return $factory();
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->factories);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Intl\Grapheme as p;

if (!defined('GRAPHEME_EXTR_COUNT')) {
    define('GRAPHEME_EXTR_COUNT', 0);
}
if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
    define('GRAPHEME_EXTR_MAXBYTES', 1);
}
if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
    define('GRAPHEME_EXTR_MAXCHARS', 2);
}

if (!function_exists('grapheme_extract')) {
    function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); }
}
if (!function_exists('grapheme_stripos')) {
    function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_stristr')) {
    function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); }
}
if (!function_exists('grapheme_strlen')) {
    function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); }
}
if (!function_exists('grapheme_strpos')) {
    function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strripos')) {
    function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strrpos')) {
    function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strstr')) {
    function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); }
}
if (!function_exists('grapheme_substr')) {
    function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Intl\Grapheme as p;

if (extension_loaded('intl')) {
    return;
}

if (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!defined('GRAPHEME_EXTR_COUNT')) {
    define('GRAPHEME_EXTR_COUNT', 0);
}
if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
    define('GRAPHEME_EXTR_MAXBYTES', 1);
}
if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
    define('GRAPHEME_EXTR_MAXCHARS', 2);
}

if (!function_exists('grapheme_extract')) {
    function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); }
}
if (!function_exists('grapheme_stripos')) {
    function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_stristr')) {
    function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); }
}
if (!function_exists('grapheme_strlen')) {
    function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); }
}
if (!function_exists('grapheme_strpos')) {
    function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strripos')) {
    function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strrpos')) {
    function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strstr')) {
    function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); }
}
if (!function_exists('grapheme_substr')) {
    function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Intl\Grapheme;

\define('SYMFONY_GRAPHEME_CLUSTER_RX', \PCRE_VERSION >= '8.32' ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX);

/**
 * Partial intl implementation in pure PHP.
 *
 * Implemented:
 * - grapheme_extract  - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8
 * - grapheme_stripos  - Find position (in grapheme units) of first occurrence of a case-insensitive string
 * - grapheme_stristr  - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack
 * - grapheme_strlen   - Get string length in grapheme units
 * - grapheme_strpos   - Find position (in grapheme units) of first occurrence of a string
 * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string
 * - grapheme_strrpos  - Find position (in grapheme units) of last occurrence of a string
 * - grapheme_strstr   - Returns part of haystack string from the first occurrence of needle to the end of haystack
 * - grapheme_substr   - Return part of a string
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Grapheme
{
    // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
    // This regular expression is a work around for http://bugs.exim.org/1279
    public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])';

    private const CASE_FOLD = [
        ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
        ['μ', 's', 'ι',        'σ', 'β',        'θ',        'φ',        'π',        'κ',        'ρ',        'ε',        "\xE1\xB9\xA1", 'ι'],
    ];

    public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0)
    {
        if (0 > $start) {
            $start = \strlen($s) + $start;
        }

        if (!is_scalar($s)) {
            $hasError = false;
            set_error_handler(function () use (&$hasError) { $hasError = true; });
            $next = substr($s, $start);
            restore_error_handler();
            if ($hasError) {
                substr($s, $start);
                $s = '';
            } else {
                $s = $next;
            }
        } else {
            $s = substr($s, $start);
        }
        $size = (int) $size;
        $type = (int) $type;
        $start = (int) $start;

        if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) {
            if (80000 > \PHP_VERSION_ID) {
                return false;
            }

            throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS');
        }

        if (!isset($s[0]) || 0 > $size || 0 > $start) {
            return false;
        }
        if (0 === $size) {
            return '';
        }

        $next = $start;

        $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE);

        if (!isset($s[1])) {
            return false;
        }

        $i = 1;
        $ret = '';

        do {
            if (\GRAPHEME_EXTR_COUNT === $type) {
                --$size;
            } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) {
                $size -= \strlen($s[$i]);
            } else {
                $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE');
            }

            if ($size >= 0) {
                $ret .= $s[$i];
            }
        } while (isset($s[++$i]) && $size > 0);

        $next += \strlen($ret);

        return $ret;
    }

    public static function grapheme_strlen($s)
    {
        preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len);

        return 0 === $len && '' !== $s ? null : $len;
    }

    public static function grapheme_substr($s, $start, $len = null)
    {
        if (null === $len) {
            $len = 2147483647;
        }

        preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s);

        $slen = \count($s[0]);
        $start = (int) $start;

        if (0 > $start) {
            $start += $slen;
        }
        if (0 > $start) {
            if (\PHP_VERSION_ID < 80000) {
                return false;
            }

            $start = 0;
        }
        if ($start >= $slen) {
            return \PHP_VERSION_ID >= 80000 ? '' : false;
        }

        $rem = $slen - $start;

        if (0 > $len) {
            $len += $rem;
        }
        if (0 === $len) {
            return '';
        }
        if (0 > $len) {
            return \PHP_VERSION_ID >= 80000 ? '' : false;
        }
        if ($len > $rem) {
            $len = $rem;
        }

        return implode('', \array_slice($s[0], $start, $len));
    }

    public static function grapheme_strpos($s, $needle, $offset = 0)
    {
        return self::grapheme_position($s, $needle, $offset, 0);
    }

    public static function grapheme_stripos($s, $needle, $offset = 0)
    {
        return self::grapheme_position($s, $needle, $offset, 1);
    }

    public static function grapheme_strrpos($s, $needle, $offset = 0)
    {
        return self::grapheme_position($s, $needle, $offset, 2);
    }

    public static function grapheme_strripos($s, $needle, $offset = 0)
    {
        return self::grapheme_position($s, $needle, $offset, 3);
    }

    public static function grapheme_stristr($s, $needle, $beforeNeedle = false)
    {
        return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8');
    }

    public static function grapheme_strstr($s, $needle, $beforeNeedle = false)
    {
        return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8');
    }

    private static function grapheme_position($s, $needle, $offset, $mode)
    {
        $needle = (string) $needle;
        if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) {
            return false;
        }
        $s = (string) $s;
        if (!preg_match('/./us', $s)) {
            return false;
        }
        if ($offset > 0) {
            $s = self::grapheme_substr($s, $offset);
        } elseif ($offset < 0) {
            if (2 > $mode) {
                $offset += self::grapheme_strlen($s);
                $s = self::grapheme_substr($s, $offset);
                if (0 > $offset) {
                    $offset = 0;
                }
            } elseif (0 > $offset += self::grapheme_strlen($needle)) {
                $s = self::grapheme_substr($s, 0, $offset);
                $offset = 0;
            } else {
                $offset = 0;
            }
        }

        // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8,
        // we can use normal binary string functions here. For case-insensitive searches,
        // case fold the strings first.
        $caseInsensitive = $mode & 1;
        $reverse = $mode & 2;
        if ($caseInsensitive) {
            // Use the same case folding mode as mbstring does for mb_stripos().
            // Stick to SIMPLE case folding to avoid changing the length of the string, which
            // might result in offsets being shifted.
            $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER;
            $s = mb_convert_case($s, $mode, 'UTF-8');
            $needle = mb_convert_case($needle, $mode, 'UTF-8');

            if (!\defined('MB_CASE_FOLD_SIMPLE')) {
                $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s);
                $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle);
            }
        }
        if ($reverse) {
            $needlePos = strrpos($s, $needle);
        } else {
            $needlePos = strpos($s, $needle);
        }

        return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

/**
 * Provides a way to reset an object to its initial state.
 *
 * When calling the "reset()" method on an object, it should be put back to its
 * initial state. This usually means clearing any internal buffers and forwarding
 * the call to internal dependencies. All properties of the object should be put
 * back to the same state it had when it was first ready to use.
 *
 * This method could be called, for example, to recycle objects that are used as
 * services, so that they can be used to handle several requests in the same
 * process loop (note that we advise making your services stateless instead of
 * implementing this interface when possible.)
 */
interface ResetInterface
{
    public function reset();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

// Help opcache.preload discover always-needed symbols
class_exists(ContainerExceptionInterface::class);
class_exists(NotFoundExceptionInterface::class);

/**
 * A trait to help implement ServiceProviderInterface.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
trait ServiceLocatorTrait
{
    private $factories;
    private $loading = [];
    private $providedTypes;

    /**
     * @param callable[] $factories
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    /**
     * {@inheritdoc}
     *
     * @return bool
     */
    public function has(string $id)
    {
        return isset($this->factories[$id]);
    }

    /**
     * {@inheritdoc}
     *
     * @return mixed
     */
    public function get(string $id)
    {
        if (!isset($this->factories[$id])) {
            throw $this->createNotFoundException($id);
        }

        if (isset($this->loading[$id])) {
            $ids = array_values($this->loading);
            $ids = \array_slice($this->loading, array_search($id, $ids));
            $ids[] = $id;

            throw $this->createCircularReferenceException($id, $ids);
        }

        $this->loading[$id] = $id;
        try {
            return $this->factories[$id]($this);
        } finally {
            unset($this->loading[$id]);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getProvidedServices(): array
    {
        if (null === $this->providedTypes) {
            $this->providedTypes = [];

            foreach ($this->factories as $name => $factory) {
                if (!\is_callable($factory)) {
                    $this->providedTypes[$name] = '?';
                } else {
                    $type = (new \ReflectionFunction($factory))->getReturnType();

                    $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?';
                }
            }
        }

        return $this->providedTypes;
    }

    private function createNotFoundException(string $id): NotFoundExceptionInterface
    {
        if (!$alternatives = array_keys($this->factories)) {
            $message = 'is empty...';
        } else {
            $last = array_pop($alternatives);
            if ($alternatives) {
                $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
            } else {
                $message = sprintf('only knows about the "%s" service.', $last);
            }
        }

        if ($this->loading) {
            $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
        } else {
            $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
        }

        return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
        };
    }

    private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
    {
        return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

/**
 * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
 *
 * The getSubscribedServices method returns an array of service types required by such instances,
 * optionally keyed by the service names used internally. Service types that start with an interrogation
 * mark "?" are optional, while the other ones are mandatory service dependencies.
 *
 * The injected service locators SHOULD NOT allow access to any other services not specified by the method.
 *
 * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
 * This interface does not dictate any injection method for these service locators, although constructor
 * injection is recommended.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface ServiceSubscriberInterface
{
    /**
     * Returns an array of service types required by such instances, optionally keyed by the service names used internally.
     *
     * For mandatory dependencies:
     *
     *  * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
     *    internally to fetch a service which must implement Psr\Log\LoggerInterface.
     *  * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
     *    internally to fetch an iterable of Psr\Log\LoggerInterface instances.
     *  * ['Psr\Log\LoggerInterface'] is a shortcut for
     *  * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
     *
     * otherwise:
     *
     *  * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
     *  * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
     *  * ['?Psr\Log\LoggerInterface'] is a shortcut for
     *  * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
     *
     * @return string[] The required service types, optionally keyed by service names
     */
    public static function getSubscribedServices();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Test;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;

abstract class ServiceLocatorTest extends TestCase
{
    /**
     * @return ContainerInterface
     */
    protected function getServiceLocator(array $factories)
    {
        return new class($factories) implements ContainerInterface {
            use ServiceLocatorTrait;
        };
    }

    public function testHas()
    {
        $locator = $this->getServiceLocator([
            'foo' => function () { return 'bar'; },
            'bar' => function () { return 'baz'; },
            function () { return 'dummy'; },
        ]);

        $this->assertTrue($locator->has('foo'));
        $this->assertTrue($locator->has('bar'));
        $this->assertFalse($locator->has('dummy'));
    }

    public function testGet()
    {
        $locator = $this->getServiceLocator([
            'foo' => function () { return 'bar'; },
            'bar' => function () { return 'baz'; },
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('baz', $locator->get('bar'));
    }

    public function testGetDoesNotMemoize()
    {
        $i = 0;
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$i) {
                ++$i;

                return 'bar';
            },
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame(2, $i);
    }

    public function testThrowsOnUndefinedInternalService()
    {
        if (!$this->getExpectedException()) {
            $this->expectException(\Psr\Container\NotFoundExceptionInterface::class);
            $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
        }
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $locator->get('foo');
    }

    public function testThrowsOnCircularReference()
    {
        $this->expectException(\Psr\Container\ContainerExceptionInterface::class);
        $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$locator) { return $locator->get('bar'); },
            'bar' => function () use (&$locator) { return $locator->get('baz'); },
            'baz' => function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $locator->get('foo');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

use Psr\Container\ContainerInterface;

/**
 * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Mateusz Sip <mateusz.sip@gmail.com>
 */
interface ServiceProviderInterface extends ContainerInterface
{
    /**
     * Returns an associative array of service types keyed by the identifiers provided by the current container.
     *
     * Examples:
     *
     *  * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
     *  * ['foo' => '?'] means the container provides service name "foo" of unspecified type
     *  * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
     *
     * @return string[] The provided service types, keyed by service names
     */
    public function getProvidedServices(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Attribute;

use Symfony\Contracts\Service\ServiceSubscriberTrait;

/**
 * Use with {@see ServiceSubscriberTrait} to mark a method's return type
 * as a subscribed service.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
#[\Attribute(\Attribute::TARGET_METHOD)]
final class SubscribedService
{
    /**
     * @param string|null $key The key to use for the service
     *                         If null, use "ClassName::methodName"
     */
    public function __construct(
        public ?string $key = null
    ) {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Attribute;

/**
 * A required dependency.
 *
 * This attribute indicates that a property holds a required dependency. The annotated property or method should be
 * considered during the instantiation process of the containing class.
 *
 * @author Alexander M. Turek <me@derrabus.de>
 */
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Required
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\Attribute\SubscribedService;

/**
 * Implementation of ServiceSubscriberInterface that determines subscribed services from
 * method return types. Service ids are available as "ClassName::methodName".
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
trait ServiceSubscriberTrait
{
    /** @var ContainerInterface */
    protected $container;

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedServices(): array
    {
        static $services;

        if (null !== $services) {
            return $services;
        }

        $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : [];
        $attributeOptIn = false;

        if (\PHP_VERSION_ID >= 80000) {
            foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
                if (self::class !== $method->getDeclaringClass()->name) {
                    continue;
                }

                if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) {
                    continue;
                }

                if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
                    throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name));
                }

                if (!$returnType = $method->getReturnType()) {
                    throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class));
                }

                $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;

                if ($returnType->allowsNull()) {
                    $serviceId = '?'.$serviceId;
                }

                $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId;
                $attributeOptIn = true;
            }
        }

        if (!$attributeOptIn) {
            foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
                if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
                    continue;
                }

                if (self::class !== $method->getDeclaringClass()->name) {
                    continue;
                }

                if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) {
                    continue;
                }

                if ($returnType->isBuiltin()) {
                    continue;
                }

                if (\PHP_VERSION_ID >= 80000) {
                    trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class);
                }

                $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType);
            }
        }

        return $services;
    }

    /**
     * @required
     *
     * @return ContainerInterface|null
     */
    public function setContainer(ContainerInterface $container)
    {
        $this->container = $container;

        if (\is_callable(['parent', __FUNCTION__])) {
            return parent::setContainer($container);
        }

        return null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (!function_exists('trigger_deprecation')) {
    /**
     * Triggers a silenced deprecation notice.
     *
     * @param string $package The name of the Composer package that is triggering the deprecation
     * @param string $version The version of the package that introduced the deprecation
     * @param string $message The message of the deprecation
     * @param mixed  ...$args Values to insert in the message using printf() formatting
     *
     * @author Nicolas Grekas <p@tchwork.com>
     */
    function trigger_deprecation(string $package, string $version, string $message, ...$args): void
    {
        @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Gitignore matches against text.
 *
 * @author Michael Voříšek <vorismi3@fel.cvut.cz>
 * @author Ahmed Abdou <mail@ahmd.io>
 */
class Gitignore
{
    /**
     * Returns a regexp which is the equivalent of the gitignore pattern.
     *
     * Format specification: https://git-scm.com/docs/gitignore#_pattern_format
     */
    public static function toRegex(string $gitignoreFileContent): string
    {
        return self::buildRegex($gitignoreFileContent, false);
    }

    public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string
    {
        return self::buildRegex($gitignoreFileContent, true);
    }

    private static function buildRegex(string $gitignoreFileContent, bool $inverted): string
    {
        $gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
        $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);

        $res = self::lineToRegex('');
        foreach ($gitignoreLines as $line) {
            $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);

            if ('!' === substr($line, 0, 1)) {
                $line = substr($line, 1);
                $isNegative = true;
            } else {
                $isNegative = false;
            }

            if ('' !== $line) {
                if ($isNegative xor $inverted) {
                    $res = '(?!'.self::lineToRegex($line).'$)'.$res;
                } else {
                    $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
                }
            }
        }

        return '~^(?:'.$res.')~s';
    }

    private static function lineToRegex(string $gitignoreLine): string
    {
        if ('' === $gitignoreLine) {
            return '$f'; // always false
        }

        $slashPos = strpos($gitignoreLine, '/');
        if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
            if (0 === $slashPos) {
                $gitignoreLine = substr($gitignoreLine, 1);
            }
            $isAbsolute = true;
        } else {
            $isAbsolute = false;
        }

        $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
        $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string {
            return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']';
        }, $regex);
        $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
        $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
        $regex = preg_replace('~\\\\\?~', '[^/]', $regex);

        return ($isAbsolute ? '' : '(?:[^/]+/)*')
            .$regex
            .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, \SplFileInfo>
 */
class PathFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        $filename = $this->current()->getRelativePathname();

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $filename = str_replace('\\', '/', $filename);
        }

        return $this->isAccepted($filename);
    }

    /**
     * Converts strings to regexp.
     *
     * PCRE patterns are left unchanged.
     *
     * Default conversion:
     *     'lorem/ipsum/dolor' ==>  'lorem\/ipsum\/dolor/'
     *
     * Use only / as directory separator (on Windows also).
     *
     * @param string $str Pattern: regexp or dirname
     *
     * @return string
     */
    protected function toRegex(string $str)
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Gitignore;

final class VcsIgnoredFilterIterator extends \FilterIterator
{
    /**
     * @var string
     */
    private $baseDir;

    /**
     * @var array<string, array{0: string, 1: string}|null>
     */
    private $gitignoreFilesCache = [];

    /**
     * @var array<string, bool>
     */
    private $ignoredPathsCache = [];

    public function __construct(\Iterator $iterator, string $baseDir)
    {
        $this->baseDir = $this->normalizePath($baseDir);

        parent::__construct($iterator);
    }

    public function accept(): bool
    {
        $file = $this->current();

        $fileRealPath = $this->normalizePath($file->getRealPath());

        return !$this->isIgnored($fileRealPath);
    }

    private function isIgnored(string $fileRealPath): bool
    {
        if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) {
            $fileRealPath .= '/';
        }

        if (isset($this->ignoredPathsCache[$fileRealPath])) {
            return $this->ignoredPathsCache[$fileRealPath];
        }

        $ignored = false;

        foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) {
            if ($this->isIgnored($parentDirectory)) {
                $ignored = true;

                // rules in ignored directories are ignored, no need to check further.
                break;
            }

            $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1);

            if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) {
                continue;
            }

            [$exclusionRegex, $inclusionRegex] = $regexps;

            if (preg_match($exclusionRegex, $fileRelativePath)) {
                $ignored = true;

                continue;
            }

            if (preg_match($inclusionRegex, $fileRelativePath)) {
                $ignored = false;
            }
        }

        return $this->ignoredPathsCache[$fileRealPath] = $ignored;
    }

    /**
     * @return list<string>
     */
    private function parentsDirectoryDownward(string $fileRealPath): array
    {
        $parentDirectories = [];

        $parentDirectory = $fileRealPath;

        while (true) {
            $newParentDirectory = \dirname($parentDirectory);

            // dirname('/') = '/'
            if ($newParentDirectory === $parentDirectory) {
                break;
            }

            $parentDirectory = $newParentDirectory;

            if (0 !== strpos($parentDirectory, $this->baseDir)) {
                break;
            }

            $parentDirectories[] = $parentDirectory;
        }

        return array_reverse($parentDirectories);
    }

    /**
     * @return array{0: string, 1: string}|null
     */
    private function readGitignoreFile(string $path): ?array
    {
        if (\array_key_exists($path, $this->gitignoreFilesCache)) {
            return $this->gitignoreFilesCache[$path];
        }

        if (!file_exists($path)) {
            return $this->gitignoreFilesCache[$path] = null;
        }

        if (!is_file($path) || !is_readable($path)) {
            throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable.");
        }

        $gitignoreFileContent = file_get_contents($path);

        return $this->gitignoreFilesCache[$path] = [
            Gitignore::toRegex($gitignoreFileContent),
            Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent),
        ];
    }

    private function normalizePath(string $path): string
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            return str_replace('\\', '/', $path);
        }

        return $path;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Comparator\DateComparator;

/**
 * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class DateRangeFilterIterator extends \FilterIterator
{
    private $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param DateComparator[]                $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        $fileinfo = $this->current();

        if (!file_exists($fileinfo->getPathname())) {
            return false;
        }

        $filedate = $fileinfo->getMTime();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filedate)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Extends the \RecursiveDirectoryIterator to support relative paths.
 *
 * @author Victor Berchet <victor@suumit.com>
 */
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
    /**
     * @var bool
     */
    private $ignoreUnreadableDirs;

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

    // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
    private $rootPath;
    private $subPath;
    private $directorySeparator = '/';

    /**
     * @throws \RuntimeException
     */
    public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
    {
        if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
            throw new \RuntimeException('This iterator only support returning current as fileinfo.');
        }

        parent::__construct($path, $flags);
        $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
        $this->rootPath = $path;
        if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
            $this->directorySeparator = \DIRECTORY_SEPARATOR;
        }
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     *
     * @return SplFileInfo
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        // the logic here avoids redoing the same work in all iterations

        if (null === $subPathname = $this->subPath) {
            $subPathname = $this->subPath = $this->getSubPath();
        }
        if ('' !== $subPathname) {
            $subPathname .= $this->directorySeparator;
        }
        $subPathname .= $this->getFilename();

        if ('/' !== $basePath = $this->rootPath) {
            $basePath .= $this->directorySeparator;
        }

        return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
    }

    /**
     * @param bool $allowLinks
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function hasChildren($allowLinks = false)
    {
        $hasChildren = parent::hasChildren($allowLinks);

        if (!$hasChildren || !$this->ignoreUnreadableDirs) {
            return $hasChildren;
        }

        try {
            parent::getChildren();

            return true;
        } catch (\UnexpectedValueException $e) {
            // If directory is unreadable and finder is set to ignore it, skip children
            return false;
        }
    }

    /**
     * @return \RecursiveDirectoryIterator
     *
     * @throws AccessDeniedException
     */
    #[\ReturnTypeWillChange]
    public function getChildren()
    {
        try {
            $children = parent::getChildren();

            if ($children instanceof self) {
                // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
                $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;

                // performance optimization to avoid redoing the same work in all children
                $children->rewindable = &$this->rewindable;
                $children->rootPath = $this->rootPath;
            }

            return $children;
        } catch (\UnexpectedValueException $e) {
            throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * Do nothing for non rewindable stream.
     *
     * @return void
     */
    #[\ReturnTypeWillChange]
    public function rewind()
    {
        if (false === $this->isRewindable()) {
            return;
        }

        parent::rewind();
    }

    /**
     * Checks if the stream is rewindable.
     *
     * @return bool
     */
    public function isRewindable()
    {
        if (null !== $this->rewindable) {
            return $this->rewindable;
        }

        if (false !== $stream = @opendir($this->getPath())) {
            $infos = stream_get_meta_data($stream);
            closedir($stream);

            if ($infos['seekable']) {
                return $this->rewindable = true;
            }
        }

        return $this->rewindable = false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, \SplFileInfo>
 */
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        if (!$this->matchRegexps && !$this->noMatchRegexps) {
            return true;
        }

        $fileinfo = $this->current();

        if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
            return false;
        }

        $content = $fileinfo->getContents();
        if (!$content) {
            return false;
        }

        return $this->isAccepted($content);
    }

    /**
     * Converts string to regexp if necessary.
     *
     * @param string $str Pattern: string or regexp
     *
     * @return string
     */
    protected function toRegex(string $str)
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FileTypeFilterIterator only keeps files, directories, or both.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class FileTypeFilterIterator extends \FilterIterator
{
    public const ONLY_FILES = 1;
    public const ONLY_DIRECTORIES = 2;

    private $mode;

    /**
     * @param \Iterator $iterator The Iterator to filter
     * @param int       $mode     The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
     */
    public function __construct(\Iterator $iterator, int $mode)
    {
        $this->mode = $mode;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        $fileinfo = $this->current();
        if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
            return false;
        } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Comparator\NumberComparator;

/**
 * SizeRangeFilterIterator filters out files that are not in the given size range.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class SizeRangeFilterIterator extends \FilterIterator
{
    private $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param NumberComparator[]              $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        $fileinfo = $this->current();
        if (!$fileinfo->isFile()) {
            return true;
        }

        $filesize = $fileinfo->getSize();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filesize)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
    protected $matchRegexps = [];
    protected $noMatchRegexps = [];

    /**
     * @param \Iterator $iterator        The Iterator to filter
     * @param string[]  $matchPatterns   An array of patterns that need to match
     * @param string[]  $noMatchPatterns An array of patterns that need to not match
     */
    public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
    {
        foreach ($matchPatterns as $pattern) {
            $this->matchRegexps[] = $this->toRegex($pattern);
        }

        foreach ($noMatchPatterns as $pattern) {
            $this->noMatchRegexps[] = $this->toRegex($pattern);
        }

        parent::__construct($iterator);
    }

    /**
     * Checks whether the string is accepted by the regex filters.
     *
     * If there is no regexps defined in the class, this method will accept the string.
     * Such case can be handled by child classes before calling the method if they want to
     * apply a different behavior.
     *
     * @return bool
     */
    protected function isAccepted(string $string)
    {
        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $string)) {
                return false;
            }
        }

        // should at least match one rule
        if ($this->matchRegexps) {
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $string)) {
                    return true;
                }
            }

            return false;
        }

        // If there is no match rules, the file is accepted
        return true;
    }

    /**
     * Checks whether the string is a regex.
     *
     * @return bool
     */
    protected function isRegex(string $str)
    {
        if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
            $start = substr($m[1], 0, 1);
            $end = substr($m[1], -1);

            if ($start === $end) {
                return !preg_match('/[*?[:alnum:] \\\\]/', $start);
            }

            foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
                if ($start === $delimiters[0] && $end === $delimiters[1]) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Converts string into regexp.
     *
     * @return string
     */
    abstract protected function toRegex(string $str);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * @author Jérémy Derussé <jeremy@derusse.com>
 *
 * @internal
 */
class LazyIterator implements \IteratorAggregate
{
    private $iteratorFactory;

    public function __construct(callable $iteratorFactory)
    {
        $this->iteratorFactory = $iteratorFactory;
    }

    public function getIterator(): \Traversable
    {
        yield from ($this->iteratorFactory)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * CustomFilterIterator filters files by applying anonymous functions.
 *
 * The anonymous function receives a \SplFileInfo and must return false
 * to remove files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class CustomFilterIterator extends \FilterIterator
{
    private $filters = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
     * @param callable[]                      $filters  An array of PHP callbacks
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(\Iterator $iterator, array $filters)
    {
        foreach ($filters as $filter) {
            if (!\is_callable($filter)) {
                throw new \InvalidArgumentException('Invalid PHP callback.');
            }
        }
        $this->filters = $filters;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        $fileinfo = $this->current();

        foreach ($this->filters as $filter) {
            if (false === $filter($fileinfo)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * SortableIterator applies a sort on a given Iterator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, \SplFileInfo>
 */
class SortableIterator implements \IteratorAggregate
{
    public const SORT_BY_NONE = 0;
    public const SORT_BY_NAME = 1;
    public const SORT_BY_TYPE = 2;
    public const SORT_BY_ACCESSED_TIME = 3;
    public const SORT_BY_CHANGED_TIME = 4;
    public const SORT_BY_MODIFIED_TIME = 5;
    public const SORT_BY_NAME_NATURAL = 6;

    private $iterator;
    private $sort;

    /**
     * @param \Traversable<string, \SplFileInfo> $iterator
     * @param int|callable                       $sort     The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
    {
        $this->iterator = $iterator;
        $order = $reverseOrder ? -1 : 1;

        if (self::SORT_BY_NAME === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_NAME_NATURAL === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_TYPE === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                if ($a->isDir() && $b->isFile()) {
                    return -$order;
                } elseif ($a->isFile() && $b->isDir()) {
                    return $order;
                }

                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                return $order * ($a->getATime() - $b->getATime());
            };
        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                return $order * ($a->getCTime() - $b->getCTime());
            };
        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                return $order * ($a->getMTime() - $b->getMTime());
            };
        } elseif (self::SORT_BY_NONE === $sort) {
            $this->sort = $order;
        } elseif (\is_callable($sort)) {
            $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
        } else {
            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
        }
    }

    /**
     * @return \Traversable<string, \SplFileInfo>
     */
    #[\ReturnTypeWillChange]
    public function getIterator()
    {
        if (1 === $this->sort) {
            return $this->iterator;
        }

        $array = iterator_to_array($this->iterator, true);

        if (-1 === $this->sort) {
            $array = array_reverse($array);
        } else {
            uasort($array, $this->sort);
        }

        return new \ArrayIterator($array);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Glob;

/**
 * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends MultiplePcreFilterIterator<string, \SplFileInfo>
 */
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        return $this->isAccepted($this->current()->getFilename());
    }

    /**
     * Converts glob to regexp.
     *
     * PCRE patterns are left unchanged.
     * Glob strings are transformed with Glob::toRegex().
     *
     * @param string $str Pattern: glob or regexp
     *
     * @return string
     */
    protected function toRegex(string $str)
    {
        return $this->isRegex($str) ? $str : Glob::toRegex($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * DepthRangeFilterIterator limits the directory depth.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
class DepthRangeFilterIterator extends \FilterIterator
{
    private $minDepth = 0;

    /**
     * @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter
     * @param int                                                          $minDepth The min depth
     * @param int                                                          $maxDepth The max depth
     */
    public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
    {
        $this->minDepth = $minDepth;
        $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        return $this->getInnerIterator()->getDepth() >= $this->minDepth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 * @implements \RecursiveIterator<string, \SplFileInfo>
 */
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
    private $iterator;
    private $isRecursive;
    private $excludedDirs = [];
    private $excludedPattern;

    /**
     * @param \Iterator $iterator    The Iterator to filter
     * @param string[]  $directories An array of directories to exclude
     */
    public function __construct(\Iterator $iterator, array $directories)
    {
        $this->iterator = $iterator;
        $this->isRecursive = $iterator instanceof \RecursiveIterator;
        $patterns = [];
        foreach ($directories as $directory) {
            $directory = rtrim($directory, '/');
            if (!$this->isRecursive || str_contains($directory, '/')) {
                $patterns[] = preg_quote($directory, '#');
            } else {
                $this->excludedDirs[$directory] = true;
            }
        }
        if ($patterns) {
            $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
        }

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     *
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function accept()
    {
        if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
            return false;
        }

        if ($this->excludedPattern) {
            $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
            $path = str_replace('\\', '/', $path);

            return !preg_match($this->excludedPattern, $path);
        }

        return true;
    }

    /**
     * @return bool
     */
    #[\ReturnTypeWillChange]
    public function hasChildren()
    {
        return $this->isRecursive && $this->iterator->hasChildren();
    }

    /**
     * @return self
     */
    #[\ReturnTypeWillChange]
    public function getChildren()
    {
        $children = new self($this->iterator->getChildren(), []);
        $children->excludedDirs = $this->excludedDirs;
        $children->excludedPattern = $this->excludedPattern;

        return $children;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 */
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

/**
 * @author Andreas Erhard <andreas.erhard@i-med.ac.at>
 */
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\LazyIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;

/**
 * Finder allows to build rules to find files and directories.
 *
 * It is a thin wrapper around several specialized iterator classes.
 *
 * All rules may be invoked several times.
 *
 * All methods return the current Finder object to allow chaining:
 *
 *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, SplFileInfo>
 */
class Finder implements \IteratorAggregate, \Countable
{
    public const IGNORE_VCS_FILES = 1;
    public const IGNORE_DOT_FILES = 2;
    public const IGNORE_VCS_IGNORED_FILES = 4;

    private $mode = 0;
    private $names = [];
    private $notNames = [];
    private $exclude = [];
    private $filters = [];
    private $depths = [];
    private $sizes = [];
    private $followLinks = false;
    private $reverseSorting = false;
    private $sort = false;
    private $ignore = 0;
    private $dirs = [];
    private $dates = [];
    private $iterators = [];
    private $contains = [];
    private $notContains = [];
    private $paths = [];
    private $notPaths = [];
    private $ignoreUnreadableDirs = false;

    private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];

    public function __construct()
    {
        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
    }

    /**
     * Creates a new Finder.
     *
     * @return static
     */
    public static function create()
    {
        return new static();
    }

    /**
     * Restricts the matching to directories only.
     *
     * @return $this
     */
    public function directories()
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;

        return $this;
    }

    /**
     * Restricts the matching to files only.
     *
     * @return $this
     */
    public function files()
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;

        return $this;
    }

    /**
     * Adds tests for the directory depth.
     *
     * Usage:
     *
     *     $finder->depth('> 1') // the Finder will start matching at level 1.
     *     $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
     *     $finder->depth(['>= 1', '< 3'])
     *
     * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
     *
     * @return $this
     *
     * @see DepthRangeFilterIterator
     * @see NumberComparator
     */
    public function depth($levels)
    {
        foreach ((array) $levels as $level) {
            $this->depths[] = new Comparator\NumberComparator($level);
        }

        return $this;
    }

    /**
     * Adds tests for file dates (last modified).
     *
     * The date must be something that strtotime() is able to parse:
     *
     *     $finder->date('since yesterday');
     *     $finder->date('until 2 days ago');
     *     $finder->date('> now - 2 hours');
     *     $finder->date('>= 2005-10-15');
     *     $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
     *
     * @param string|string[] $dates A date range string or an array of date ranges
     *
     * @return $this
     *
     * @see strtotime
     * @see DateRangeFilterIterator
     * @see DateComparator
     */
    public function date($dates)
    {
        foreach ((array) $dates as $date) {
            $this->dates[] = new Comparator\DateComparator($date);
        }

        return $this;
    }

    /**
     * Adds rules that files must match.
     *
     * You can use patterns (delimited with / sign), globs or simple strings.
     *
     *     $finder->name('*.php')
     *     $finder->name('/\.php$/') // same as above
     *     $finder->name('test.php')
     *     $finder->name(['test.py', 'test.php'])
     *
     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function name($patterns)
    {
        $this->names = array_merge($this->names, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that files must not match.
     *
     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notName($patterns)
    {
        $this->notNames = array_merge($this->notNames, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->contains('Lorem ipsum')
     *     $finder->contains('/Lorem ipsum/i')
     *     $finder->contains(['dolor', '/ipsum/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function contains($patterns)
    {
        $this->contains = array_merge($this->contains, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must not match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->notContains('Lorem ipsum')
     *     $finder->notContains('/Lorem ipsum/i')
     *     $finder->notContains(['lorem', '/dolor/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function notContains($patterns)
    {
        $this->notContains = array_merge($this->notContains, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $finder->path('some/special/dir')
     *     $finder->path('/some\/special\/dir/') // same as above
     *     $finder->path(['some dir', 'another/dir'])
     *
     * Use only / as dirname separator.
     *
     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function path($patterns)
    {
        $this->paths = array_merge($this->paths, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must not match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $finder->notPath('some/special/dir')
     *     $finder->notPath('/some\/special\/dir/') // same as above
     *     $finder->notPath(['some/file.txt', 'another/file.log'])
     *
     * Use only / as dirname separator.
     *
     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notPath($patterns)
    {
        $this->notPaths = array_merge($this->notPaths, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests for file sizes.
     *
     *     $finder->size('> 10K');
     *     $finder->size('<= 1Ki');
     *     $finder->size(4);
     *     $finder->size(['> 10K', '< 20K'])
     *
     * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
     *
     * @return $this
     *
     * @see SizeRangeFilterIterator
     * @see NumberComparator
     */
    public function size($sizes)
    {
        foreach ((array) $sizes as $size) {
            $this->sizes[] = new Comparator\NumberComparator($size);
        }

        return $this;
    }

    /**
     * Excludes directories.
     *
     * Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
     *
     *     $finder->in(__DIR__)->exclude('ruby');
     *
     * @param string|array $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function exclude($dirs)
    {
        $this->exclude = array_merge($this->exclude, (array) $dirs);

        return $this;
    }

    /**
     * Excludes "hidden" directories and files (starting with a dot).
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreDotFiles(bool $ignoreDotFiles)
    {
        if ($ignoreDotFiles) {
            $this->ignore |= static::IGNORE_DOT_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_DOT_FILES;
        }

        return $this;
    }

    /**
     * Forces the finder to ignore version control directories.
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreVCS(bool $ignoreVCS)
    {
        if ($ignoreVCS) {
            $this->ignore |= static::IGNORE_VCS_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_FILES;
        }

        return $this;
    }

    /**
     * Forces Finder to obey .gitignore and ignore files based on rules listed there.
     *
     * This option is disabled by default.
     *
     * @return $this
     */
    public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
    {
        if ($ignoreVCSIgnored) {
            $this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
        }

        return $this;
    }

    /**
     * Adds VCS patterns.
     *
     * @see ignoreVCS()
     *
     * @param string|string[] $pattern VCS patterns to ignore
     */
    public static function addVCSPattern($pattern)
    {
        foreach ((array) $pattern as $p) {
            self::$vcsPatterns[] = $p;
        }

        self::$vcsPatterns = array_unique(self::$vcsPatterns);
    }

    /**
     * Sorts files and directories by an anonymous function.
     *
     * The anonymous function receives two \SplFileInfo instances to compare.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sort(\Closure $closure)
    {
        $this->sort = $closure;

        return $this;
    }

    /**
     * Sorts files and directories by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByName(bool $useNaturalSort = false)
    {
        $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;

        return $this;
    }

    /**
     * Sorts files and directories by type (directories before files), then by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByType()
    {
        $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;

        return $this;
    }

    /**
     * Sorts files and directories by the last accessed time.
     *
     * This is the time that the file was last accessed, read or written to.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByAccessedTime()
    {
        $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;

        return $this;
    }

    /**
     * Reverses the sorting.
     *
     * @return $this
     */
    public function reverseSorting()
    {
        $this->reverseSorting = true;

        return $this;
    }

    /**
     * Sorts files and directories by the last inode changed time.
     *
     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
     *
     * On Windows, since inode is not available, changed time is actually the file creation time.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByChangedTime()
    {
        $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;

        return $this;
    }

    /**
     * Sorts files and directories by the last modified time.
     *
     * This is the last time the actual contents of the file were last modified.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByModifiedTime()
    {
        $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;

        return $this;
    }

    /**
     * Filters the iterator with an anonymous function.
     *
     * The anonymous function receives a \SplFileInfo and must return false
     * to remove files.
     *
     * @return $this
     *
     * @see CustomFilterIterator
     */
    public function filter(\Closure $closure)
    {
        $this->filters[] = $closure;

        return $this;
    }

    /**
     * Forces the following of symlinks.
     *
     * @return $this
     */
    public function followLinks()
    {
        $this->followLinks = true;

        return $this;
    }

    /**
     * Tells finder to ignore unreadable directories.
     *
     * By default, scanning unreadable directories content throws an AccessDeniedException.
     *
     * @return $this
     */
    public function ignoreUnreadableDirs(bool $ignore = true)
    {
        $this->ignoreUnreadableDirs = $ignore;

        return $this;
    }

    /**
     * Searches files and directories which match defined rules.
     *
     * @param string|string[] $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @throws DirectoryNotFoundException if one of the directories does not exist
     */
    public function in($dirs)
    {
        $resolvedDirs = [];

        foreach ((array) $dirs as $dir) {
            if (is_dir($dir)) {
                $resolvedDirs[] = [$this->normalizeDir($dir)];
            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
                sort($glob);
                $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob);
            } else {
                throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
            }
        }

        $this->dirs = array_merge($this->dirs, ...$resolvedDirs);

        return $this;
    }

    /**
     * Returns an Iterator for the current Finder configuration.
     *
     * This method implements the IteratorAggregate interface.
     *
     * @return \Iterator<string, SplFileInfo>
     *
     * @throws \LogicException if the in() method has not been called
     */
    #[\ReturnTypeWillChange]
    public function getIterator()
    {
        if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
        }

        if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
            $iterator = $this->searchInDirectory($this->dirs[0]);

            if ($this->sort || $this->reverseSorting) {
                $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
            }

            return $iterator;
        }

        $iterator = new \AppendIterator();
        foreach ($this->dirs as $dir) {
            $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) {
                return $this->searchInDirectory($dir);
            })));
        }

        foreach ($this->iterators as $it) {
            $iterator->append($it);
        }

        if ($this->sort || $this->reverseSorting) {
            $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
        }

        return $iterator;
    }

    /**
     * Appends an existing set of files/directories to the finder.
     *
     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
     *
     * @return $this
     *
     * @throws \InvalidArgumentException when the given argument is not iterable
     */
    public function append(iterable $iterator)
    {
        if ($iterator instanceof \IteratorAggregate) {
            $this->iterators[] = $iterator->getIterator();
        } elseif ($iterator instanceof \Iterator) {
            $this->iterators[] = $iterator;
        } elseif (is_iterable($iterator)) {
            $it = new \ArrayIterator();
            foreach ($iterator as $file) {
                $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
                $it[$file->getPathname()] = $file;
            }
            $this->iterators[] = $it;
        } else {
            throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
        }

        return $this;
    }

    /**
     * Check if any results were found.
     *
     * @return bool
     */
    public function hasResults()
    {
        foreach ($this->getIterator() as $_) {
            return true;
        }

        return false;
    }

    /**
     * Counts all the results collected by the iterators.
     *
     * @return int
     */
    #[\ReturnTypeWillChange]
    public function count()
    {
        return iterator_count($this->getIterator());
    }

    private function searchInDirectory(string $dir): \Iterator
    {
        $exclude = $this->exclude;
        $notPaths = $this->notPaths;

        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
            $exclude = array_merge($exclude, self::$vcsPatterns);
        }

        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
            $notPaths[] = '#(^|/)\..+(/|$)#';
        }

        $minDepth = 0;
        $maxDepth = \PHP_INT_MAX;

        foreach ($this->depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $maxDepth = $comparator->getTarget();
                    break;
                default:
                    $minDepth = $maxDepth = $comparator->getTarget();
            }
        }

        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

        if ($exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
        }

        if ($this->mode) {
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
        }

        if ($this->names || $this->notNames) {
            $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
        }

        if ($this->contains || $this->notContains) {
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->sizes) {
            $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
        }

        if ($this->dates) {
            $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
        }

        if ($this->filters) {
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
        }

        if ($this->paths || $notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
        }

        if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
            $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
        }

        return $iterator;
    }

    /**
     * Normalizes given directory names by removing trailing slashes.
     *
     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
     */
    private function normalizeDir(string $dir): string
    {
        if ('/' === $dir) {
            return $dir;
        }

        $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);

        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
            $dir .= '/';
        }

        return $dir;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * DateCompare compiles date comparisons.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DateComparator extends Comparator
{
    /**
     * @param string $test A comparison string
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    public function __construct(string $test)
    {
        if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
            throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
        }

        try {
            $date = new \DateTime($matches[2]);
            $target = $date->format('U');
        } catch (\Exception $e) {
            throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
        }

        $operator = $matches[1] ?? '==';
        if ('since' === $operator || 'after' === $operator) {
            $operator = '>';
        }

        if ('until' === $operator || 'before' === $operator) {
            $operator = '<';
        }

        parent::__construct($target, $operator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Comparator
{
    private $target;
    private $operator = '==';

    public function __construct(string $target = null, string $operator = '==')
    {
        if (null === $target) {
            trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__);
        }

        $this->target = $target;
        $this->doSetOperator($operator);
    }

    /**
     * Gets the target value.
     *
     * @return string
     */
    public function getTarget()
    {
        if (null === $this->target) {
            trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
        }

        return $this->target;
    }

    /**
     * @deprecated set the target via the constructor instead
     */
    public function setTarget(string $target)
    {
        trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__);

        $this->target = $target;
    }

    /**
     * Gets the comparison operator.
     *
     * @return string
     */
    public function getOperator()
    {
        return $this->operator;
    }

    /**
     * Sets the comparison operator.
     *
     * @throws \InvalidArgumentException
     *
     * @deprecated set the operator via the constructor instead
     */
    public function setOperator(string $operator)
    {
        trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__);

        $this->doSetOperator('' === $operator ? '==' : $operator);
    }

    /**
     * Tests against the target.
     *
     * @param mixed $test A test value
     *
     * @return bool
     */
    public function test($test)
    {
        if (null === $this->target) {
            trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
        }

        switch ($this->operator) {
            case '>':
                return $test > $this->target;
            case '>=':
                return $test >= $this->target;
            case '<':
                return $test < $this->target;
            case '<=':
                return $test <= $this->target;
            case '!=':
                return $test != $this->target;
        }

        return $test == $this->target;
    }

    private function doSetOperator(string $operator): void
    {
        if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
            throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
        }

        $this->operator = $operator;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * NumberComparator compiles a simple comparison to an anonymous
 * subroutine, which you can call with a value to be tested again.
 *
 * Now this would be very pointless, if NumberCompare didn't understand
 * magnitudes.
 *
 * The target value may use magnitudes of kilobytes (k, ki),
 * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
 * with an i use the appropriate 2**n version in accordance with the
 * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
 *
 * Based on the Perl Number::Compare module.
 *
 * @author    Fabien Potencier <fabien@symfony.com> PHP port
 * @author    Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright 2002 Richard Clamp <richardc@unixbeard.net>
 *
 * @see http://physics.nist.gov/cuu/Units/binary.html
 */
class NumberComparator extends Comparator
{
    /**
     * @param string|int $test A comparison string or an integer
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    public function __construct(?string $test)
    {
        if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
            throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null'));
        }

        $target = $matches[2];
        if (!is_numeric($target)) {
            throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
        }
        if (isset($matches[3])) {
            // magnitude
            switch (strtolower($matches[3])) {
                case 'k':
                    $target *= 1000;
                    break;
                case 'ki':
                    $target *= 1024;
                    break;
                case 'm':
                    $target *= 1000000;
                    break;
                case 'mi':
                    $target *= 1024 * 1024;
                    break;
                case 'g':
                    $target *= 1000000000;
                    break;
                case 'gi':
                    $target *= 1024 * 1024 * 1024;
                    break;
            }
        }

        parent::__construct($target, $matches[1] ?: '==');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Extends \SplFileInfo to support relative paths.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SplFileInfo extends \SplFileInfo
{
    private $relativePath;
    private $relativePathname;

    /**
     * @param string $file             The file name
     * @param string $relativePath     The relative path
     * @param string $relativePathname The relative path name
     */
    public function __construct(string $file, string $relativePath, string $relativePathname)
    {
        parent::__construct($file);
        $this->relativePath = $relativePath;
        $this->relativePathname = $relativePathname;
    }

    /**
     * Returns the relative path.
     *
     * This path does not contain the file name.
     *
     * @return string
     */
    public function getRelativePath()
    {
        return $this->relativePath;
    }

    /**
     * Returns the relative path name.
     *
     * This path contains the file name.
     *
     * @return string
     */
    public function getRelativePathname()
    {
        return $this->relativePathname;
    }

    public function getFilenameWithoutExtension(): string
    {
        $filename = $this->getFilename();

        return pathinfo($filename, \PATHINFO_FILENAME);
    }

    /**
     * Returns the contents of the file.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    public function getContents()
    {
        set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
        try {
            $content = file_get_contents($this->getPathname());
        } finally {
            restore_error_handler();
        }
        if (false === $content) {
            throw new \RuntimeException($error);
        }

        return $content;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Glob matches globbing patterns against text.
 *
 *     if match_glob("foo.*", "foo.bar") echo "matched\n";
 *
 *     // prints foo.bar and foo.baz
 *     $regex = glob_to_regex("foo.*");
 *     for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
 *     {
 *         if (/$regex/) echo "matched: $car\n";
 *     }
 *
 * Glob implements glob(3) style matching that can be used to match
 * against text, rather than fetching names from a filesystem.
 *
 * Based on the Perl Text::Glob module.
 *
 * @author Fabien Potencier <fabien@symfony.com> PHP port
 * @author     Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright  2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
 */
class Glob
{
    /**
     * Returns a regexp which is the equivalent of the glob pattern.
     *
     * @return string
     */
    public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#')
    {
        $firstByte = true;
        $escaping = false;
        $inCurlies = 0;
        $regex = '';
        $sizeGlob = \strlen($glob);
        for ($i = 0; $i < $sizeGlob; ++$i) {
            $car = $glob[$i];
            if ($firstByte && $strictLeadingDot && '.' !== $car) {
                $regex .= '(?=[^\.])';
            }

            $firstByte = '/' === $car;

            if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
                $car = '[^/]++/';
                if (!isset($glob[$i + 3])) {
                    $car .= '?';
                }

                if ($strictLeadingDot) {
                    $car = '(?=[^\.])'.$car;
                }

                $car = '/(?:'.$car.')*';
                $i += 2 + isset($glob[$i + 3]);

                if ('/' === $delimiter) {
                    $car = str_replace('/', '\\/', $car);
                }
            }

            if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
                $regex .= "\\$car";
            } elseif ('*' === $car) {
                $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
            } elseif ('?' === $car) {
                $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
            } elseif ('{' === $car) {
                $regex .= $escaping ? '\\{' : '(';
                if (!$escaping) {
                    ++$inCurlies;
                }
            } elseif ('}' === $car && $inCurlies) {
                $regex .= $escaping ? '}' : ')';
                if (!$escaping) {
                    --$inCurlies;
                }
            } elseif (',' === $car && $inCurlies) {
                $regex .= $escaping ? ',' : '|';
            } elseif ('\\' === $car) {
                if ($escaping) {
                    $regex .= '\\\\';
                    $escaping = false;
                } else {
                    $escaping = true;
                }

                continue;
            } else {
                $regex .= $car;
            }
            $escaping = false;
        }

        return $delimiter.'^'.$regex.'$'.$delimiter;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php73;

/**
 * @author Gabriel Caruso <carusogabriel34@gmail.com>
 * @author Ion Bazan <ion.bazan@gmail.com>
 *
 * @internal
 */
final class Php73
{
    public static $startAt = 1533462603;

    /**
     * @param bool $asNum
     *
     * @return array|float|int
     */
    public static function hrtime($asNum = false)
    {
        $ns = microtime(false);
        $s = substr($ns, 11) - self::$startAt;
        $ns = 1E9 * (float) $ns;

        if ($asNum) {
            $ns += $s * 1E9;

            return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
        }

        return [$s, (int) $ns];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class JsonException extends Exception
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php73 as p;

if (\PHP_VERSION_ID >= 70300) {
    return;
}

if (!function_exists('is_countable')) {
    function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; }
}
if (!function_exists('hrtime')) {
    require_once __DIR__.'/Php73.php';
    p\Php73::$startAt = (int) microtime(true);
    function hrtime($as_number = false) { return p\Php73::hrtime($as_number); }
}
if (!function_exists('array_key_first')) {
    function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
}
if (!function_exists('array_key_last')) {
    function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alnum($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a letter, FALSE otherwise.
     *
     * @see https://php.net/ctype-alpha
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alpha($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
     *
     * @see https://php.net/ctype-cntrl
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_cntrl($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
    }

    /**
     * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-digit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_digit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
     *
     * @see https://php.net/ctype-graph
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_graph($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a lowercase letter.
     *
     * @see https://php.net/ctype-lower
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_lower($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
     *
     * @see https://php.net/ctype-print
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_print($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
     *
     * @see https://php.net/ctype-punct
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_punct($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
     *
     * @see https://php.net/ctype-space
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_space($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
    }

    /**
     * Returns TRUE if every character in text is an uppercase letter.
     *
     * @see https://php.net/ctype-upper
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_upper($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
     *
     * @see https://php.net/ctype-xdigit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_xdigit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
    }

    /**
     * Converts integers to their char versions according to normal ctype behaviour, if needed.
     *
     * If an integer between -128 and 255 inclusive is provided,
     * it is interpreted as the ASCII value of a single character
     * (negative values have 256 added in order to allow characters in the Extended ASCII range).
     * Any other integer is interpreted as a string containing the decimal digits of the integer.
     *
     * @param string|int $int
     *
     * @return mixed
     */
    private static function convert_int_to_char_for_ctype($int)
    {
        if (!\is_int($int)) {
            return $int;
        }

        if ($int < -128 || $int > 255) {
            return (string) $int;
        }

        if ($int < 0) {
            $int += 256;
        }

        return \chr($int);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Ctype as p;

if (!function_exists('ctype_alnum')) {
    function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
    function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
    function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
    function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
    function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
    function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
    function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
    function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
    function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
    function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
    function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Ctype as p;

if (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\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>
 *
 * @implements \IteratorAggregate<string, string>
 */
class Process implements \IteratorAggregate
{
    public const ERR = 'err';
    public const OUT = 'out';

    public const STATUS_READY = 'ready';
    public const STATUS_STARTED = 'started';
    public const STATUS_TERMINATED = 'terminated';

    public const STDIN = 0;
    public const STDOUT = 1;
    public const STDERR = 2;

    // Timeout Precision in seconds.
    public const TIMEOUT_PRECISION = 0.2;

    public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
    public const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
    public const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
    public const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating

    private $callback;
    private $hasCallback = false;
    private $commandline;
    private $cwd;
    private $env;
    private $input;
    private $starttime;
    private $lastOutputTime;
    private $timeout;
    private $idleTimeout;
    private $exitcode;
    private $fallbackStatus = [];
    private $processInformation;
    private $outputDisabled = false;
    private $stdout;
    private $stderr;
    private $process;
    private $status = self::STATUS_READY;
    private $incrementalOutputOffset = 0;
    private $incrementalErrorOutputOffset = 0;
    private $tty = false;
    private $pty;
    private $options = ['suppress_errors' => true, 'bypass_shell' => true];

    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 array          $command The command to run and its arguments listed as separate entries
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @throws LogicException When proc_open is not installed
     */
    public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
    {
        if (!\function_exists('proc_open')) {
            throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
        }

        $this->commandline = $command;
        $this->cwd = $cwd;

        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
        // @see : https://bugs.php.net/51800
        // @see : https://bugs.php.net/50524
        if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
            $this->cwd = getcwd();
        }
        if (null !== $env) {
            $this->setEnv($env);
        }

        $this->setInput($input);
        $this->setTimeout($timeout);
        $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
        $this->pty = false;
    }

    /**
     * Creates a Process instance as a command-line to be run in a shell wrapper.
     *
     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
     * shell wrapper and not to your commands.
     *
     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
     * This will save escaping values, which is not portable nor secure anyway:
     *
     *   $process = Process::fromShellCommandline('my_command "$MY_VAR"');
     *   $process->run(null, ['MY_VAR' => $theValue]);
     *
     * @param string         $command The command line to pass to the shell of the OS
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @return static
     *
     * @throws LogicException When proc_open is not installed
     */
    public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
    {
        $process = new static([], $cwd, $env, $input, $timeout);
        $process->commandline = $command;

        return $process;
    }

    /**
     * @return array
     */
    public function __sleep()
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __wakeup()
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        if ($this->options['create_new_console'] ?? false) {
            $this->processPipes->close();
        } else {
            $this->stop(0);
        }
    }

    public function __clone()
    {
        $this->resetProcessData();
    }

    /**
     * Runs the process.
     *
     * The callback receives the type of output (out or err) and
     * some bytes from the output in real-time. It allows to have feedback
     * from the independent process during execution.
     *
     * The STDOUT and STDERR are also available after the process is finished
     * via the getOutput() and getErrorOutput() methods.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return int The exit status code
     *
     * @throws RuntimeException         When process can't be launched
     * @throws RuntimeException         When process is already running
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           In case a callback is provided and output has been disabled
     *
     * @final
     */
    public function run(callable $callback = null, array $env = []): int
    {
        $this->start($callback, $env);

        return $this->wait();
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @return $this
     *
     * @throws ProcessFailedException if the process didn't terminate successfully
     *
     * @final
     */
    public function mustRun(callable $callback = null, array $env = []): self
    {
        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.');
        }

        $this->resetProcessData();
        $this->starttime = $this->lastOutputTime = microtime(true);
        $this->callback = $this->buildCallback($callback);
        $this->hasCallback = null !== $callback;
        $descriptors = $this->getDescriptors();

        if ($this->env) {
            $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
        }

        $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();

        if (\is_array($commandline = $this->commandline)) {
            $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));

            if ('\\' !== \DIRECTORY_SEPARATOR) {
                // exec is mandatory to deal with sending a signal to the process
                $commandline = 'exec '.$commandline;
            }
        } else {
            $commandline = $this->replacePlaceholders($commandline, $env);
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
        } elseif (!$this->useFileHandles && $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');
        }

        $envPairs = [];
        foreach ($env as $k => $v) {
            if (false !== $v && 'argc' !== $k && 'argv' !== $k) {
                $envPairs[] = $k.'='.$v;
            }
        }

        if (!is_dir($this->cwd)) {
            throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
        }

        $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
     */
    public function restart(callable $callback = null, array $env = []): self
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }

        $process = clone $this;
        $process->start($callback, $env);

        return $process;
    }

    /**
     * Waits for the process to terminate.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A valid PHP callback
     *
     * @return int The exitcode of the process
     *
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           When process is not yet started
     */
    public function wait(callable $callback = null)
    {
        $this->requireProcessIsStarted(__FUNCTION__);

        $this->updateStatus(false);

        if (null !== $callback) {
            if (!$this->processPipes->haveReadSupport()) {
                $this->stop(0);
                throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
            }
            $this->callback = $this->buildCallback($callback);
        }

        do {
            $this->checkTimeout();
            $running = '\\' === \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 ProcessSignaledException($this);
        }

        return $this->exitcode;
    }

    /**
     * Waits until the callback returns true.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @throws RuntimeException         When process timed out
     * @throws LogicException           When process is not yet started
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function waitUntil(callable $callback): bool
    {
        $this->requireProcessIsStarted(__FUNCTION__);
        $this->updateStatus(false);

        if (!$this->processPipes->haveReadSupport()) {
            $this->stop(0);
            throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
        }
        $callback = $this->buildCallback($callback);

        $ready = false;
        while (true) {
            $this->checkTimeout();
            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
            $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);

            foreach ($output as $type => $data) {
                if (3 !== $type) {
                    $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
                } elseif (!isset($this->fallbackStatus['signaled'])) {
                    $this->fallbackStatus['exitcode'] = (int) $data;
                }
            }
            if ($ready) {
                return true;
            }
            if (!$running) {
                return false;
            }

            usleep(1000);
        }
    }

    /**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return int|null The process id if running, null otherwise
     */
    public function getPid()
    {
        return $this->isRunning() ? $this->processInformation['pid'] : null;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
     *
     * @return $this
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    public function signal(int $signal)
    {
        $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 cannot be disabled while an idle timeout is set.');
        }

        $this->outputDisabled = true;

        return $this;
    }

    /**
     * Enables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     */
    public function enableOutput()
    {
        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
     *
     * @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
     *
     * @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<string, string>
     */
    #[\ReturnTypeWillChange]
    public function getIterator(int $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
     *
     * @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
     *
     * @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
     */
    public function getExitCode()
    {
        $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 self::$exitCodes[$exitcode] ?? 'Unknown error';
    }

    /**
     * Checks if the process ended successfully.
     *
     * @return bool
     */
    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 LogicException In case the process is not terminated
     */
    public function hasBeenSignaled()
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['signaled'];
    }

    /**
     * Returns the number of the signal that caused the child process to terminate its execution.
     *
     * It is only meaningful if hasBeenSignaled() returns true.
     *
     * @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() && -1 === $this->processInformation['termsig']) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
        }

        return $this->processInformation['termsig'];
    }

    /**
     * Returns true if the child process has been stopped by a signal.
     *
     * It always returns false on Windows.
     *
     * @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
     */
    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
     */
    public function isStarted()
    {
        return self::STATUS_READY != $this->status;
    }

    /**
     * Checks if the process is terminated.
     *
     * @return bool
     */
    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
     */
    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(float $timeout = 10, int $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
     */
    public function addOutput(string $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
     */
    public function addErrorOutput(string $line)
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stderr, 0, \SEEK_END);
        fwrite($this->stderr, $line);
        fseek($this->stderr, $this->incrementalErrorOutputOffset);
    }

    /**
     * Gets the last output time in seconds.
     *
     * @return float|null
     */
    public function getLastOutputTime(): ?float
    {
        return $this->lastOutputTime;
    }

    /**
     * Gets the command line to be executed.
     *
     * @return string
     */
    public function getCommandLine()
    {
        return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
    }

    /**
     * Gets the process timeout in seconds (max. runtime).
     *
     * @return float|null
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Gets the process idle timeout in seconds (max. time since last output).
     *
     * @return float|null
     */
    public function getIdleTimeout()
    {
        return $this->idleTimeout;
    }

    /**
     * Sets the process timeout (max. runtime) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setTimeout(?float $timeout)
    {
        $this->timeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Sets the process idle timeout (max. time since last output) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws LogicException           if the output is disabled
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setIdleTimeout(?float $timeout)
    {
        if (null !== $timeout && $this->outputDisabled) {
            throw new LogicException('Idle timeout cannot be set while the output is disabled.');
        }

        $this->idleTimeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Enables or disables the TTY mode.
     *
     * @return $this
     *
     * @throws RuntimeException In case the TTY mode is not supported
     */
    public function setTty(bool $tty)
    {
        if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
            throw new RuntimeException('TTY mode is not supported on Windows platform.');
        }

        if ($tty && !self::isTtySupported()) {
            throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
        }

        $this->tty = $tty;

        return $this;
    }

    /**
     * Checks if the TTY mode is enabled.
     *
     * @return bool
     */
    public function isTty()
    {
        return $this->tty;
    }

    /**
     * Sets PTY mode.
     *
     * @return $this
     */
    public function setPty(bool $bool)
    {
        $this->pty = $bool;

        return $this;
    }

    /**
     * Returns PTY state.
     *
     * @return bool
     */
    public function isPty()
    {
        return $this->pty;
    }

    /**
     * Gets the working directory.
     *
     * @return string|null
     */
    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.
     *
     * @return $this
     */
    public function setWorkingDirectory(string $cwd)
    {
        $this->cwd = $cwd;

        return $this;
    }

    /**
     * Gets the environment variables.
     *
     * @return array
     */
    public function getEnv()
    {
        return $this->env;
    }

    /**
     * Sets the environment variables.
     *
     * @param array<string|\Stringable> $env The new environment variables
     *
     * @return $this
     */
    public function setEnv(array $env)
    {
        $this->env = $env;

        return $this;
    }

    /**
     * Gets the Process input.
     *
     * @return resource|string|\Iterator|null
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param string|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 cannot be set while the process is running.');
        }

        $this->input = ProcessUtils::validateInput(__METHOD__, $input);

        return $this;
    }

    /**
     * Performs a check between the timeout definition and the time the process started.
     *
     * In case you run a background process (with the start method), you should
     * trigger this method regularly to ensure the process timeout
     *
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
        }

        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
        }
    }

    /**
     * @throws LogicException in case process is not started
     */
    public function getStartTime(): float
    {
        if (!$this->isStarted()) {
            throw new LogicException('Start time is only available after process start.');
        }

        return $this->starttime;
    }

    /**
     * Defines options to pass to the underlying proc_open().
     *
     * @see https://php.net/proc_open for the options supported by PHP.
     *
     * Enabling the "create_new_console" option allows a subprocess to continue
     * to run after the main process exited, on both Windows and *nix
     */
    public function setOptions(array $options)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Setting options while the process is running is not possible.');
        }

        $defaultOptions = $this->options;
        $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];

        foreach ($options as $key => $value) {
            if (!\in_array($key, $existingOptions)) {
                $this->options = $defaultOptions;
                throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
            }
            $this->options[$key] = $value;
        }
    }

    /**
     * Returns whether TTY is supported on the current operating system.
     */
    public static function isTtySupported(): bool
    {
        static $isTtySupported;

        if (null === $isTtySupported) {
            $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
        }

        return $isTtySupported;
    }

    /**
     * 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.
     */
    private function getDescriptors(): array
    {
        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
     */
    protected function buildCallback(callable $callback = null)
    {
        if ($this->outputDisabled) {
            return function ($type, $data) use ($callback): bool {
                return null !== $callback && $callback($type, $data);
            };
        }

        $out = self::OUT;

        return function ($type, $data) use ($callback, $out): bool {
            if ($out == $type) {
                $this->addOutput($data);
            } else {
                $this->addErrorOutput($data);
            }

            return null !== $callback && $callback($type, $data);
        };
    }

    /**
     * Updates the status of the process, reads pipes.
     *
     * @param bool $blocking Whether to use a blocking read call
     */
    protected function updateStatus(bool $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->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')) {
            return self::$sigchild = false;
        }

        ob_start();
        phpinfo(\INFO_GENERAL);

        return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
    }

    /**
     * Reads pipes for the freshest output.
     *
     * @param string $caller   The name of the method that needs fresh outputs
     * @param bool   $blocking Whether to use blocking calls or not
     *
     * @throws LogicException in case output has been disabled or process is not started
     */
    private function readPipesForOutput(string $caller, bool $blocking = false)
    {
        if ($this->outputDisabled) {
            throw new LogicException('Output has been disabled.');
        }

        $this->requireProcessIsStarted($caller);

        $this->updateStatus($blocking);
    }

    /**
     * Validates and returns the filtered timeout.
     *
     * @throws InvalidArgumentException if the given timeout is a negative number
     */
    private function validateTimeout(?float $timeout): ?float
    {
        $timeout = (float) $timeout;

        if (0.0 === $timeout) {
            $timeout = null;
        } elseif ($timeout < 0) {
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        return $timeout;
    }

    /**
     * Reads pipes, executes callback.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close file handles or not
     */
    private function readPipes(bool $blocking, bool $close)
    {
        $result = $this->processPipes->readAndWrite($blocking, $close);

        $callback = $this->callback;
        foreach ($result as $type => $data) {
            if (3 !== $type) {
                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
            } elseif (!isset($this->fallbackStatus['signaled'])) {
                $this->fallbackStatus['exitcode'] = (int) $data;
            }
        }
    }

    /**
     * Closes process resource, closes file handles, sets the exitcode.
     *
     * @return int The exitcode
     */
    private function close(): int
    {
        $this->processPipes->close();
        if (\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->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+');
        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
        $this->process = null;
        $this->latestSignal = null;
        $this->status = self::STATUS_READY;
        $this->incrementalOutputOffset = 0;
        $this->incrementalErrorOutputOffset = 0;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
     * @param bool $throwException Whether to throw exception in case signal failed
     *
     * @return bool
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    private function doSignal(int $signal, bool $throwException): bool
    {
        if (null === $pid = $this->getPid()) {
            if ($throwException) {
                throw new LogicException('Cannot send signal on a non running process.');
            }

            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
            if ($exitCode && $this->isRunning()) {
                if ($throwException) {
                    throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
                }

                return false;
            }
        } else {
            if (!$this->isSigchildEnabled()) {
                $ok = @proc_terminate($this->process, $signal);
            } elseif (\function_exists('posix_kill')) {
                $ok = @posix_kill($pid, $signal);
            } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
                $ok = false === fgets($pipes[2]);
            }
            if (!$ok) {
                if ($throwException) {
                    throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
                }

                return false;
            }
        }

        $this->latestSignal = $signal;
        $this->fallbackStatus['signaled'] = true;
        $this->fallbackStatus['exitcode'] = -1;
        $this->fallbackStatus['termsig'] = $this->latestSignal;

        return true;
    }

    private function prepareWindowsCommandLine(string $cmd, array &$env): string
    {
        $uid = uniqid('', true);
        $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 (str_contains($value = $m[1], "\0")) {
                    $value = str_replace("\0", '?', $value);
                }
                if (false === strpbrk($value, "\"%!\n")) {
                    return '"'.$value.'"';
                }

                $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
                $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
                $var = $uid.++$varCount;

                $env[$var] = $value;

                return $varCache[$m[0]] = '!'.$var.'!';
            },
            $cmd
        );

        $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
        foreach ($this->processPipes->getFiles() as $offset => $filename) {
            $cmd .= ' '.$offset.'>"'.$filename.'"';
        }

        return $cmd;
    }

    /**
     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
     *
     * @throws LogicException if the process has not run
     */
    private function requireProcessIsStarted(string $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".
     *
     * @throws LogicException if the process is not yet terminated
     */
    private function requireProcessIsTerminated(string $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.
     */
    private function escapeArgument(?string $argument): string
    {
        if ('' === $argument || null === $argument) {
            return '""';
        }
        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }
        if (str_contains($argument, "\0")) {
            $argument = str_replace("\0", '?', $argument);
        }
        if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
            return $argument;
        }
        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);

        return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
    }

    private function replacePlaceholders(string $commandline, array $env)
    {
        return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
            if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
                throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
            }

            return $this->escapeArgument($env[$matches[1]]);
        }, $commandline);
    }

    private function getDefaultEnv(): array
    {
        $env = getenv();
        $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;

        return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

/**
 * Provides a way to continuously write to the input of a Process until the InputStream is closed.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @implements \IteratorAggregate<int, string>
 */
class InputStream implements \IteratorAggregate
{
    /** @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;
    }

    /**
     * @return \Traversable<int, string>
     */
    #[\ReturnTypeWillChange]
    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) {
                yield from $current;
            } else {
                yield $current;
            }
            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
                $this->write($onEmpty($this));
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpProcess runs a PHP script in an independent process.
 *
 *     $p = new PhpProcess('<?php echo "foo"; ?>');
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class PhpProcess extends Process
{
    /**
     * @param string      $script  The PHP script to run (as a string)
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
     */
    public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
    {
        if (null === $php) {
            $executableFinder = new PhpExecutableFinder();
            $php = $executableFinder->find(false);
            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
        }
        if ('phpdbg' === \PHP_SAPI) {
            $file = tempnam(sys_get_temp_dir(), 'dbg');
            file_put_contents($file, $script);
            register_shutdown_function('unlink', $file);
            $php[] = $file;
            $script = null;
        }

        parent::__construct($php, $cwd, $env, $script, $timeout);
    }

    /**
     * {@inheritdoc}
     */
    public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
    {
        throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
    }

    /**
     * {@inheritdoc}
     */
    public function start(callable $callback = null, array $env = [])
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback, $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process times out.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessTimedOutException extends RuntimeException
{
    public const TYPE_GENERAL = 1;
    public const TYPE_IDLE = 2;

    private $process;
    private $timeoutType;

    public function __construct(Process $process, int $timeoutType)
    {
        $this->process = $process;
        $this->timeoutType = $timeoutType;

        parent::__construct(sprintf(
            'The process "%s" exceeded the timeout of %s seconds.',
            $process->getCommandLine(),
            $this->getExceededTimeout()
        ));
    }

    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;

use Symfony\Component\Process\Process;

/**
 * Exception for failed processes.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessFailedException extends RuntimeException
{
    private $process;

    public function __construct(Process $process)
    {
        if ($process->isSuccessful()) {
            throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
        }

        $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
            $process->getCommandLine(),
            $process->getExitCode(),
            $process->getExitCodeText(),
            $process->getWorkingDirectory()
        );

        if (!$process->isOutputDisabled()) {
            $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
                $process->getOutput(),
                $process->getErrorOutput()
            );
        }

        parent::__construct($error);

        $this->process = $process;
    }

    public function getProcess()
    {
        return $this->process;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * Marker Interface for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * LogicException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * RuntimeException for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * 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 has been signaled.
 *
 * @author Sullivan Senechal <soullivaneuh@gmail.com>
 */
final class ProcessSignaledException extends RuntimeException
{
    private $process;

    public function __construct(Process $process)
    {
        $this->process = $process;

        parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
    }

    public function getProcess(): Process
    {
        return $this->process;
    }

    public function getSignal(): int
    {
        return $this->getProcess()->getTermSignal();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * 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.
     *
     * @return string|false
     */
    public function find(bool $includeArgs = true)
    {
        if ($php = getenv('PHP_BINARY')) {
            if (!is_executable($php)) {
                $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
                if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
                    if (!is_executable($php)) {
                        return false;
                    }
                } else {
                    return false;
                }
            }

            return $php;
        }

        $args = $this->findArguments();
        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';

        // PHP_BINARY return the current sapi executable
        if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', '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
     */
    public function findArguments()
    {
        $arguments = [];
        if ('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\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(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
    {
        $this->ttyMode = $ttyMode;
        $this->ptyMode = $ptyMode;
        $this->haveReadSupport = $haveReadSupport;

        parent::__construct($input);
    }

    public function __sleep(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __wakeup()
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('/dev/null', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        if ($this->ttyMode) {
            return [
                ['file', '/dev/tty', 'r'],
                ['file', '/dev/tty', 'w'],
                ['file', '/dev/tty', 'w'],
            ];
        }

        if ($this->ptyMode && Process::isPtySupported()) {
            return [
                ['pty'],
                ['pty'],
                ['pty'],
            ];
        }

        return [
            ['pipe', 'r'],
            ['pipe', 'w'], // stdout
            ['pipe', 'w'], // stderr
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFiles(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();

        $read = $e = [];
        $r = $this->pipes;
        unset($r[0]);

        // let's have a look if something changed in streams
        set_error_handler([$this, 'handleError']);
        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
            restore_error_handler();
            // if a system call has been interrupted, forget about it, let's try again
            // otherwise, an error occurred, let's reset pipes
            if (!$this->hasSystemCallBeenInterrupted()) {
                $this->pipes = [];
            }

            return $read;
        }
        restore_error_handler();

        foreach ($r as $pipe) {
            // prior PHP 5.4 the array passed to stream_select is modified and
            // lose key association, we have to find back the key
            $read[$type = array_search($pipe, $this->pipes, true)] = '';

            do {
                $data = @fread($pipe, self::CHUNK_SIZE);
                $read[$type] .= $data;
            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));

            if (!isset($read[$type][0])) {
                unset($read[$type]);
            }

            if ($close && feof($pipe)) {
                fclose($pipe);
                unset($this->pipes[$type]);
            }
        }

        return $read;
    }

    /**
     * {@inheritdoc}
     */
    public function haveReadSupport(): bool
    {
        return $this->haveReadSupport;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen(): bool
    {
        return (bool) $this->pipes;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

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.
     */
    protected function hasSystemCallBeenInterrupted(): bool
    {
        $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.
     *
     * @throws InvalidArgumentException When an input iterator yields a non supported value
     */
    protected function write(): ?array
    {
        if (!isset($this->pipes[0])) {
            return null;
        }
        $input = $this->input;

        if ($input instanceof \Iterator) {
            if (!$input->valid()) {
                $input = null;
            } elseif (\is_resource($input = $input->current())) {
                stream_set_blocking($input, 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_debug_type($this->input), get_debug_type($input)));
                    }
                    $input = (string) $input;
                }
                $this->inputBuffer = $input;
                $this->input->next();
                $input = null;
            } else {
                $input = null;
            }
        }

        $r = $e = [];
        $w = [$this->pipes[0]];

        // let's have a look if something changed in streams
        if (false === @stream_select($r, $w, $e, 0, 0)) {
            return null;
        }

        foreach ($w as $stdin) {
            if (isset($this->inputBuffer[0])) {
                $written = fwrite($stdin, $this->inputBuffer);
                $this->inputBuffer = substr($this->inputBuffer, $written);
                if (isset($this->inputBuffer[0])) {
                    return [$this->pipes[0]];
                }
            }

            if ($input) {
                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(int $type, string $msg)
    {
        $this->lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\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, bool $haveReadSupport)
    {
        $this->haveReadSupport = $haveReadSupport;

        if ($this->haveReadSupport) {
            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
            //
            // @see https://bugs.php.net/51800
            $pipes = [
                Process::STDOUT => Process::OUT,
                Process::STDERR => Process::ERR,
            ];
            $tmpDir = sys_get_temp_dir();
            $lastError = 'unknown reason';
            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
            for ($i = 0;; ++$i) {
                foreach ($pipes as $pipe => $name) {
                    $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);

                    if (!$h = fopen($file.'.lock', 'w')) {
                        if (file_exists($file.'.lock')) {
                            continue 2;
                        }
                        restore_error_handler();
                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
                    }
                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
                        continue 2;
                    }
                    if (isset($this->lockHandles[$pipe])) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                    }
                    $this->lockHandles[$pipe] = $h;

                    if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                        unset($this->lockHandles[$pipe]);
                        continue 2;
                    }
                    $this->fileHandles[$pipe] = $h;
                    $this->files[$pipe] = $file;
                }
                break;
            }
            restore_error_handler();
        }

        parent::__construct($input);
    }

    public function __sleep(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __wakeup()
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('NUL', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
        // So we redirect output within the commandline and pass the nul device to the process
        return [
            ['pipe', 'r'],
            ['file', 'NUL', 'w'],
            ['file', 'NUL', 'w'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFiles(): array
    {
        return $this->files;
    }

    /**
     * {@inheritdoc}
     */
    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();
        $read = $r = $e = [];

        if ($blocking) {
            if ($w) {
                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
            } elseif ($this->fileHandles) {
                usleep(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(): bool
    {
        return $this->haveReadSupport;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen(): bool
    {
        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
{
    public const CHUNK_SIZE = 16384;

    /**
     * Returns an array of descriptors for the use of proc_open.
     */
    public function getDescriptors(): array;

    /**
     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
     *
     * @return string[]
     */
    public function getFiles(): array;

    /**
     * Reads data in file handles and pipes.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close pipes if they've reached EOF
     *
     * @return string[] An array of read data indexed by their fd
     */
    public function readAndWrite(bool $blocking, bool $close = false): array;

    /**
     * Returns if the current state has open file handles or pipes.
     */
    public function areOpen(): bool;

    /**
     * Returns if pipes are able to read output.
     */
    public function haveReadSupport(): bool;

    /**
     * Closes file handles and pipes.
     */
    public function close();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * ProcessUtils is a bunch of utility methods.
 *
 * This class contains static methods only and is not meant to be instantiated.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class ProcessUtils
{
    /**
     * This class should not be instantiated.
     */
    private function __construct()
    {
    }

    /**
     * Validates and normalizes a Process input.
     *
     * @param string $caller The name of method call that validates the input
     * @param mixed  $input  The input to validate
     *
     * @return mixed
     *
     * @throws InvalidArgumentException In case the input is not valid
     */
    public static function validateInput(string $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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 = ['.exe', '.bat', '.cmd', '.com'];

    /**
     * Replaces default suffixes of executable.
     */
    public function setSuffixes(array $suffixes)
    {
        $this->suffixes = $suffixes;
    }

    /**
     * Adds new possible suffix to check for executable.
     */
    public function addSuffix(string $suffix)
    {
        $this->suffixes[] = $suffix;
    }

    /**
     * Finds an executable by name.
     *
     * @param string      $name      The executable name (without the extension)
     * @param string|null $default   The default to return if no executable is found
     * @param array       $extraDirs Additional dirs to check into
     *
     * @return string|null
     */
    public function find(string $name, string $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 = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
        }
        foreach ($suffixes as $suffix) {
            foreach ($dirs as $dir) {
                if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
                    return $file;
                }
            }
        }

        return $default;
    }
}
<?php

// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt

return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
<?php

return array (
  'A' => 'a',
  'B' => 'b',
  'C' => 'c',
  'D' => 'd',
  'E' => 'e',
  'F' => 'f',
  'G' => 'g',
  'H' => 'h',
  'I' => 'i',
  'J' => 'j',
  'K' => 'k',
  'L' => 'l',
  'M' => 'm',
  'N' => 'n',
  'O' => 'o',
  'P' => 'p',
  'Q' => 'q',
  'R' => 'r',
  'S' => 's',
  'T' => 't',
  'U' => 'u',
  'V' => 'v',
  'W' => 'w',
  'X' => 'x',
  'Y' => 'y',
  'Z' => 'z',
  'À' => 'à',
  'Á' => 'á',
  'Â' => 'â',
  'Ã' => 'ã',
  'Ä' => 'ä',
  'Å' => 'å',
  'Æ' => 'æ',
  'Ç' => 'ç',
  'È' => 'è',
  'É' => 'é',
  'Ê' => 'ê',
  'Ë' => 'ë',
  'Ì' => 'ì',
  'Í' => 'í',
  'Î' => 'î',
  'Ï' => 'ï',
  'Ð' => 'ð',
  'Ñ' => 'ñ',
  'Ò' => 'ò',
  'Ó' => 'ó',
  'Ô' => 'ô',
  'Õ' => 'õ',
  'Ö' => 'ö',
  'Ø' => 'ø',
  'Ù' => 'ù',
  'Ú' => 'ú',
  'Û' => 'û',
  'Ü' => 'ü',
  'Ý' => 'ý',
  'Þ' => 'þ',
  'Ā' => 'ā',
  'Ă' => 'ă',
  'Ą' => 'ą',
  'Ć' => 'ć',
  'Ĉ' => 'ĉ',
  'Ċ' => 'ċ',
  'Č' => 'č',
  'Ď' => 'ď',
  'Đ' => 'đ',
  'Ē' => 'ē',
  'Ĕ' => 'ĕ',
  'Ė' => 'ė',
  'Ę' => 'ę',
  'Ě' => 'ě',
  'Ĝ' => 'ĝ',
  'Ğ' => 'ğ',
  'Ġ' => 'ġ',
  'Ģ' => 'ģ',
  'Ĥ' => 'ĥ',
  'Ħ' => 'ħ',
  'Ĩ' => 'ĩ',
  'Ī' => 'ī',
  'Ĭ' => 'ĭ',
  'Į' => 'į',
  'İ' => 'i̇',
  'IJ' => 'ij',
  'Ĵ' => 'ĵ',
  'Ķ' => 'ķ',
  'Ĺ' => 'ĺ',
  'Ļ' => 'ļ',
  'Ľ' => 'ľ',
  'Ŀ' => 'ŀ',
  'Ł' => 'ł',
  'Ń' => 'ń',
  'Ņ' => 'ņ',
  'Ň' => 'ň',
  'Ŋ' => 'ŋ',
  'Ō' => 'ō',
  'Ŏ' => 'ŏ',
  'Ő' => 'ő',
  'Œ' => 'œ',
  'Ŕ' => 'ŕ',
  'Ŗ' => 'ŗ',
  'Ř' => 'ř',
  'Ś' => 'ś',
  'Ŝ' => 'ŝ',
  'Ş' => 'ş',
  'Š' => 'š',
  'Ţ' => 'ţ',
  'Ť' => 'ť',
  'Ŧ' => 'ŧ',
  'Ũ' => 'ũ',
  'Ū' => 'ū',
  'Ŭ' => 'ŭ',
  'Ů' => 'ů',
  'Ű' => 'ű',
  'Ų' => 'ų',
  'Ŵ' => 'ŵ',
  'Ŷ' => 'ŷ',
  'Ÿ' => 'ÿ',
  'Ź' => 'ź',
  'Ż' => 'ż',
  'Ž' => 'ž',
  'Ɓ' => 'ɓ',
  'Ƃ' => 'ƃ',
  'Ƅ' => 'ƅ',
  'Ɔ' => 'ɔ',
  'Ƈ' => 'ƈ',
  'Ɖ' => 'ɖ',
  'Ɗ' => 'ɗ',
  'Ƌ' => 'ƌ',
  'Ǝ' => 'ǝ',
  'Ə' => 'ə',
  'Ɛ' => 'ɛ',
  'Ƒ' => 'ƒ',
  'Ɠ' => 'ɠ',
  'Ɣ' => 'ɣ',
  'Ɩ' => 'ɩ',
  'Ɨ' => 'ɨ',
  'Ƙ' => 'ƙ',
  'Ɯ' => 'ɯ',
  'Ɲ' => 'ɲ',
  'Ɵ' => 'ɵ',
  'Ơ' => 'ơ',
  'Ƣ' => 'ƣ',
  'Ƥ' => 'ƥ',
  'Ʀ' => 'ʀ',
  'Ƨ' => 'ƨ',
  'Ʃ' => 'ʃ',
  'Ƭ' => 'ƭ',
  'Ʈ' => 'ʈ',
  'Ư' => 'ư',
  'Ʊ' => 'ʊ',
  'Ʋ' => 'ʋ',
  'Ƴ' => 'ƴ',
  'Ƶ' => 'ƶ',
  'Ʒ' => 'ʒ',
  'Ƹ' => 'ƹ',
  'Ƽ' => 'ƽ',
  'DŽ' => 'dž',
  'Dž' => 'dž',
  'LJ' => 'lj',
  'Lj' => 'lj',
  'NJ' => 'nj',
  'Nj' => 'nj',
  'Ǎ' => 'ǎ',
  'Ǐ' => 'ǐ',
  'Ǒ' => 'ǒ',
  'Ǔ' => 'ǔ',
  'Ǖ' => 'ǖ',
  'Ǘ' => 'ǘ',
  'Ǚ' => 'ǚ',
  'Ǜ' => 'ǜ',
  'Ǟ' => 'ǟ',
  'Ǡ' => 'ǡ',
  'Ǣ' => 'ǣ',
  'Ǥ' => 'ǥ',
  'Ǧ' => 'ǧ',
  'Ǩ' => 'ǩ',
  'Ǫ' => 'ǫ',
  'Ǭ' => 'ǭ',
  'Ǯ' => 'ǯ',
  'DZ' => 'dz',
  'Dz' => 'dz',
  'Ǵ' => 'ǵ',
  'Ƕ' => 'ƕ',
  'Ƿ' => 'ƿ',
  'Ǹ' => 'ǹ',
  'Ǻ' => 'ǻ',
  'Ǽ' => 'ǽ',
  'Ǿ' => 'ǿ',
  'Ȁ' => 'ȁ',
  'Ȃ' => 'ȃ',
  'Ȅ' => 'ȅ',
  'Ȇ' => 'ȇ',
  'Ȉ' => 'ȉ',
  'Ȋ' => 'ȋ',
  'Ȍ' => 'ȍ',
  'Ȏ' => 'ȏ',
  'Ȑ' => 'ȑ',
  'Ȓ' => 'ȓ',
  'Ȕ' => 'ȕ',
  'Ȗ' => 'ȗ',
  'Ș' => 'ș',
  'Ț' => 'ț',
  'Ȝ' => 'ȝ',
  'Ȟ' => 'ȟ',
  'Ƞ' => 'ƞ',
  'Ȣ' => 'ȣ',
  'Ȥ' => 'ȥ',
  'Ȧ' => 'ȧ',
  'Ȩ' => 'ȩ',
  'Ȫ' => 'ȫ',
  'Ȭ' => 'ȭ',
  'Ȯ' => 'ȯ',
  'Ȱ' => 'ȱ',
  'Ȳ' => 'ȳ',
  'Ⱥ' => 'ⱥ',
  'Ȼ' => 'ȼ',
  'Ƚ' => 'ƚ',
  'Ⱦ' => 'ⱦ',
  'Ɂ' => 'ɂ',
  'Ƀ' => 'ƀ',
  'Ʉ' => 'ʉ',
  'Ʌ' => 'ʌ',
  'Ɇ' => 'ɇ',
  'Ɉ' => 'ɉ',
  'Ɋ' => 'ɋ',
  'Ɍ' => 'ɍ',
  'Ɏ' => 'ɏ',
  'Ͱ' => 'ͱ',
  'Ͳ' => 'ͳ',
  'Ͷ' => 'ͷ',
  'Ϳ' => 'ϳ',
  'Ά' => 'ά',
  'Έ' => 'έ',
  'Ή' => 'ή',
  'Ί' => 'ί',
  'Ό' => 'ό',
  'Ύ' => 'ύ',
  'Ώ' => 'ώ',
  'Α' => 'α',
  'Β' => 'β',
  'Γ' => 'γ',
  'Δ' => 'δ',
  'Ε' => 'ε',
  'Ζ' => 'ζ',
  'Η' => 'η',
  'Θ' => 'θ',
  'Ι' => 'ι',
  'Κ' => 'κ',
  'Λ' => 'λ',
  'Μ' => 'μ',
  'Ν' => 'ν',
  'Ξ' => 'ξ',
  'Ο' => 'ο',
  'Π' => 'π',
  'Ρ' => 'ρ',
  'Σ' => 'σ',
  'Τ' => 'τ',
  'Υ' => 'υ',
  'Φ' => 'φ',
  'Χ' => 'χ',
  'Ψ' => 'ψ',
  'Ω' => 'ω',
  'Ϊ' => 'ϊ',
  'Ϋ' => 'ϋ',
  'Ϗ' => 'ϗ',
  'Ϙ' => 'ϙ',
  'Ϛ' => 'ϛ',
  'Ϝ' => 'ϝ',
  'Ϟ' => 'ϟ',
  'Ϡ' => 'ϡ',
  'Ϣ' => 'ϣ',
  'Ϥ' => 'ϥ',
  'Ϧ' => 'ϧ',
  'Ϩ' => 'ϩ',
  'Ϫ' => 'ϫ',
  'Ϭ' => 'ϭ',
  'Ϯ' => 'ϯ',
  'ϴ' => 'θ',
  'Ϸ' => 'ϸ',
  'Ϲ' => 'ϲ',
  'Ϻ' => 'ϻ',
  'Ͻ' => 'ͻ',
  'Ͼ' => 'ͼ',
  'Ͽ' => 'ͽ',
  'Ѐ' => 'ѐ',
  'Ё' => 'ё',
  'Ђ' => 'ђ',
  'Ѓ' => 'ѓ',
  'Є' => 'є',
  'Ѕ' => 'ѕ',
  'І' => 'і',
  'Ї' => 'ї',
  'Ј' => 'ј',
  'Љ' => 'љ',
  'Њ' => 'њ',
  'Ћ' => 'ћ',
  'Ќ' => 'ќ',
  'Ѝ' => 'ѝ',
  'Ў' => 'ў',
  'Џ' => 'џ',
  'А' => 'а',
  'Б' => 'б',
  'В' => 'в',
  'Г' => 'г',
  'Д' => 'д',
  'Е' => 'е',
  'Ж' => 'ж',
  'З' => 'з',
  'И' => 'и',
  'Й' => 'й',
  'К' => 'к',
  'Л' => 'л',
  'М' => 'м',
  'Н' => 'н',
  'О' => 'о',
  'П' => 'п',
  'Р' => 'р',
  'С' => 'с',
  'Т' => 'т',
  'У' => 'у',
  'Ф' => 'ф',
  'Х' => 'х',
  'Ц' => 'ц',
  'Ч' => 'ч',
  'Ш' => 'ш',
  'Щ' => 'щ',
  'Ъ' => 'ъ',
  'Ы' => 'ы',
  'Ь' => 'ь',
  'Э' => 'э',
  'Ю' => 'ю',
  'Я' => 'я',
  'Ѡ' => 'ѡ',
  'Ѣ' => 'ѣ',
  'Ѥ' => 'ѥ',
  'Ѧ' => 'ѧ',
  'Ѩ' => 'ѩ',
  'Ѫ' => 'ѫ',
  'Ѭ' => 'ѭ',
  'Ѯ' => 'ѯ',
  'Ѱ' => 'ѱ',
  'Ѳ' => 'ѳ',
  'Ѵ' => 'ѵ',
  'Ѷ' => 'ѷ',
  'Ѹ' => 'ѹ',
  'Ѻ' => 'ѻ',
  'Ѽ' => 'ѽ',
  'Ѿ' => 'ѿ',
  'Ҁ' => 'ҁ',
  'Ҋ' => 'ҋ',
  'Ҍ' => 'ҍ',
  'Ҏ' => 'ҏ',
  'Ґ' => 'ґ',
  'Ғ' => 'ғ',
  'Ҕ' => 'ҕ',
  'Җ' => 'җ',
  'Ҙ' => 'ҙ',
  'Қ' => 'қ',
  'Ҝ' => 'ҝ',
  'Ҟ' => 'ҟ',
  'Ҡ' => 'ҡ',
  'Ң' => 'ң',
  'Ҥ' => 'ҥ',
  'Ҧ' => 'ҧ',
  'Ҩ' => 'ҩ',
  'Ҫ' => 'ҫ',
  'Ҭ' => 'ҭ',
  'Ү' => 'ү',
  'Ұ' => 'ұ',
  'Ҳ' => 'ҳ',
  'Ҵ' => 'ҵ',
  'Ҷ' => 'ҷ',
  'Ҹ' => 'ҹ',
  'Һ' => 'һ',
  'Ҽ' => 'ҽ',
  'Ҿ' => 'ҿ',
  'Ӏ' => 'ӏ',
  'Ӂ' => 'ӂ',
  'Ӄ' => 'ӄ',
  'Ӆ' => 'ӆ',
  'Ӈ' => 'ӈ',
  'Ӊ' => 'ӊ',
  'Ӌ' => 'ӌ',
  'Ӎ' => 'ӎ',
  'Ӑ' => 'ӑ',
  'Ӓ' => 'ӓ',
  'Ӕ' => 'ӕ',
  'Ӗ' => 'ӗ',
  'Ә' => 'ә',
  'Ӛ' => 'ӛ',
  'Ӝ' => 'ӝ',
  'Ӟ' => 'ӟ',
  'Ӡ' => 'ӡ',
  'Ӣ' => 'ӣ',
  'Ӥ' => 'ӥ',
  'Ӧ' => 'ӧ',
  'Ө' => 'ө',
  'Ӫ' => 'ӫ',
  'Ӭ' => 'ӭ',
  'Ӯ' => 'ӯ',
  'Ӱ' => 'ӱ',
  'Ӳ' => 'ӳ',
  'Ӵ' => 'ӵ',
  'Ӷ' => 'ӷ',
  'Ӹ' => 'ӹ',
  'Ӻ' => 'ӻ',
  'Ӽ' => 'ӽ',
  'Ӿ' => 'ӿ',
  'Ԁ' => 'ԁ',
  'Ԃ' => 'ԃ',
  'Ԅ' => 'ԅ',
  'Ԇ' => 'ԇ',
  'Ԉ' => 'ԉ',
  'Ԋ' => 'ԋ',
  'Ԍ' => 'ԍ',
  'Ԏ' => 'ԏ',
  'Ԑ' => 'ԑ',
  'Ԓ' => 'ԓ',
  'Ԕ' => 'ԕ',
  'Ԗ' => 'ԗ',
  'Ԙ' => 'ԙ',
  'Ԛ' => 'ԛ',
  'Ԝ' => 'ԝ',
  'Ԟ' => 'ԟ',
  'Ԡ' => 'ԡ',
  'Ԣ' => 'ԣ',
  'Ԥ' => 'ԥ',
  'Ԧ' => 'ԧ',
  'Ԩ' => 'ԩ',
  'Ԫ' => 'ԫ',
  'Ԭ' => 'ԭ',
  'Ԯ' => 'ԯ',
  'Ա' => 'ա',
  'Բ' => 'բ',
  'Գ' => 'գ',
  'Դ' => 'դ',
  'Ե' => 'ե',
  'Զ' => 'զ',
  'Է' => 'է',
  'Ը' => 'ը',
  'Թ' => 'թ',
  'Ժ' => 'ժ',
  'Ի' => 'ի',
  'Լ' => 'լ',
  'Խ' => 'խ',
  'Ծ' => 'ծ',
  'Կ' => 'կ',
  'Հ' => 'հ',
  'Ձ' => 'ձ',
  'Ղ' => 'ղ',
  'Ճ' => 'ճ',
  'Մ' => 'մ',
  'Յ' => 'յ',
  'Ն' => 'ն',
  'Շ' => 'շ',
  'Ո' => 'ո',
  'Չ' => 'չ',
  'Պ' => 'պ',
  'Ջ' => 'ջ',
  'Ռ' => 'ռ',
  'Ս' => 'ս',
  'Վ' => 'վ',
  'Տ' => 'տ',
  'Ր' => 'ր',
  'Ց' => 'ց',
  'Ւ' => 'ւ',
  'Փ' => 'փ',
  'Ք' => 'ք',
  'Օ' => 'օ',
  'Ֆ' => 'ֆ',
  'Ⴀ' => 'ⴀ',
  'Ⴁ' => 'ⴁ',
  'Ⴂ' => 'ⴂ',
  'Ⴃ' => 'ⴃ',
  'Ⴄ' => 'ⴄ',
  'Ⴅ' => 'ⴅ',
  'Ⴆ' => 'ⴆ',
  'Ⴇ' => 'ⴇ',
  'Ⴈ' => 'ⴈ',
  'Ⴉ' => 'ⴉ',
  'Ⴊ' => 'ⴊ',
  'Ⴋ' => 'ⴋ',
  'Ⴌ' => 'ⴌ',
  'Ⴍ' => 'ⴍ',
  'Ⴎ' => 'ⴎ',
  'Ⴏ' => 'ⴏ',
  'Ⴐ' => 'ⴐ',
  'Ⴑ' => 'ⴑ',
  'Ⴒ' => 'ⴒ',
  'Ⴓ' => 'ⴓ',
  'Ⴔ' => 'ⴔ',
  'Ⴕ' => 'ⴕ',
  'Ⴖ' => 'ⴖ',
  'Ⴗ' => 'ⴗ',
  'Ⴘ' => 'ⴘ',
  'Ⴙ' => 'ⴙ',
  'Ⴚ' => 'ⴚ',
  'Ⴛ' => 'ⴛ',
  'Ⴜ' => 'ⴜ',
  'Ⴝ' => 'ⴝ',
  'Ⴞ' => 'ⴞ',
  'Ⴟ' => 'ⴟ',
  'Ⴠ' => 'ⴠ',
  'Ⴡ' => 'ⴡ',
  'Ⴢ' => 'ⴢ',
  'Ⴣ' => 'ⴣ',
  'Ⴤ' => 'ⴤ',
  'Ⴥ' => 'ⴥ',
  'Ⴧ' => 'ⴧ',
  'Ⴭ' => 'ⴭ',
  'Ꭰ' => 'ꭰ',
  'Ꭱ' => 'ꭱ',
  'Ꭲ' => 'ꭲ',
  'Ꭳ' => 'ꭳ',
  'Ꭴ' => 'ꭴ',
  'Ꭵ' => 'ꭵ',
  'Ꭶ' => 'ꭶ',
  'Ꭷ' => 'ꭷ',
  'Ꭸ' => 'ꭸ',
  'Ꭹ' => 'ꭹ',
  'Ꭺ' => 'ꭺ',
  'Ꭻ' => 'ꭻ',
  'Ꭼ' => 'ꭼ',
  'Ꭽ' => 'ꭽ',
  'Ꭾ' => 'ꭾ',
  'Ꭿ' => 'ꭿ',
  'Ꮀ' => 'ꮀ',
  'Ꮁ' => 'ꮁ',
  'Ꮂ' => 'ꮂ',
  'Ꮃ' => 'ꮃ',
  'Ꮄ' => 'ꮄ',
  'Ꮅ' => 'ꮅ',
  'Ꮆ' => 'ꮆ',
  'Ꮇ' => 'ꮇ',
  'Ꮈ' => 'ꮈ',
  'Ꮉ' => 'ꮉ',
  'Ꮊ' => 'ꮊ',
  'Ꮋ' => 'ꮋ',
  'Ꮌ' => 'ꮌ',
  'Ꮍ' => 'ꮍ',
  'Ꮎ' => 'ꮎ',
  'Ꮏ' => 'ꮏ',
  'Ꮐ' => 'ꮐ',
  'Ꮑ' => 'ꮑ',
  'Ꮒ' => 'ꮒ',
  'Ꮓ' => 'ꮓ',
  'Ꮔ' => 'ꮔ',
  'Ꮕ' => 'ꮕ',
  'Ꮖ' => 'ꮖ',
  'Ꮗ' => 'ꮗ',
  'Ꮘ' => 'ꮘ',
  'Ꮙ' => 'ꮙ',
  'Ꮚ' => 'ꮚ',
  'Ꮛ' => 'ꮛ',
  'Ꮜ' => 'ꮜ',
  'Ꮝ' => 'ꮝ',
  'Ꮞ' => 'ꮞ',
  'Ꮟ' => 'ꮟ',
  'Ꮠ' => 'ꮠ',
  'Ꮡ' => 'ꮡ',
  'Ꮢ' => 'ꮢ',
  'Ꮣ' => 'ꮣ',
  'Ꮤ' => 'ꮤ',
  'Ꮥ' => 'ꮥ',
  'Ꮦ' => 'ꮦ',
  'Ꮧ' => 'ꮧ',
  'Ꮨ' => 'ꮨ',
  'Ꮩ' => 'ꮩ',
  'Ꮪ' => 'ꮪ',
  'Ꮫ' => 'ꮫ',
  'Ꮬ' => 'ꮬ',
  'Ꮭ' => 'ꮭ',
  'Ꮮ' => 'ꮮ',
  'Ꮯ' => 'ꮯ',
  'Ꮰ' => 'ꮰ',
  'Ꮱ' => 'ꮱ',
  'Ꮲ' => 'ꮲ',
  'Ꮳ' => 'ꮳ',
  'Ꮴ' => 'ꮴ',
  'Ꮵ' => 'ꮵ',
  'Ꮶ' => 'ꮶ',
  'Ꮷ' => 'ꮷ',
  'Ꮸ' => 'ꮸ',
  'Ꮹ' => 'ꮹ',
  'Ꮺ' => 'ꮺ',
  'Ꮻ' => 'ꮻ',
  'Ꮼ' => 'ꮼ',
  'Ꮽ' => 'ꮽ',
  'Ꮾ' => 'ꮾ',
  'Ꮿ' => 'ꮿ',
  'Ᏸ' => 'ᏸ',
  'Ᏹ' => 'ᏹ',
  'Ᏺ' => 'ᏺ',
  'Ᏻ' => 'ᏻ',
  'Ᏼ' => 'ᏼ',
  'Ᏽ' => 'ᏽ',
  'Ა' => 'ა',
  'Ბ' => 'ბ',
  'Გ' => 'გ',
  'Დ' => 'დ',
  'Ე' => 'ე',
  'Ვ' => 'ვ',
  'Ზ' => 'ზ',
  'Თ' => 'თ',
  'Ი' => 'ი',
  'Კ' => 'კ',
  'Ლ' => 'ლ',
  'Მ' => 'მ',
  'Ნ' => 'ნ',
  'Ო' => 'ო',
  'Პ' => 'პ',
  'Ჟ' => 'ჟ',
  'Რ' => 'რ',
  'Ს' => 'ს',
  'Ტ' => 'ტ',
  'Უ' => 'უ',
  'Ფ' => 'ფ',
  'Ქ' => 'ქ',
  'Ღ' => 'ღ',
  'Ყ' => 'ყ',
  'Შ' => 'შ',
  'Ჩ' => 'ჩ',
  'Ც' => 'ც',
  'Ძ' => 'ძ',
  'Წ' => 'წ',
  'Ჭ' => 'ჭ',
  'Ხ' => 'ხ',
  'Ჯ' => 'ჯ',
  'Ჰ' => 'ჰ',
  'Ჱ' => 'ჱ',
  'Ჲ' => 'ჲ',
  'Ჳ' => 'ჳ',
  'Ჴ' => 'ჴ',
  'Ჵ' => 'ჵ',
  'Ჶ' => 'ჶ',
  'Ჷ' => 'ჷ',
  'Ჸ' => 'ჸ',
  'Ჹ' => 'ჹ',
  'Ჺ' => 'ჺ',
  'Ჽ' => 'ჽ',
  'Ჾ' => 'ჾ',
  'Ჿ' => 'ჿ',
  'Ḁ' => 'ḁ',
  'Ḃ' => 'ḃ',
  'Ḅ' => 'ḅ',
  'Ḇ' => 'ḇ',
  'Ḉ' => 'ḉ',
  'Ḋ' => 'ḋ',
  'Ḍ' => 'ḍ',
  'Ḏ' => 'ḏ',
  'Ḑ' => 'ḑ',
  'Ḓ' => 'ḓ',
  'Ḕ' => 'ḕ',
  'Ḗ' => 'ḗ',
  'Ḙ' => 'ḙ',
  'Ḛ' => 'ḛ',
  'Ḝ' => 'ḝ',
  'Ḟ' => 'ḟ',
  'Ḡ' => 'ḡ',
  'Ḣ' => 'ḣ',
  'Ḥ' => 'ḥ',
  'Ḧ' => 'ḧ',
  'Ḩ' => 'ḩ',
  'Ḫ' => 'ḫ',
  'Ḭ' => 'ḭ',
  'Ḯ' => 'ḯ',
  'Ḱ' => 'ḱ',
  'Ḳ' => 'ḳ',
  'Ḵ' => 'ḵ',
  'Ḷ' => 'ḷ',
  'Ḹ' => 'ḹ',
  'Ḻ' => 'ḻ',
  'Ḽ' => 'ḽ',
  'Ḿ' => 'ḿ',
  'Ṁ' => 'ṁ',
  'Ṃ' => 'ṃ',
  'Ṅ' => 'ṅ',
  'Ṇ' => 'ṇ',
  'Ṉ' => 'ṉ',
  'Ṋ' => 'ṋ',
  'Ṍ' => 'ṍ',
  'Ṏ' => 'ṏ',
  'Ṑ' => 'ṑ',
  'Ṓ' => 'ṓ',
  'Ṕ' => 'ṕ',
  'Ṗ' => 'ṗ',
  'Ṙ' => 'ṙ',
  'Ṛ' => 'ṛ',
  'Ṝ' => 'ṝ',
  'Ṟ' => 'ṟ',
  'Ṡ' => 'ṡ',
  'Ṣ' => 'ṣ',
  'Ṥ' => 'ṥ',
  'Ṧ' => 'ṧ',
  'Ṩ' => 'ṩ',
  'Ṫ' => 'ṫ',
  'Ṭ' => 'ṭ',
  'Ṯ' => 'ṯ',
  'Ṱ' => 'ṱ',
  'Ṳ' => 'ṳ',
  'Ṵ' => 'ṵ',
  'Ṷ' => 'ṷ',
  'Ṹ' => 'ṹ',
  'Ṻ' => 'ṻ',
  'Ṽ' => 'ṽ',
  'Ṿ' => 'ṿ',
  'Ẁ' => 'ẁ',
  'Ẃ' => 'ẃ',
  'Ẅ' => 'ẅ',
  'Ẇ' => 'ẇ',
  'Ẉ' => 'ẉ',
  'Ẋ' => 'ẋ',
  'Ẍ' => 'ẍ',
  'Ẏ' => 'ẏ',
  'Ẑ' => 'ẑ',
  'Ẓ' => 'ẓ',
  'Ẕ' => 'ẕ',
  'ẞ' => 'ß',
  'Ạ' => 'ạ',
  'Ả' => 'ả',
  'Ấ' => 'ấ',
  'Ầ' => 'ầ',
  'Ẩ' => 'ẩ',
  'Ẫ' => 'ẫ',
  'Ậ' => 'ậ',
  'Ắ' => 'ắ',
  'Ằ' => 'ằ',
  'Ẳ' => 'ẳ',
  'Ẵ' => 'ẵ',
  'Ặ' => 'ặ',
  'Ẹ' => 'ẹ',
  'Ẻ' => 'ẻ',
  'Ẽ' => 'ẽ',
  'Ế' => 'ế',
  'Ề' => 'ề',
  'Ể' => 'ể',
  'Ễ' => 'ễ',
  'Ệ' => 'ệ',
  'Ỉ' => 'ỉ',
  'Ị' => 'ị',
  'Ọ' => 'ọ',
  'Ỏ' => 'ỏ',
  'Ố' => 'ố',
  'Ồ' => 'ồ',
  'Ổ' => 'ổ',
  'Ỗ' => 'ỗ',
  'Ộ' => 'ộ',
  'Ớ' => 'ớ',
  'Ờ' => 'ờ',
  'Ở' => 'ở',
  'Ỡ' => 'ỡ',
  'Ợ' => 'ợ',
  'Ụ' => 'ụ',
  'Ủ' => 'ủ',
  'Ứ' => 'ứ',
  'Ừ' => 'ừ',
  'Ử' => 'ử',
  'Ữ' => 'ữ',
  'Ự' => 'ự',
  'Ỳ' => 'ỳ',
  'Ỵ' => 'ỵ',
  'Ỷ' => 'ỷ',
  'Ỹ' => 'ỹ',
  'Ỻ' => 'ỻ',
  'Ỽ' => 'ỽ',
  'Ỿ' => 'ỿ',
  'Ἀ' => 'ἀ',
  'Ἁ' => 'ἁ',
  'Ἂ' => 'ἂ',
  'Ἃ' => 'ἃ',
  'Ἄ' => 'ἄ',
  'Ἅ' => 'ἅ',
  'Ἆ' => 'ἆ',
  'Ἇ' => 'ἇ',
  'Ἐ' => 'ἐ',
  'Ἑ' => 'ἑ',
  'Ἒ' => 'ἒ',
  'Ἓ' => 'ἓ',
  'Ἔ' => 'ἔ',
  'Ἕ' => 'ἕ',
  'Ἠ' => 'ἠ',
  'Ἡ' => 'ἡ',
  'Ἢ' => 'ἢ',
  'Ἣ' => 'ἣ',
  'Ἤ' => 'ἤ',
  'Ἥ' => 'ἥ',
  'Ἦ' => 'ἦ',
  'Ἧ' => 'ἧ',
  'Ἰ' => 'ἰ',
  'Ἱ' => 'ἱ',
  'Ἲ' => 'ἲ',
  'Ἳ' => 'ἳ',
  'Ἴ' => 'ἴ',
  'Ἵ' => 'ἵ',
  'Ἶ' => 'ἶ',
  'Ἷ' => 'ἷ',
  'Ὀ' => 'ὀ',
  'Ὁ' => 'ὁ',
  'Ὂ' => 'ὂ',
  'Ὃ' => 'ὃ',
  'Ὄ' => 'ὄ',
  'Ὅ' => 'ὅ',
  'Ὑ' => 'ὑ',
  'Ὓ' => 'ὓ',
  'Ὕ' => 'ὕ',
  'Ὗ' => 'ὗ',
  'Ὠ' => 'ὠ',
  'Ὡ' => 'ὡ',
  'Ὢ' => 'ὢ',
  'Ὣ' => 'ὣ',
  'Ὤ' => 'ὤ',
  'Ὥ' => 'ὥ',
  'Ὦ' => 'ὦ',
  'Ὧ' => 'ὧ',
  'ᾈ' => 'ᾀ',
  'ᾉ' => 'ᾁ',
  'ᾊ' => 'ᾂ',
  'ᾋ' => 'ᾃ',
  'ᾌ' => 'ᾄ',
  'ᾍ' => 'ᾅ',
  'ᾎ' => 'ᾆ',
  'ᾏ' => 'ᾇ',
  'ᾘ' => 'ᾐ',
  'ᾙ' => 'ᾑ',
  'ᾚ' => 'ᾒ',
  'ᾛ' => 'ᾓ',
  'ᾜ' => 'ᾔ',
  'ᾝ' => 'ᾕ',
  'ᾞ' => 'ᾖ',
  'ᾟ' => 'ᾗ',
  'ᾨ' => 'ᾠ',
  'ᾩ' => 'ᾡ',
  'ᾪ' => 'ᾢ',
  'ᾫ' => 'ᾣ',
  'ᾬ' => 'ᾤ',
  'ᾭ' => 'ᾥ',
  'ᾮ' => 'ᾦ',
  'ᾯ' => 'ᾧ',
  'Ᾰ' => 'ᾰ',
  'Ᾱ' => 'ᾱ',
  'Ὰ' => 'ὰ',
  'Ά' => 'ά',
  'ᾼ' => 'ᾳ',
  'Ὲ' => 'ὲ',
  'Έ' => 'έ',
  'Ὴ' => 'ὴ',
  'Ή' => 'ή',
  'ῌ' => 'ῃ',
  'Ῐ' => 'ῐ',
  'Ῑ' => 'ῑ',
  'Ὶ' => 'ὶ',
  'Ί' => 'ί',
  'Ῠ' => 'ῠ',
  'Ῡ' => 'ῡ',
  'Ὺ' => 'ὺ',
  'Ύ' => 'ύ',
  'Ῥ' => 'ῥ',
  'Ὸ' => 'ὸ',
  'Ό' => 'ό',
  'Ὼ' => 'ὼ',
  'Ώ' => 'ώ',
  'ῼ' => 'ῳ',
  'Ω' => 'ω',
  'K' => 'k',
  'Å' => 'å',
  'Ⅎ' => 'ⅎ',
  'Ⅰ' => 'ⅰ',
  'Ⅱ' => 'ⅱ',
  'Ⅲ' => 'ⅲ',
  'Ⅳ' => 'ⅳ',
  'Ⅴ' => 'ⅴ',
  'Ⅵ' => 'ⅵ',
  'Ⅶ' => 'ⅶ',
  'Ⅷ' => 'ⅷ',
  'Ⅸ' => 'ⅸ',
  'Ⅹ' => 'ⅹ',
  'Ⅺ' => 'ⅺ',
  'Ⅻ' => 'ⅻ',
  'Ⅼ' => 'ⅼ',
  'Ⅽ' => 'ⅽ',
  'Ⅾ' => 'ⅾ',
  'Ⅿ' => 'ⅿ',
  'Ↄ' => 'ↄ',
  'Ⓐ' => 'ⓐ',
  'Ⓑ' => 'ⓑ',
  'Ⓒ' => 'ⓒ',
  'Ⓓ' => 'ⓓ',
  'Ⓔ' => 'ⓔ',
  'Ⓕ' => 'ⓕ',
  'Ⓖ' => 'ⓖ',
  'Ⓗ' => 'ⓗ',
  'Ⓘ' => 'ⓘ',
  'Ⓙ' => 'ⓙ',
  'Ⓚ' => 'ⓚ',
  'Ⓛ' => 'ⓛ',
  'Ⓜ' => 'ⓜ',
  'Ⓝ' => 'ⓝ',
  'Ⓞ' => 'ⓞ',
  'Ⓟ' => 'ⓟ',
  'Ⓠ' => 'ⓠ',
  'Ⓡ' => 'ⓡ',
  'Ⓢ' => 'ⓢ',
  'Ⓣ' => 'ⓣ',
  'Ⓤ' => 'ⓤ',
  'Ⓥ' => 'ⓥ',
  'Ⓦ' => 'ⓦ',
  'Ⓧ' => 'ⓧ',
  'Ⓨ' => 'ⓨ',
  'Ⓩ' => 'ⓩ',
  'Ⰰ' => 'ⰰ',
  'Ⰱ' => 'ⰱ',
  'Ⰲ' => 'ⰲ',
  'Ⰳ' => 'ⰳ',
  'Ⰴ' => 'ⰴ',
  'Ⰵ' => 'ⰵ',
  'Ⰶ' => 'ⰶ',
  'Ⰷ' => 'ⰷ',
  'Ⰸ' => 'ⰸ',
  'Ⰹ' => 'ⰹ',
  'Ⰺ' => 'ⰺ',
  'Ⰻ' => 'ⰻ',
  'Ⰼ' => 'ⰼ',
  'Ⰽ' => 'ⰽ',
  'Ⰾ' => 'ⰾ',
  'Ⰿ' => 'ⰿ',
  'Ⱀ' => 'ⱀ',
  'Ⱁ' => 'ⱁ',
  'Ⱂ' => 'ⱂ',
  'Ⱃ' => 'ⱃ',
  'Ⱄ' => 'ⱄ',
  'Ⱅ' => 'ⱅ',
  'Ⱆ' => 'ⱆ',
  'Ⱇ' => 'ⱇ',
  'Ⱈ' => 'ⱈ',
  'Ⱉ' => 'ⱉ',
  'Ⱊ' => 'ⱊ',
  'Ⱋ' => 'ⱋ',
  'Ⱌ' => 'ⱌ',
  'Ⱍ' => 'ⱍ',
  'Ⱎ' => 'ⱎ',
  'Ⱏ' => 'ⱏ',
  'Ⱐ' => 'ⱐ',
  'Ⱑ' => 'ⱑ',
  'Ⱒ' => 'ⱒ',
  'Ⱓ' => 'ⱓ',
  'Ⱔ' => 'ⱔ',
  'Ⱕ' => 'ⱕ',
  'Ⱖ' => 'ⱖ',
  'Ⱗ' => 'ⱗ',
  'Ⱘ' => 'ⱘ',
  'Ⱙ' => 'ⱙ',
  'Ⱚ' => 'ⱚ',
  'Ⱛ' => 'ⱛ',
  'Ⱜ' => 'ⱜ',
  'Ⱝ' => 'ⱝ',
  'Ⱞ' => 'ⱞ',
  'Ⱡ' => 'ⱡ',
  'Ɫ' => 'ɫ',
  'Ᵽ' => 'ᵽ',
  'Ɽ' => 'ɽ',
  'Ⱨ' => 'ⱨ',
  'Ⱪ' => 'ⱪ',
  'Ⱬ' => 'ⱬ',
  'Ɑ' => 'ɑ',
  'Ɱ' => 'ɱ',
  'Ɐ' => 'ɐ',
  'Ɒ' => 'ɒ',
  'Ⱳ' => 'ⱳ',
  'Ⱶ' => 'ⱶ',
  'Ȿ' => 'ȿ',
  'Ɀ' => 'ɀ',
  'Ⲁ' => 'ⲁ',
  'Ⲃ' => 'ⲃ',
  'Ⲅ' => 'ⲅ',
  'Ⲇ' => 'ⲇ',
  'Ⲉ' => 'ⲉ',
  'Ⲋ' => 'ⲋ',
  'Ⲍ' => 'ⲍ',
  'Ⲏ' => 'ⲏ',
  'Ⲑ' => 'ⲑ',
  'Ⲓ' => 'ⲓ',
  'Ⲕ' => 'ⲕ',
  'Ⲗ' => 'ⲗ',
  'Ⲙ' => 'ⲙ',
  'Ⲛ' => 'ⲛ',
  'Ⲝ' => 'ⲝ',
  'Ⲟ' => 'ⲟ',
  'Ⲡ' => 'ⲡ',
  'Ⲣ' => 'ⲣ',
  'Ⲥ' => 'ⲥ',
  'Ⲧ' => 'ⲧ',
  'Ⲩ' => 'ⲩ',
  'Ⲫ' => 'ⲫ',
  'Ⲭ' => 'ⲭ',
  'Ⲯ' => 'ⲯ',
  'Ⲱ' => 'ⲱ',
  'Ⲳ' => 'ⲳ',
  'Ⲵ' => 'ⲵ',
  'Ⲷ' => 'ⲷ',
  'Ⲹ' => 'ⲹ',
  'Ⲻ' => 'ⲻ',
  'Ⲽ' => 'ⲽ',
  'Ⲿ' => 'ⲿ',
  'Ⳁ' => 'ⳁ',
  'Ⳃ' => 'ⳃ',
  'Ⳅ' => 'ⳅ',
  'Ⳇ' => 'ⳇ',
  'Ⳉ' => 'ⳉ',
  'Ⳋ' => 'ⳋ',
  'Ⳍ' => 'ⳍ',
  'Ⳏ' => 'ⳏ',
  'Ⳑ' => 'ⳑ',
  'Ⳓ' => 'ⳓ',
  'Ⳕ' => 'ⳕ',
  'Ⳗ' => 'ⳗ',
  'Ⳙ' => 'ⳙ',
  'Ⳛ' => 'ⳛ',
  'Ⳝ' => 'ⳝ',
  'Ⳟ' => 'ⳟ',
  'Ⳡ' => 'ⳡ',
  'Ⳣ' => 'ⳣ',
  'Ⳬ' => 'ⳬ',
  'Ⳮ' => 'ⳮ',
  'Ⳳ' => 'ⳳ',
  'Ꙁ' => 'ꙁ',
  'Ꙃ' => 'ꙃ',
  'Ꙅ' => 'ꙅ',
  'Ꙇ' => 'ꙇ',
  'Ꙉ' => 'ꙉ',
  'Ꙋ' => 'ꙋ',
  'Ꙍ' => 'ꙍ',
  'Ꙏ' => 'ꙏ',
  'Ꙑ' => 'ꙑ',
  'Ꙓ' => 'ꙓ',
  'Ꙕ' => 'ꙕ',
  'Ꙗ' => 'ꙗ',
  'Ꙙ' => 'ꙙ',
  'Ꙛ' => 'ꙛ',
  'Ꙝ' => 'ꙝ',
  'Ꙟ' => 'ꙟ',
  'Ꙡ' => 'ꙡ',
  'Ꙣ' => 'ꙣ',
  'Ꙥ' => 'ꙥ',
  'Ꙧ' => 'ꙧ',
  'Ꙩ' => 'ꙩ',
  'Ꙫ' => 'ꙫ',
  'Ꙭ' => 'ꙭ',
  'Ꚁ' => 'ꚁ',
  'Ꚃ' => 'ꚃ',
  'Ꚅ' => 'ꚅ',
  'Ꚇ' => 'ꚇ',
  'Ꚉ' => 'ꚉ',
  'Ꚋ' => 'ꚋ',
  'Ꚍ' => 'ꚍ',
  'Ꚏ' => 'ꚏ',
  'Ꚑ' => 'ꚑ',
  'Ꚓ' => 'ꚓ',
  'Ꚕ' => 'ꚕ',
  'Ꚗ' => 'ꚗ',
  'Ꚙ' => 'ꚙ',
  'Ꚛ' => 'ꚛ',
  'Ꜣ' => 'ꜣ',
  'Ꜥ' => 'ꜥ',
  'Ꜧ' => 'ꜧ',
  'Ꜩ' => 'ꜩ',
  'Ꜫ' => 'ꜫ',
  'Ꜭ' => 'ꜭ',
  'Ꜯ' => 'ꜯ',
  'Ꜳ' => 'ꜳ',
  'Ꜵ' => 'ꜵ',
  'Ꜷ' => 'ꜷ',
  'Ꜹ' => 'ꜹ',
  'Ꜻ' => 'ꜻ',
  'Ꜽ' => 'ꜽ',
  'Ꜿ' => 'ꜿ',
  'Ꝁ' => 'ꝁ',
  'Ꝃ' => 'ꝃ',
  'Ꝅ' => 'ꝅ',
  'Ꝇ' => 'ꝇ',
  'Ꝉ' => 'ꝉ',
  'Ꝋ' => 'ꝋ',
  'Ꝍ' => 'ꝍ',
  'Ꝏ' => 'ꝏ',
  'Ꝑ' => 'ꝑ',
  'Ꝓ' => 'ꝓ',
  'Ꝕ' => 'ꝕ',
  'Ꝗ' => 'ꝗ',
  'Ꝙ' => 'ꝙ',
  'Ꝛ' => 'ꝛ',
  'Ꝝ' => 'ꝝ',
  'Ꝟ' => 'ꝟ',
  'Ꝡ' => 'ꝡ',
  'Ꝣ' => 'ꝣ',
  'Ꝥ' => 'ꝥ',
  'Ꝧ' => 'ꝧ',
  'Ꝩ' => 'ꝩ',
  'Ꝫ' => 'ꝫ',
  'Ꝭ' => 'ꝭ',
  'Ꝯ' => 'ꝯ',
  'Ꝺ' => 'ꝺ',
  'Ꝼ' => 'ꝼ',
  'Ᵹ' => 'ᵹ',
  'Ꝿ' => 'ꝿ',
  'Ꞁ' => 'ꞁ',
  'Ꞃ' => 'ꞃ',
  'Ꞅ' => 'ꞅ',
  'Ꞇ' => 'ꞇ',
  'Ꞌ' => 'ꞌ',
  'Ɥ' => 'ɥ',
  'Ꞑ' => 'ꞑ',
  'Ꞓ' => 'ꞓ',
  'Ꞗ' => 'ꞗ',
  'Ꞙ' => 'ꞙ',
  'Ꞛ' => 'ꞛ',
  'Ꞝ' => 'ꞝ',
  'Ꞟ' => 'ꞟ',
  'Ꞡ' => 'ꞡ',
  'Ꞣ' => 'ꞣ',
  'Ꞥ' => 'ꞥ',
  'Ꞧ' => 'ꞧ',
  'Ꞩ' => 'ꞩ',
  'Ɦ' => 'ɦ',
  'Ɜ' => 'ɜ',
  'Ɡ' => 'ɡ',
  'Ɬ' => 'ɬ',
  'Ɪ' => 'ɪ',
  'Ʞ' => 'ʞ',
  'Ʇ' => 'ʇ',
  'Ʝ' => 'ʝ',
  'Ꭓ' => 'ꭓ',
  'Ꞵ' => 'ꞵ',
  'Ꞷ' => 'ꞷ',
  'Ꞹ' => 'ꞹ',
  'Ꞻ' => 'ꞻ',
  'Ꞽ' => 'ꞽ',
  'Ꞿ' => 'ꞿ',
  'Ꟃ' => 'ꟃ',
  'Ꞔ' => 'ꞔ',
  'Ʂ' => 'ʂ',
  'Ᶎ' => 'ᶎ',
  'Ꟈ' => 'ꟈ',
  'Ꟊ' => 'ꟊ',
  'Ꟶ' => 'ꟶ',
  'A' => 'a',
  'B' => 'b',
  'C' => 'c',
  'D' => 'd',
  'E' => 'e',
  'F' => 'f',
  'G' => 'g',
  'H' => 'h',
  'I' => 'i',
  'J' => 'j',
  'K' => 'k',
  'L' => 'l',
  'M' => 'm',
  'N' => 'n',
  'O' => 'o',
  'P' => 'p',
  'Q' => 'q',
  'R' => 'r',
  'S' => 's',
  'T' => 't',
  'U' => 'u',
  'V' => 'v',
  'W' => 'w',
  'X' => 'x',
  'Y' => 'y',
  'Z' => 'z',
  '𐐀' => '𐐨',
  '𐐁' => '𐐩',
  '𐐂' => '𐐪',
  '𐐃' => '𐐫',
  '𐐄' => '𐐬',
  '𐐅' => '𐐭',
  '𐐆' => '𐐮',
  '𐐇' => '𐐯',
  '𐐈' => '𐐰',
  '𐐉' => '𐐱',
  '𐐊' => '𐐲',
  '𐐋' => '𐐳',
  '𐐌' => '𐐴',
  '𐐍' => '𐐵',
  '𐐎' => '𐐶',
  '𐐏' => '𐐷',
  '𐐐' => '𐐸',
  '𐐑' => '𐐹',
  '𐐒' => '𐐺',
  '𐐓' => '𐐻',
  '𐐔' => '𐐼',
  '𐐕' => '𐐽',
  '𐐖' => '𐐾',
  '𐐗' => '𐐿',
  '𐐘' => '𐑀',
  '𐐙' => '𐑁',
  '𐐚' => '𐑂',
  '𐐛' => '𐑃',
  '𐐜' => '𐑄',
  '𐐝' => '𐑅',
  '𐐞' => '𐑆',
  '𐐟' => '𐑇',
  '𐐠' => '𐑈',
  '𐐡' => '𐑉',
  '𐐢' => '𐑊',
  '𐐣' => '𐑋',
  '𐐤' => '𐑌',
  '𐐥' => '𐑍',
  '𐐦' => '𐑎',
  '𐐧' => '𐑏',
  '𐒰' => '𐓘',
  '𐒱' => '𐓙',
  '𐒲' => '𐓚',
  '𐒳' => '𐓛',
  '𐒴' => '𐓜',
  '𐒵' => '𐓝',
  '𐒶' => '𐓞',
  '𐒷' => '𐓟',
  '𐒸' => '𐓠',
  '𐒹' => '𐓡',
  '𐒺' => '𐓢',
  '𐒻' => '𐓣',
  '𐒼' => '𐓤',
  '𐒽' => '𐓥',
  '𐒾' => '𐓦',
  '𐒿' => '𐓧',
  '𐓀' => '𐓨',
  '𐓁' => '𐓩',
  '𐓂' => '𐓪',
  '𐓃' => '𐓫',
  '𐓄' => '𐓬',
  '𐓅' => '𐓭',
  '𐓆' => '𐓮',
  '𐓇' => '𐓯',
  '𐓈' => '𐓰',
  '𐓉' => '𐓱',
  '𐓊' => '𐓲',
  '𐓋' => '𐓳',
  '𐓌' => '𐓴',
  '𐓍' => '𐓵',
  '𐓎' => '𐓶',
  '𐓏' => '𐓷',
  '𐓐' => '𐓸',
  '𐓑' => '𐓹',
  '𐓒' => '𐓺',
  '𐓓' => '𐓻',
  '𐲀' => '𐳀',
  '𐲁' => '𐳁',
  '𐲂' => '𐳂',
  '𐲃' => '𐳃',
  '𐲄' => '𐳄',
  '𐲅' => '𐳅',
  '𐲆' => '𐳆',
  '𐲇' => '𐳇',
  '𐲈' => '𐳈',
  '𐲉' => '𐳉',
  '𐲊' => '𐳊',
  '𐲋' => '𐳋',
  '𐲌' => '𐳌',
  '𐲍' => '𐳍',
  '𐲎' => '𐳎',
  '𐲏' => '𐳏',
  '𐲐' => '𐳐',
  '𐲑' => '𐳑',
  '𐲒' => '𐳒',
  '𐲓' => '𐳓',
  '𐲔' => '𐳔',
  '𐲕' => '𐳕',
  '𐲖' => '𐳖',
  '𐲗' => '𐳗',
  '𐲘' => '𐳘',
  '𐲙' => '𐳙',
  '𐲚' => '𐳚',
  '𐲛' => '𐳛',
  '𐲜' => '𐳜',
  '𐲝' => '𐳝',
  '𐲞' => '𐳞',
  '𐲟' => '𐳟',
  '𐲠' => '𐳠',
  '𐲡' => '𐳡',
  '𐲢' => '𐳢',
  '𐲣' => '𐳣',
  '𐲤' => '𐳤',
  '𐲥' => '𐳥',
  '𐲦' => '𐳦',
  '𐲧' => '𐳧',
  '𐲨' => '𐳨',
  '𐲩' => '𐳩',
  '𐲪' => '𐳪',
  '𐲫' => '𐳫',
  '𐲬' => '𐳬',
  '𐲭' => '𐳭',
  '𐲮' => '𐳮',
  '𐲯' => '𐳯',
  '𐲰' => '𐳰',
  '𐲱' => '𐳱',
  '𐲲' => '𐳲',
  '𑢠' => '𑣀',
  '𑢡' => '𑣁',
  '𑢢' => '𑣂',
  '𑢣' => '𑣃',
  '𑢤' => '𑣄',
  '𑢥' => '𑣅',
  '𑢦' => '𑣆',
  '𑢧' => '𑣇',
  '𑢨' => '𑣈',
  '𑢩' => '𑣉',
  '𑢪' => '𑣊',
  '𑢫' => '𑣋',
  '𑢬' => '𑣌',
  '𑢭' => '𑣍',
  '𑢮' => '𑣎',
  '𑢯' => '𑣏',
  '𑢰' => '𑣐',
  '𑢱' => '𑣑',
  '𑢲' => '𑣒',
  '𑢳' => '𑣓',
  '𑢴' => '𑣔',
  '𑢵' => '𑣕',
  '𑢶' => '𑣖',
  '𑢷' => '𑣗',
  '𑢸' => '𑣘',
  '𑢹' => '𑣙',
  '𑢺' => '𑣚',
  '𑢻' => '𑣛',
  '𑢼' => '𑣜',
  '𑢽' => '𑣝',
  '𑢾' => '𑣞',
  '𑢿' => '𑣟',
  '𖹀' => '𖹠',
  '𖹁' => '𖹡',
  '𖹂' => '𖹢',
  '𖹃' => '𖹣',
  '𖹄' => '𖹤',
  '𖹅' => '𖹥',
  '𖹆' => '𖹦',
  '𖹇' => '𖹧',
  '𖹈' => '𖹨',
  '𖹉' => '𖹩',
  '𖹊' => '𖹪',
  '𖹋' => '𖹫',
  '𖹌' => '𖹬',
  '𖹍' => '𖹭',
  '𖹎' => '𖹮',
  '𖹏' => '𖹯',
  '𖹐' => '𖹰',
  '𖹑' => '𖹱',
  '𖹒' => '𖹲',
  '𖹓' => '𖹳',
  '𖹔' => '𖹴',
  '𖹕' => '𖹵',
  '𖹖' => '𖹶',
  '𖹗' => '𖹷',
  '𖹘' => '𖹸',
  '𖹙' => '𖹹',
  '𖹚' => '𖹺',
  '𖹛' => '𖹻',
  '𖹜' => '𖹼',
  '𖹝' => '𖹽',
  '𖹞' => '𖹾',
  '𖹟' => '𖹿',
  '𞤀' => '𞤢',
  '𞤁' => '𞤣',
  '𞤂' => '𞤤',
  '𞤃' => '𞤥',
  '𞤄' => '𞤦',
  '𞤅' => '𞤧',
  '𞤆' => '𞤨',
  '𞤇' => '𞤩',
  '𞤈' => '𞤪',
  '𞤉' => '𞤫',
  '𞤊' => '𞤬',
  '𞤋' => '𞤭',
  '𞤌' => '𞤮',
  '𞤍' => '𞤯',
  '𞤎' => '𞤰',
  '𞤏' => '𞤱',
  '𞤐' => '𞤲',
  '𞤑' => '𞤳',
  '𞤒' => '𞤴',
  '𞤓' => '𞤵',
  '𞤔' => '𞤶',
  '𞤕' => '𞤷',
  '𞤖' => '𞤸',
  '𞤗' => '𞤹',
  '𞤘' => '𞤺',
  '𞤙' => '𞤻',
  '𞤚' => '𞤼',
  '𞤛' => '𞤽',
  '𞤜' => '𞤾',
  '𞤝' => '𞤿',
  '𞤞' => '𞥀',
  '𞤟' => '𞥁',
  '𞤠' => '𞥂',
  '𞤡' => '𞥃',
);
<?php

return array (
  'a' => 'A',
  'b' => 'B',
  'c' => 'C',
  'd' => 'D',
  'e' => 'E',
  'f' => 'F',
  'g' => 'G',
  'h' => 'H',
  'i' => 'I',
  'j' => 'J',
  'k' => 'K',
  'l' => 'L',
  'm' => 'M',
  'n' => 'N',
  'o' => 'O',
  'p' => 'P',
  'q' => 'Q',
  'r' => 'R',
  's' => 'S',
  't' => 'T',
  'u' => 'U',
  'v' => 'V',
  'w' => 'W',
  'x' => 'X',
  'y' => 'Y',
  'z' => 'Z',
  'µ' => 'Μ',
  'à' => 'À',
  'á' => 'Á',
  'â' => 'Â',
  'ã' => 'Ã',
  'ä' => 'Ä',
  'å' => 'Å',
  'æ' => 'Æ',
  'ç' => 'Ç',
  'è' => 'È',
  'é' => 'É',
  'ê' => 'Ê',
  'ë' => 'Ë',
  'ì' => 'Ì',
  'í' => 'Í',
  'î' => 'Î',
  'ï' => 'Ï',
  'ð' => 'Ð',
  'ñ' => 'Ñ',
  'ò' => 'Ò',
  'ó' => 'Ó',
  'ô' => 'Ô',
  'õ' => 'Õ',
  'ö' => 'Ö',
  'ø' => 'Ø',
  'ù' => 'Ù',
  'ú' => 'Ú',
  'û' => 'Û',
  'ü' => 'Ü',
  'ý' => 'Ý',
  'þ' => 'Þ',
  'ÿ' => 'Ÿ',
  'ā' => 'Ā',
  'ă' => 'Ă',
  'ą' => 'Ą',
  'ć' => 'Ć',
  'ĉ' => 'Ĉ',
  'ċ' => 'Ċ',
  'č' => 'Č',
  'ď' => 'Ď',
  'đ' => 'Đ',
  'ē' => 'Ē',
  'ĕ' => 'Ĕ',
  'ė' => 'Ė',
  'ę' => 'Ę',
  'ě' => 'Ě',
  'ĝ' => 'Ĝ',
  'ğ' => 'Ğ',
  'ġ' => 'Ġ',
  'ģ' => 'Ģ',
  'ĥ' => 'Ĥ',
  'ħ' => 'Ħ',
  'ĩ' => 'Ĩ',
  'ī' => 'Ī',
  'ĭ' => 'Ĭ',
  'į' => 'Į',
  'ı' => 'I',
  'ij' => 'IJ',
  'ĵ' => 'Ĵ',
  'ķ' => 'Ķ',
  'ĺ' => 'Ĺ',
  'ļ' => 'Ļ',
  'ľ' => 'Ľ',
  'ŀ' => 'Ŀ',
  'ł' => 'Ł',
  'ń' => 'Ń',
  'ņ' => 'Ņ',
  'ň' => 'Ň',
  'ŋ' => 'Ŋ',
  'ō' => 'Ō',
  'ŏ' => 'Ŏ',
  'ő' => 'Ő',
  'œ' => 'Œ',
  'ŕ' => 'Ŕ',
  'ŗ' => 'Ŗ',
  'ř' => 'Ř',
  'ś' => 'Ś',
  'ŝ' => 'Ŝ',
  'ş' => 'Ş',
  'š' => 'Š',
  'ţ' => 'Ţ',
  'ť' => 'Ť',
  'ŧ' => 'Ŧ',
  'ũ' => 'Ũ',
  'ū' => 'Ū',
  'ŭ' => 'Ŭ',
  'ů' => 'Ů',
  'ű' => 'Ű',
  'ų' => 'Ų',
  'ŵ' => 'Ŵ',
  'ŷ' => 'Ŷ',
  'ź' => 'Ź',
  'ż' => 'Ż',
  'ž' => 'Ž',
  'ſ' => 'S',
  'ƀ' => 'Ƀ',
  'ƃ' => 'Ƃ',
  'ƅ' => 'Ƅ',
  'ƈ' => 'Ƈ',
  'ƌ' => 'Ƌ',
  'ƒ' => 'Ƒ',
  'ƕ' => 'Ƕ',
  'ƙ' => 'Ƙ',
  'ƚ' => 'Ƚ',
  'ƞ' => 'Ƞ',
  'ơ' => 'Ơ',
  'ƣ' => 'Ƣ',
  'ƥ' => 'Ƥ',
  'ƨ' => 'Ƨ',
  'ƭ' => 'Ƭ',
  'ư' => 'Ư',
  'ƴ' => 'Ƴ',
  'ƶ' => 'Ƶ',
  'ƹ' => 'Ƹ',
  'ƽ' => 'Ƽ',
  'ƿ' => 'Ƿ',
  'Dž' => 'DŽ',
  'dž' => 'DŽ',
  'Lj' => 'LJ',
  'lj' => 'LJ',
  'Nj' => 'NJ',
  'nj' => 'NJ',
  'ǎ' => 'Ǎ',
  'ǐ' => 'Ǐ',
  'ǒ' => 'Ǒ',
  'ǔ' => 'Ǔ',
  'ǖ' => 'Ǖ',
  'ǘ' => 'Ǘ',
  'ǚ' => 'Ǚ',
  'ǜ' => 'Ǜ',
  'ǝ' => 'Ǝ',
  'ǟ' => 'Ǟ',
  'ǡ' => 'Ǡ',
  'ǣ' => 'Ǣ',
  'ǥ' => 'Ǥ',
  'ǧ' => 'Ǧ',
  'ǩ' => 'Ǩ',
  'ǫ' => 'Ǫ',
  'ǭ' => 'Ǭ',
  'ǯ' => 'Ǯ',
  'Dz' => 'DZ',
  'dz' => 'DZ',
  'ǵ' => 'Ǵ',
  'ǹ' => 'Ǹ',
  'ǻ' => 'Ǻ',
  'ǽ' => 'Ǽ',
  'ǿ' => 'Ǿ',
  'ȁ' => 'Ȁ',
  'ȃ' => 'Ȃ',
  'ȅ' => 'Ȅ',
  'ȇ' => 'Ȇ',
  'ȉ' => 'Ȉ',
  'ȋ' => 'Ȋ',
  'ȍ' => 'Ȍ',
  'ȏ' => 'Ȏ',
  'ȑ' => 'Ȑ',
  'ȓ' => 'Ȓ',
  'ȕ' => 'Ȕ',
  'ȗ' => 'Ȗ',
  'ș' => 'Ș',
  'ț' => 'Ț',
  'ȝ' => 'Ȝ',
  'ȟ' => 'Ȟ',
  'ȣ' => 'Ȣ',
  'ȥ' => 'Ȥ',
  'ȧ' => 'Ȧ',
  'ȩ' => 'Ȩ',
  'ȫ' => 'Ȫ',
  'ȭ' => 'Ȭ',
  'ȯ' => 'Ȯ',
  'ȱ' => 'Ȱ',
  'ȳ' => 'Ȳ',
  'ȼ' => 'Ȼ',
  'ȿ' => 'Ȿ',
  'ɀ' => 'Ɀ',
  'ɂ' => 'Ɂ',
  'ɇ' => 'Ɇ',
  'ɉ' => 'Ɉ',
  'ɋ' => 'Ɋ',
  'ɍ' => 'Ɍ',
  'ɏ' => 'Ɏ',
  'ɐ' => 'Ɐ',
  'ɑ' => 'Ɑ',
  'ɒ' => 'Ɒ',
  'ɓ' => 'Ɓ',
  'ɔ' => 'Ɔ',
  'ɖ' => 'Ɖ',
  'ɗ' => 'Ɗ',
  'ə' => 'Ə',
  'ɛ' => 'Ɛ',
  'ɜ' => 'Ɜ',
  'ɠ' => 'Ɠ',
  'ɡ' => 'Ɡ',
  'ɣ' => 'Ɣ',
  'ɥ' => 'Ɥ',
  'ɦ' => 'Ɦ',
  'ɨ' => 'Ɨ',
  'ɩ' => 'Ɩ',
  'ɪ' => 'Ɪ',
  'ɫ' => 'Ɫ',
  'ɬ' => 'Ɬ',
  'ɯ' => 'Ɯ',
  'ɱ' => 'Ɱ',
  'ɲ' => 'Ɲ',
  'ɵ' => 'Ɵ',
  'ɽ' => 'Ɽ',
  'ʀ' => 'Ʀ',
  'ʂ' => 'Ʂ',
  'ʃ' => 'Ʃ',
  'ʇ' => 'Ʇ',
  'ʈ' => 'Ʈ',
  'ʉ' => 'Ʉ',
  'ʊ' => 'Ʊ',
  'ʋ' => 'Ʋ',
  'ʌ' => 'Ʌ',
  'ʒ' => 'Ʒ',
  'ʝ' => 'Ʝ',
  'ʞ' => 'Ʞ',
  'ͅ' => 'Ι',
  'ͱ' => 'Ͱ',
  'ͳ' => 'Ͳ',
  'ͷ' => 'Ͷ',
  'ͻ' => 'Ͻ',
  'ͼ' => 'Ͼ',
  'ͽ' => 'Ͽ',
  'ά' => 'Ά',
  'έ' => 'Έ',
  'ή' => 'Ή',
  'ί' => 'Ί',
  'α' => 'Α',
  'β' => 'Β',
  'γ' => 'Γ',
  'δ' => 'Δ',
  'ε' => 'Ε',
  'ζ' => 'Ζ',
  'η' => 'Η',
  'θ' => 'Θ',
  'ι' => 'Ι',
  'κ' => 'Κ',
  'λ' => 'Λ',
  'μ' => 'Μ',
  'ν' => 'Ν',
  'ξ' => 'Ξ',
  'ο' => 'Ο',
  'π' => 'Π',
  'ρ' => 'Ρ',
  'ς' => 'Σ',
  'σ' => 'Σ',
  'τ' => 'Τ',
  'υ' => 'Υ',
  'φ' => 'Φ',
  'χ' => 'Χ',
  'ψ' => 'Ψ',
  'ω' => 'Ω',
  'ϊ' => 'Ϊ',
  'ϋ' => 'Ϋ',
  'ό' => 'Ό',
  'ύ' => 'Ύ',
  'ώ' => 'Ώ',
  'ϐ' => 'Β',
  'ϑ' => 'Θ',
  'ϕ' => 'Φ',
  'ϖ' => 'Π',
  'ϗ' => 'Ϗ',
  'ϙ' => 'Ϙ',
  'ϛ' => 'Ϛ',
  'ϝ' => 'Ϝ',
  'ϟ' => 'Ϟ',
  'ϡ' => 'Ϡ',
  'ϣ' => 'Ϣ',
  'ϥ' => 'Ϥ',
  'ϧ' => 'Ϧ',
  'ϩ' => 'Ϩ',
  'ϫ' => 'Ϫ',
  'ϭ' => 'Ϭ',
  'ϯ' => 'Ϯ',
  'ϰ' => 'Κ',
  'ϱ' => 'Ρ',
  'ϲ' => 'Ϲ',
  'ϳ' => 'Ϳ',
  'ϵ' => 'Ε',
  'ϸ' => 'Ϸ',
  'ϻ' => 'Ϻ',
  'а' => 'А',
  'б' => 'Б',
  'в' => 'В',
  'г' => 'Г',
  'д' => 'Д',
  'е' => 'Е',
  'ж' => 'Ж',
  'з' => 'З',
  'и' => 'И',
  'й' => 'Й',
  'к' => 'К',
  'л' => 'Л',
  'м' => 'М',
  'н' => 'Н',
  'о' => 'О',
  'п' => 'П',
  'р' => 'Р',
  'с' => 'С',
  'т' => 'Т',
  'у' => 'У',
  'ф' => 'Ф',
  'х' => 'Х',
  'ц' => 'Ц',
  'ч' => 'Ч',
  'ш' => 'Ш',
  'щ' => 'Щ',
  'ъ' => 'Ъ',
  'ы' => 'Ы',
  'ь' => 'Ь',
  'э' => 'Э',
  'ю' => 'Ю',
  'я' => 'Я',
  'ѐ' => 'Ѐ',
  'ё' => 'Ё',
  'ђ' => 'Ђ',
  'ѓ' => 'Ѓ',
  'є' => 'Є',
  'ѕ' => 'Ѕ',
  'і' => 'І',
  'ї' => 'Ї',
  'ј' => 'Ј',
  'љ' => 'Љ',
  'њ' => 'Њ',
  'ћ' => 'Ћ',
  'ќ' => 'Ќ',
  'ѝ' => 'Ѝ',
  'ў' => 'Ў',
  'џ' => 'Џ',
  'ѡ' => 'Ѡ',
  'ѣ' => 'Ѣ',
  'ѥ' => 'Ѥ',
  'ѧ' => 'Ѧ',
  'ѩ' => 'Ѩ',
  'ѫ' => 'Ѫ',
  'ѭ' => 'Ѭ',
  'ѯ' => 'Ѯ',
  'ѱ' => 'Ѱ',
  'ѳ' => 'Ѳ',
  'ѵ' => 'Ѵ',
  'ѷ' => 'Ѷ',
  'ѹ' => 'Ѹ',
  'ѻ' => 'Ѻ',
  'ѽ' => 'Ѽ',
  'ѿ' => 'Ѿ',
  'ҁ' => 'Ҁ',
  'ҋ' => 'Ҋ',
  'ҍ' => 'Ҍ',
  'ҏ' => 'Ҏ',
  'ґ' => 'Ґ',
  'ғ' => 'Ғ',
  'ҕ' => 'Ҕ',
  'җ' => 'Җ',
  'ҙ' => 'Ҙ',
  'қ' => 'Қ',
  'ҝ' => 'Ҝ',
  'ҟ' => 'Ҟ',
  'ҡ' => 'Ҡ',
  'ң' => 'Ң',
  'ҥ' => 'Ҥ',
  'ҧ' => 'Ҧ',
  'ҩ' => 'Ҩ',
  'ҫ' => 'Ҫ',
  'ҭ' => 'Ҭ',
  'ү' => 'Ү',
  'ұ' => 'Ұ',
  'ҳ' => 'Ҳ',
  'ҵ' => 'Ҵ',
  'ҷ' => 'Ҷ',
  'ҹ' => 'Ҹ',
  'һ' => 'Һ',
  'ҽ' => 'Ҽ',
  'ҿ' => 'Ҿ',
  'ӂ' => 'Ӂ',
  'ӄ' => 'Ӄ',
  'ӆ' => 'Ӆ',
  'ӈ' => 'Ӈ',
  'ӊ' => 'Ӊ',
  'ӌ' => 'Ӌ',
  'ӎ' => 'Ӎ',
  'ӏ' => 'Ӏ',
  'ӑ' => 'Ӑ',
  'ӓ' => 'Ӓ',
  'ӕ' => 'Ӕ',
  'ӗ' => 'Ӗ',
  'ә' => 'Ә',
  'ӛ' => 'Ӛ',
  'ӝ' => 'Ӝ',
  'ӟ' => 'Ӟ',
  'ӡ' => 'Ӡ',
  'ӣ' => 'Ӣ',
  'ӥ' => 'Ӥ',
  'ӧ' => 'Ӧ',
  'ө' => 'Ө',
  'ӫ' => 'Ӫ',
  'ӭ' => 'Ӭ',
  'ӯ' => 'Ӯ',
  'ӱ' => 'Ӱ',
  'ӳ' => 'Ӳ',
  'ӵ' => 'Ӵ',
  'ӷ' => 'Ӷ',
  'ӹ' => 'Ӹ',
  'ӻ' => 'Ӻ',
  'ӽ' => 'Ӽ',
  'ӿ' => 'Ӿ',
  'ԁ' => 'Ԁ',
  'ԃ' => 'Ԃ',
  'ԅ' => 'Ԅ',
  'ԇ' => 'Ԇ',
  'ԉ' => 'Ԉ',
  'ԋ' => 'Ԋ',
  'ԍ' => 'Ԍ',
  'ԏ' => 'Ԏ',
  'ԑ' => 'Ԑ',
  'ԓ' => 'Ԓ',
  'ԕ' => 'Ԕ',
  'ԗ' => 'Ԗ',
  'ԙ' => 'Ԙ',
  'ԛ' => 'Ԛ',
  'ԝ' => 'Ԝ',
  'ԟ' => 'Ԟ',
  'ԡ' => 'Ԡ',
  'ԣ' => 'Ԣ',
  'ԥ' => 'Ԥ',
  'ԧ' => 'Ԧ',
  'ԩ' => 'Ԩ',
  'ԫ' => 'Ԫ',
  'ԭ' => 'Ԭ',
  'ԯ' => 'Ԯ',
  'ա' => 'Ա',
  'բ' => 'Բ',
  'գ' => 'Գ',
  'դ' => 'Դ',
  'ե' => 'Ե',
  'զ' => 'Զ',
  'է' => 'Է',
  'ը' => 'Ը',
  'թ' => 'Թ',
  'ժ' => 'Ժ',
  'ի' => 'Ի',
  'լ' => 'Լ',
  'խ' => 'Խ',
  'ծ' => 'Ծ',
  'կ' => 'Կ',
  'հ' => 'Հ',
  'ձ' => 'Ձ',
  'ղ' => 'Ղ',
  'ճ' => 'Ճ',
  'մ' => 'Մ',
  'յ' => 'Յ',
  'ն' => 'Ն',
  'շ' => 'Շ',
  'ո' => 'Ո',
  'չ' => 'Չ',
  'պ' => 'Պ',
  'ջ' => 'Ջ',
  'ռ' => 'Ռ',
  'ս' => 'Ս',
  'վ' => 'Վ',
  'տ' => 'Տ',
  'ր' => 'Ր',
  'ց' => 'Ց',
  'ւ' => 'Ւ',
  'փ' => 'Փ',
  'ք' => 'Ք',
  'օ' => 'Օ',
  'ֆ' => 'Ֆ',
  'ა' => 'Ა',
  'ბ' => 'Ბ',
  'გ' => 'Გ',
  'დ' => 'Დ',
  'ე' => 'Ე',
  'ვ' => 'Ვ',
  'ზ' => 'Ზ',
  'თ' => 'Თ',
  'ი' => 'Ი',
  'კ' => 'Კ',
  'ლ' => 'Ლ',
  'მ' => 'Მ',
  'ნ' => 'Ნ',
  'ო' => 'Ო',
  'პ' => 'Პ',
  'ჟ' => 'Ჟ',
  'რ' => 'Რ',
  'ს' => 'Ს',
  'ტ' => 'Ტ',
  'უ' => 'Უ',
  'ფ' => 'Ფ',
  'ქ' => 'Ქ',
  'ღ' => 'Ღ',
  'ყ' => 'Ყ',
  'შ' => 'Შ',
  'ჩ' => 'Ჩ',
  'ც' => 'Ც',
  'ძ' => 'Ძ',
  'წ' => 'Წ',
  'ჭ' => 'Ჭ',
  'ხ' => 'Ხ',
  'ჯ' => 'Ჯ',
  'ჰ' => 'Ჰ',
  'ჱ' => 'Ჱ',
  'ჲ' => 'Ჲ',
  'ჳ' => 'Ჳ',
  'ჴ' => 'Ჴ',
  'ჵ' => 'Ჵ',
  'ჶ' => 'Ჶ',
  'ჷ' => 'Ჷ',
  'ჸ' => 'Ჸ',
  'ჹ' => 'Ჹ',
  'ჺ' => 'Ჺ',
  'ჽ' => 'Ჽ',
  'ჾ' => 'Ჾ',
  'ჿ' => 'Ჿ',
  'ᏸ' => 'Ᏸ',
  'ᏹ' => 'Ᏹ',
  'ᏺ' => 'Ᏺ',
  'ᏻ' => 'Ᏻ',
  'ᏼ' => 'Ᏼ',
  'ᏽ' => 'Ᏽ',
  'ᲀ' => 'В',
  'ᲁ' => 'Д',
  'ᲂ' => 'О',
  'ᲃ' => 'С',
  'ᲄ' => 'Т',
  'ᲅ' => 'Т',
  'ᲆ' => 'Ъ',
  'ᲇ' => 'Ѣ',
  'ᲈ' => 'Ꙋ',
  'ᵹ' => 'Ᵹ',
  'ᵽ' => 'Ᵽ',
  'ᶎ' => 'Ᶎ',
  'ḁ' => 'Ḁ',
  'ḃ' => 'Ḃ',
  'ḅ' => 'Ḅ',
  'ḇ' => 'Ḇ',
  'ḉ' => 'Ḉ',
  'ḋ' => 'Ḋ',
  'ḍ' => 'Ḍ',
  'ḏ' => 'Ḏ',
  'ḑ' => 'Ḑ',
  'ḓ' => 'Ḓ',
  'ḕ' => 'Ḕ',
  'ḗ' => 'Ḗ',
  'ḙ' => 'Ḙ',
  'ḛ' => 'Ḛ',
  'ḝ' => 'Ḝ',
  'ḟ' => 'Ḟ',
  'ḡ' => 'Ḡ',
  'ḣ' => 'Ḣ',
  'ḥ' => 'Ḥ',
  'ḧ' => 'Ḧ',
  'ḩ' => 'Ḩ',
  'ḫ' => 'Ḫ',
  'ḭ' => 'Ḭ',
  'ḯ' => 'Ḯ',
  'ḱ' => 'Ḱ',
  'ḳ' => 'Ḳ',
  'ḵ' => 'Ḵ',
  'ḷ' => 'Ḷ',
  'ḹ' => 'Ḹ',
  'ḻ' => 'Ḻ',
  'ḽ' => 'Ḽ',
  'ḿ' => 'Ḿ',
  'ṁ' => 'Ṁ',
  'ṃ' => 'Ṃ',
  'ṅ' => 'Ṅ',
  'ṇ' => 'Ṇ',
  'ṉ' => 'Ṉ',
  'ṋ' => 'Ṋ',
  'ṍ' => 'Ṍ',
  'ṏ' => 'Ṏ',
  'ṑ' => 'Ṑ',
  'ṓ' => 'Ṓ',
  'ṕ' => 'Ṕ',
  'ṗ' => 'Ṗ',
  'ṙ' => 'Ṙ',
  'ṛ' => 'Ṛ',
  'ṝ' => 'Ṝ',
  'ṟ' => 'Ṟ',
  'ṡ' => 'Ṡ',
  'ṣ' => 'Ṣ',
  'ṥ' => 'Ṥ',
  'ṧ' => 'Ṧ',
  'ṩ' => 'Ṩ',
  'ṫ' => 'Ṫ',
  'ṭ' => 'Ṭ',
  'ṯ' => 'Ṯ',
  'ṱ' => 'Ṱ',
  'ṳ' => 'Ṳ',
  'ṵ' => 'Ṵ',
  'ṷ' => 'Ṷ',
  'ṹ' => 'Ṹ',
  'ṻ' => 'Ṻ',
  'ṽ' => 'Ṽ',
  'ṿ' => 'Ṿ',
  'ẁ' => 'Ẁ',
  'ẃ' => 'Ẃ',
  'ẅ' => 'Ẅ',
  'ẇ' => 'Ẇ',
  'ẉ' => 'Ẉ',
  'ẋ' => 'Ẋ',
  'ẍ' => 'Ẍ',
  'ẏ' => 'Ẏ',
  'ẑ' => 'Ẑ',
  'ẓ' => 'Ẓ',
  'ẕ' => 'Ẕ',
  'ẛ' => 'Ṡ',
  'ạ' => 'Ạ',
  'ả' => 'Ả',
  'ấ' => 'Ấ',
  'ầ' => 'Ầ',
  'ẩ' => 'Ẩ',
  'ẫ' => 'Ẫ',
  'ậ' => 'Ậ',
  'ắ' => 'Ắ',
  'ằ' => 'Ằ',
  'ẳ' => 'Ẳ',
  'ẵ' => 'Ẵ',
  'ặ' => 'Ặ',
  'ẹ' => 'Ẹ',
  'ẻ' => 'Ẻ',
  'ẽ' => 'Ẽ',
  'ế' => 'Ế',
  'ề' => 'Ề',
  'ể' => 'Ể',
  'ễ' => 'Ễ',
  'ệ' => 'Ệ',
  'ỉ' => 'Ỉ',
  'ị' => 'Ị',
  'ọ' => 'Ọ',
  'ỏ' => 'Ỏ',
  'ố' => 'Ố',
  'ồ' => 'Ồ',
  'ổ' => 'Ổ',
  'ỗ' => 'Ỗ',
  'ộ' => 'Ộ',
  'ớ' => 'Ớ',
  'ờ' => 'Ờ',
  'ở' => 'Ở',
  'ỡ' => 'Ỡ',
  'ợ' => 'Ợ',
  'ụ' => 'Ụ',
  'ủ' => 'Ủ',
  'ứ' => 'Ứ',
  'ừ' => 'Ừ',
  'ử' => 'Ử',
  'ữ' => 'Ữ',
  'ự' => 'Ự',
  'ỳ' => 'Ỳ',
  'ỵ' => 'Ỵ',
  'ỷ' => 'Ỷ',
  'ỹ' => 'Ỹ',
  'ỻ' => 'Ỻ',
  'ỽ' => 'Ỽ',
  'ỿ' => 'Ỿ',
  'ἀ' => 'Ἀ',
  'ἁ' => 'Ἁ',
  'ἂ' => 'Ἂ',
  'ἃ' => 'Ἃ',
  'ἄ' => 'Ἄ',
  'ἅ' => 'Ἅ',
  'ἆ' => 'Ἆ',
  'ἇ' => 'Ἇ',
  'ἐ' => 'Ἐ',
  'ἑ' => 'Ἑ',
  'ἒ' => 'Ἒ',
  'ἓ' => 'Ἓ',
  'ἔ' => 'Ἔ',
  'ἕ' => 'Ἕ',
  'ἠ' => 'Ἠ',
  'ἡ' => 'Ἡ',
  'ἢ' => 'Ἢ',
  'ἣ' => 'Ἣ',
  'ἤ' => 'Ἤ',
  'ἥ' => 'Ἥ',
  'ἦ' => 'Ἦ',
  'ἧ' => 'Ἧ',
  'ἰ' => 'Ἰ',
  'ἱ' => 'Ἱ',
  'ἲ' => 'Ἲ',
  'ἳ' => 'Ἳ',
  'ἴ' => 'Ἴ',
  'ἵ' => 'Ἵ',
  'ἶ' => 'Ἶ',
  'ἷ' => 'Ἷ',
  'ὀ' => 'Ὀ',
  'ὁ' => 'Ὁ',
  'ὂ' => 'Ὂ',
  'ὃ' => 'Ὃ',
  'ὄ' => 'Ὄ',
  'ὅ' => 'Ὅ',
  'ὑ' => 'Ὑ',
  'ὓ' => 'Ὓ',
  'ὕ' => 'Ὕ',
  'ὗ' => 'Ὗ',
  'ὠ' => 'Ὠ',
  'ὡ' => 'Ὡ',
  'ὢ' => 'Ὢ',
  'ὣ' => 'Ὣ',
  'ὤ' => 'Ὤ',
  'ὥ' => 'Ὥ',
  'ὦ' => 'Ὦ',
  'ὧ' => 'Ὧ',
  'ὰ' => 'Ὰ',
  'ά' => 'Ά',
  'ὲ' => 'Ὲ',
  'έ' => 'Έ',
  'ὴ' => 'Ὴ',
  'ή' => 'Ή',
  'ὶ' => 'Ὶ',
  'ί' => 'Ί',
  'ὸ' => 'Ὸ',
  'ό' => 'Ό',
  'ὺ' => 'Ὺ',
  'ύ' => 'Ύ',
  'ὼ' => 'Ὼ',
  'ώ' => 'Ώ',
  'ᾀ' => 'ἈΙ',
  'ᾁ' => 'ἉΙ',
  'ᾂ' => 'ἊΙ',
  'ᾃ' => 'ἋΙ',
  'ᾄ' => 'ἌΙ',
  'ᾅ' => 'ἍΙ',
  'ᾆ' => 'ἎΙ',
  'ᾇ' => 'ἏΙ',
  'ᾐ' => 'ἨΙ',
  'ᾑ' => 'ἩΙ',
  'ᾒ' => 'ἪΙ',
  'ᾓ' => 'ἫΙ',
  'ᾔ' => 'ἬΙ',
  'ᾕ' => 'ἭΙ',
  'ᾖ' => 'ἮΙ',
  'ᾗ' => 'ἯΙ',
  'ᾠ' => 'ὨΙ',
  'ᾡ' => 'ὩΙ',
  'ᾢ' => 'ὪΙ',
  'ᾣ' => 'ὫΙ',
  'ᾤ' => 'ὬΙ',
  'ᾥ' => 'ὭΙ',
  'ᾦ' => 'ὮΙ',
  'ᾧ' => 'ὯΙ',
  'ᾰ' => 'Ᾰ',
  'ᾱ' => 'Ᾱ',
  'ᾳ' => 'ΑΙ',
  'ι' => 'Ι',
  'ῃ' => 'ΗΙ',
  'ῐ' => 'Ῐ',
  'ῑ' => 'Ῑ',
  'ῠ' => 'Ῠ',
  'ῡ' => 'Ῡ',
  'ῥ' => 'Ῥ',
  'ῳ' => 'ΩΙ',
  'ⅎ' => 'Ⅎ',
  'ⅰ' => 'Ⅰ',
  'ⅱ' => 'Ⅱ',
  'ⅲ' => 'Ⅲ',
  'ⅳ' => 'Ⅳ',
  'ⅴ' => 'Ⅴ',
  'ⅵ' => 'Ⅵ',
  'ⅶ' => 'Ⅶ',
  'ⅷ' => 'Ⅷ',
  'ⅸ' => 'Ⅸ',
  'ⅹ' => 'Ⅹ',
  'ⅺ' => 'Ⅺ',
  'ⅻ' => 'Ⅻ',
  'ⅼ' => 'Ⅼ',
  'ⅽ' => 'Ⅽ',
  'ⅾ' => 'Ⅾ',
  'ⅿ' => 'Ⅿ',
  'ↄ' => 'Ↄ',
  'ⓐ' => 'Ⓐ',
  'ⓑ' => 'Ⓑ',
  'ⓒ' => 'Ⓒ',
  'ⓓ' => 'Ⓓ',
  'ⓔ' => 'Ⓔ',
  'ⓕ' => 'Ⓕ',
  'ⓖ' => 'Ⓖ',
  'ⓗ' => 'Ⓗ',
  'ⓘ' => 'Ⓘ',
  'ⓙ' => 'Ⓙ',
  'ⓚ' => 'Ⓚ',
  'ⓛ' => 'Ⓛ',
  'ⓜ' => 'Ⓜ',
  'ⓝ' => 'Ⓝ',
  'ⓞ' => 'Ⓞ',
  'ⓟ' => 'Ⓟ',
  'ⓠ' => 'Ⓠ',
  'ⓡ' => 'Ⓡ',
  'ⓢ' => 'Ⓢ',
  'ⓣ' => 'Ⓣ',
  'ⓤ' => 'Ⓤ',
  'ⓥ' => 'Ⓥ',
  'ⓦ' => 'Ⓦ',
  'ⓧ' => 'Ⓧ',
  'ⓨ' => 'Ⓨ',
  'ⓩ' => 'Ⓩ',
  'ⰰ' => 'Ⰰ',
  'ⰱ' => 'Ⰱ',
  'ⰲ' => 'Ⰲ',
  'ⰳ' => 'Ⰳ',
  'ⰴ' => 'Ⰴ',
  'ⰵ' => 'Ⰵ',
  'ⰶ' => 'Ⰶ',
  'ⰷ' => 'Ⰷ',
  'ⰸ' => 'Ⰸ',
  'ⰹ' => 'Ⰹ',
  'ⰺ' => 'Ⰺ',
  'ⰻ' => 'Ⰻ',
  'ⰼ' => 'Ⰼ',
  'ⰽ' => 'Ⰽ',
  'ⰾ' => 'Ⰾ',
  'ⰿ' => 'Ⰿ',
  'ⱀ' => 'Ⱀ',
  'ⱁ' => 'Ⱁ',
  'ⱂ' => 'Ⱂ',
  'ⱃ' => 'Ⱃ',
  'ⱄ' => 'Ⱄ',
  'ⱅ' => 'Ⱅ',
  'ⱆ' => 'Ⱆ',
  'ⱇ' => 'Ⱇ',
  'ⱈ' => 'Ⱈ',
  'ⱉ' => 'Ⱉ',
  'ⱊ' => 'Ⱊ',
  'ⱋ' => 'Ⱋ',
  'ⱌ' => 'Ⱌ',
  'ⱍ' => 'Ⱍ',
  'ⱎ' => 'Ⱎ',
  'ⱏ' => 'Ⱏ',
  'ⱐ' => 'Ⱐ',
  'ⱑ' => 'Ⱑ',
  'ⱒ' => 'Ⱒ',
  'ⱓ' => 'Ⱓ',
  'ⱔ' => 'Ⱔ',
  'ⱕ' => 'Ⱕ',
  'ⱖ' => 'Ⱖ',
  'ⱗ' => 'Ⱗ',
  'ⱘ' => 'Ⱘ',
  'ⱙ' => 'Ⱙ',
  'ⱚ' => 'Ⱚ',
  'ⱛ' => 'Ⱛ',
  'ⱜ' => 'Ⱜ',
  'ⱝ' => 'Ⱝ',
  'ⱞ' => 'Ⱞ',
  'ⱡ' => 'Ⱡ',
  'ⱥ' => 'Ⱥ',
  'ⱦ' => 'Ⱦ',
  'ⱨ' => 'Ⱨ',
  'ⱪ' => 'Ⱪ',
  'ⱬ' => 'Ⱬ',
  'ⱳ' => 'Ⱳ',
  'ⱶ' => 'Ⱶ',
  'ⲁ' => 'Ⲁ',
  'ⲃ' => 'Ⲃ',
  'ⲅ' => 'Ⲅ',
  'ⲇ' => 'Ⲇ',
  'ⲉ' => 'Ⲉ',
  'ⲋ' => 'Ⲋ',
  'ⲍ' => 'Ⲍ',
  'ⲏ' => 'Ⲏ',
  'ⲑ' => 'Ⲑ',
  'ⲓ' => 'Ⲓ',
  'ⲕ' => 'Ⲕ',
  'ⲗ' => 'Ⲗ',
  'ⲙ' => 'Ⲙ',
  'ⲛ' => 'Ⲛ',
  'ⲝ' => 'Ⲝ',
  'ⲟ' => 'Ⲟ',
  'ⲡ' => 'Ⲡ',
  'ⲣ' => 'Ⲣ',
  'ⲥ' => 'Ⲥ',
  'ⲧ' => 'Ⲧ',
  'ⲩ' => 'Ⲩ',
  'ⲫ' => 'Ⲫ',
  'ⲭ' => 'Ⲭ',
  'ⲯ' => 'Ⲯ',
  'ⲱ' => 'Ⲱ',
  'ⲳ' => 'Ⲳ',
  'ⲵ' => 'Ⲵ',
  'ⲷ' => 'Ⲷ',
  'ⲹ' => 'Ⲹ',
  'ⲻ' => 'Ⲻ',
  'ⲽ' => 'Ⲽ',
  'ⲿ' => 'Ⲿ',
  'ⳁ' => 'Ⳁ',
  'ⳃ' => 'Ⳃ',
  'ⳅ' => 'Ⳅ',
  'ⳇ' => 'Ⳇ',
  'ⳉ' => 'Ⳉ',
  'ⳋ' => 'Ⳋ',
  'ⳍ' => 'Ⳍ',
  'ⳏ' => 'Ⳏ',
  'ⳑ' => 'Ⳑ',
  'ⳓ' => 'Ⳓ',
  'ⳕ' => 'Ⳕ',
  'ⳗ' => 'Ⳗ',
  'ⳙ' => 'Ⳙ',
  'ⳛ' => 'Ⳛ',
  'ⳝ' => 'Ⳝ',
  'ⳟ' => 'Ⳟ',
  'ⳡ' => 'Ⳡ',
  'ⳣ' => 'Ⳣ',
  'ⳬ' => 'Ⳬ',
  'ⳮ' => 'Ⳮ',
  'ⳳ' => 'Ⳳ',
  'ⴀ' => 'Ⴀ',
  'ⴁ' => 'Ⴁ',
  'ⴂ' => 'Ⴂ',
  'ⴃ' => 'Ⴃ',
  'ⴄ' => 'Ⴄ',
  'ⴅ' => 'Ⴅ',
  'ⴆ' => 'Ⴆ',
  'ⴇ' => 'Ⴇ',
  'ⴈ' => 'Ⴈ',
  'ⴉ' => 'Ⴉ',
  'ⴊ' => 'Ⴊ',
  'ⴋ' => 'Ⴋ',
  'ⴌ' => 'Ⴌ',
  'ⴍ' => 'Ⴍ',
  'ⴎ' => 'Ⴎ',
  'ⴏ' => 'Ⴏ',
  'ⴐ' => 'Ⴐ',
  'ⴑ' => 'Ⴑ',
  'ⴒ' => 'Ⴒ',
  'ⴓ' => 'Ⴓ',
  'ⴔ' => 'Ⴔ',
  'ⴕ' => 'Ⴕ',
  'ⴖ' => 'Ⴖ',
  'ⴗ' => 'Ⴗ',
  'ⴘ' => 'Ⴘ',
  'ⴙ' => 'Ⴙ',
  'ⴚ' => 'Ⴚ',
  'ⴛ' => 'Ⴛ',
  'ⴜ' => 'Ⴜ',
  'ⴝ' => 'Ⴝ',
  'ⴞ' => 'Ⴞ',
  'ⴟ' => 'Ⴟ',
  'ⴠ' => 'Ⴠ',
  'ⴡ' => 'Ⴡ',
  'ⴢ' => 'Ⴢ',
  'ⴣ' => 'Ⴣ',
  'ⴤ' => 'Ⴤ',
  'ⴥ' => 'Ⴥ',
  'ⴧ' => 'Ⴧ',
  'ⴭ' => 'Ⴭ',
  'ꙁ' => 'Ꙁ',
  'ꙃ' => 'Ꙃ',
  'ꙅ' => 'Ꙅ',
  'ꙇ' => 'Ꙇ',
  'ꙉ' => 'Ꙉ',
  'ꙋ' => 'Ꙋ',
  'ꙍ' => 'Ꙍ',
  'ꙏ' => 'Ꙏ',
  'ꙑ' => 'Ꙑ',
  'ꙓ' => 'Ꙓ',
  'ꙕ' => 'Ꙕ',
  'ꙗ' => 'Ꙗ',
  'ꙙ' => 'Ꙙ',
  'ꙛ' => 'Ꙛ',
  'ꙝ' => 'Ꙝ',
  'ꙟ' => 'Ꙟ',
  'ꙡ' => 'Ꙡ',
  'ꙣ' => 'Ꙣ',
  'ꙥ' => 'Ꙥ',
  'ꙧ' => 'Ꙧ',
  'ꙩ' => 'Ꙩ',
  'ꙫ' => 'Ꙫ',
  'ꙭ' => 'Ꙭ',
  'ꚁ' => 'Ꚁ',
  'ꚃ' => 'Ꚃ',
  'ꚅ' => 'Ꚅ',
  'ꚇ' => 'Ꚇ',
  'ꚉ' => 'Ꚉ',
  'ꚋ' => 'Ꚋ',
  'ꚍ' => 'Ꚍ',
  'ꚏ' => 'Ꚏ',
  'ꚑ' => 'Ꚑ',
  'ꚓ' => 'Ꚓ',
  'ꚕ' => 'Ꚕ',
  'ꚗ' => 'Ꚗ',
  'ꚙ' => 'Ꚙ',
  'ꚛ' => 'Ꚛ',
  'ꜣ' => 'Ꜣ',
  'ꜥ' => 'Ꜥ',
  'ꜧ' => 'Ꜧ',
  'ꜩ' => 'Ꜩ',
  'ꜫ' => 'Ꜫ',
  'ꜭ' => 'Ꜭ',
  'ꜯ' => 'Ꜯ',
  'ꜳ' => 'Ꜳ',
  'ꜵ' => 'Ꜵ',
  'ꜷ' => 'Ꜷ',
  'ꜹ' => 'Ꜹ',
  'ꜻ' => 'Ꜻ',
  'ꜽ' => 'Ꜽ',
  'ꜿ' => 'Ꜿ',
  'ꝁ' => 'Ꝁ',
  'ꝃ' => 'Ꝃ',
  'ꝅ' => 'Ꝅ',
  'ꝇ' => 'Ꝇ',
  'ꝉ' => 'Ꝉ',
  'ꝋ' => 'Ꝋ',
  'ꝍ' => 'Ꝍ',
  'ꝏ' => 'Ꝏ',
  'ꝑ' => 'Ꝑ',
  'ꝓ' => 'Ꝓ',
  'ꝕ' => 'Ꝕ',
  'ꝗ' => 'Ꝗ',
  'ꝙ' => 'Ꝙ',
  'ꝛ' => 'Ꝛ',
  'ꝝ' => 'Ꝝ',
  'ꝟ' => 'Ꝟ',
  'ꝡ' => 'Ꝡ',
  'ꝣ' => 'Ꝣ',
  'ꝥ' => 'Ꝥ',
  'ꝧ' => 'Ꝧ',
  'ꝩ' => 'Ꝩ',
  'ꝫ' => 'Ꝫ',
  'ꝭ' => 'Ꝭ',
  'ꝯ' => 'Ꝯ',
  'ꝺ' => 'Ꝺ',
  'ꝼ' => 'Ꝼ',
  'ꝿ' => 'Ꝿ',
  'ꞁ' => 'Ꞁ',
  'ꞃ' => 'Ꞃ',
  'ꞅ' => 'Ꞅ',
  'ꞇ' => 'Ꞇ',
  'ꞌ' => 'Ꞌ',
  'ꞑ' => 'Ꞑ',
  'ꞓ' => 'Ꞓ',
  'ꞔ' => 'Ꞔ',
  'ꞗ' => 'Ꞗ',
  'ꞙ' => 'Ꞙ',
  'ꞛ' => 'Ꞛ',
  'ꞝ' => 'Ꞝ',
  'ꞟ' => 'Ꞟ',
  'ꞡ' => 'Ꞡ',
  'ꞣ' => 'Ꞣ',
  'ꞥ' => 'Ꞥ',
  'ꞧ' => 'Ꞧ',
  'ꞩ' => 'Ꞩ',
  'ꞵ' => 'Ꞵ',
  'ꞷ' => 'Ꞷ',
  'ꞹ' => 'Ꞹ',
  'ꞻ' => 'Ꞻ',
  'ꞽ' => 'Ꞽ',
  'ꞿ' => 'Ꞿ',
  'ꟃ' => 'Ꟃ',
  'ꟈ' => 'Ꟈ',
  'ꟊ' => 'Ꟊ',
  'ꟶ' => 'Ꟶ',
  'ꭓ' => 'Ꭓ',
  'ꭰ' => 'Ꭰ',
  'ꭱ' => 'Ꭱ',
  'ꭲ' => 'Ꭲ',
  'ꭳ' => 'Ꭳ',
  'ꭴ' => 'Ꭴ',
  'ꭵ' => 'Ꭵ',
  'ꭶ' => 'Ꭶ',
  'ꭷ' => 'Ꭷ',
  'ꭸ' => 'Ꭸ',
  'ꭹ' => 'Ꭹ',
  'ꭺ' => 'Ꭺ',
  'ꭻ' => 'Ꭻ',
  'ꭼ' => 'Ꭼ',
  'ꭽ' => 'Ꭽ',
  'ꭾ' => 'Ꭾ',
  'ꭿ' => 'Ꭿ',
  'ꮀ' => 'Ꮀ',
  'ꮁ' => 'Ꮁ',
  'ꮂ' => 'Ꮂ',
  'ꮃ' => 'Ꮃ',
  'ꮄ' => 'Ꮄ',
  'ꮅ' => 'Ꮅ',
  'ꮆ' => 'Ꮆ',
  'ꮇ' => 'Ꮇ',
  'ꮈ' => 'Ꮈ',
  'ꮉ' => 'Ꮉ',
  'ꮊ' => 'Ꮊ',
  'ꮋ' => 'Ꮋ',
  'ꮌ' => 'Ꮌ',
  'ꮍ' => 'Ꮍ',
  'ꮎ' => 'Ꮎ',
  'ꮏ' => 'Ꮏ',
  'ꮐ' => 'Ꮐ',
  'ꮑ' => 'Ꮑ',
  'ꮒ' => 'Ꮒ',
  'ꮓ' => 'Ꮓ',
  'ꮔ' => 'Ꮔ',
  'ꮕ' => 'Ꮕ',
  'ꮖ' => 'Ꮖ',
  'ꮗ' => 'Ꮗ',
  'ꮘ' => 'Ꮘ',
  'ꮙ' => 'Ꮙ',
  'ꮚ' => 'Ꮚ',
  'ꮛ' => 'Ꮛ',
  'ꮜ' => 'Ꮜ',
  'ꮝ' => 'Ꮝ',
  'ꮞ' => 'Ꮞ',
  'ꮟ' => 'Ꮟ',
  'ꮠ' => 'Ꮠ',
  'ꮡ' => 'Ꮡ',
  'ꮢ' => 'Ꮢ',
  'ꮣ' => 'Ꮣ',
  'ꮤ' => 'Ꮤ',
  'ꮥ' => 'Ꮥ',
  'ꮦ' => 'Ꮦ',
  'ꮧ' => 'Ꮧ',
  'ꮨ' => 'Ꮨ',
  'ꮩ' => 'Ꮩ',
  'ꮪ' => 'Ꮪ',
  'ꮫ' => 'Ꮫ',
  'ꮬ' => 'Ꮬ',
  'ꮭ' => 'Ꮭ',
  'ꮮ' => 'Ꮮ',
  'ꮯ' => 'Ꮯ',
  'ꮰ' => 'Ꮰ',
  'ꮱ' => 'Ꮱ',
  'ꮲ' => 'Ꮲ',
  'ꮳ' => 'Ꮳ',
  'ꮴ' => 'Ꮴ',
  'ꮵ' => 'Ꮵ',
  'ꮶ' => 'Ꮶ',
  'ꮷ' => 'Ꮷ',
  'ꮸ' => 'Ꮸ',
  'ꮹ' => 'Ꮹ',
  'ꮺ' => 'Ꮺ',
  'ꮻ' => 'Ꮻ',
  'ꮼ' => 'Ꮼ',
  'ꮽ' => 'Ꮽ',
  'ꮾ' => 'Ꮾ',
  'ꮿ' => 'Ꮿ',
  'a' => 'A',
  'b' => 'B',
  'c' => 'C',
  'd' => 'D',
  'e' => 'E',
  'f' => 'F',
  'g' => 'G',
  'h' => 'H',
  'i' => 'I',
  'j' => 'J',
  'k' => 'K',
  'l' => 'L',
  'm' => 'M',
  'n' => 'N',
  'o' => 'O',
  'p' => 'P',
  'q' => 'Q',
  'r' => 'R',
  's' => 'S',
  't' => 'T',
  'u' => 'U',
  'v' => 'V',
  'w' => 'W',
  'x' => 'X',
  'y' => 'Y',
  'z' => 'Z',
  '𐐨' => '𐐀',
  '𐐩' => '𐐁',
  '𐐪' => '𐐂',
  '𐐫' => '𐐃',
  '𐐬' => '𐐄',
  '𐐭' => '𐐅',
  '𐐮' => '𐐆',
  '𐐯' => '𐐇',
  '𐐰' => '𐐈',
  '𐐱' => '𐐉',
  '𐐲' => '𐐊',
  '𐐳' => '𐐋',
  '𐐴' => '𐐌',
  '𐐵' => '𐐍',
  '𐐶' => '𐐎',
  '𐐷' => '𐐏',
  '𐐸' => '𐐐',
  '𐐹' => '𐐑',
  '𐐺' => '𐐒',
  '𐐻' => '𐐓',
  '𐐼' => '𐐔',
  '𐐽' => '𐐕',
  '𐐾' => '𐐖',
  '𐐿' => '𐐗',
  '𐑀' => '𐐘',
  '𐑁' => '𐐙',
  '𐑂' => '𐐚',
  '𐑃' => '𐐛',
  '𐑄' => '𐐜',
  '𐑅' => '𐐝',
  '𐑆' => '𐐞',
  '𐑇' => '𐐟',
  '𐑈' => '𐐠',
  '𐑉' => '𐐡',
  '𐑊' => '𐐢',
  '𐑋' => '𐐣',
  '𐑌' => '𐐤',
  '𐑍' => '𐐥',
  '𐑎' => '𐐦',
  '𐑏' => '𐐧',
  '𐓘' => '𐒰',
  '𐓙' => '𐒱',
  '𐓚' => '𐒲',
  '𐓛' => '𐒳',
  '𐓜' => '𐒴',
  '𐓝' => '𐒵',
  '𐓞' => '𐒶',
  '𐓟' => '𐒷',
  '𐓠' => '𐒸',
  '𐓡' => '𐒹',
  '𐓢' => '𐒺',
  '𐓣' => '𐒻',
  '𐓤' => '𐒼',
  '𐓥' => '𐒽',
  '𐓦' => '𐒾',
  '𐓧' => '𐒿',
  '𐓨' => '𐓀',
  '𐓩' => '𐓁',
  '𐓪' => '𐓂',
  '𐓫' => '𐓃',
  '𐓬' => '𐓄',
  '𐓭' => '𐓅',
  '𐓮' => '𐓆',
  '𐓯' => '𐓇',
  '𐓰' => '𐓈',
  '𐓱' => '𐓉',
  '𐓲' => '𐓊',
  '𐓳' => '𐓋',
  '𐓴' => '𐓌',
  '𐓵' => '𐓍',
  '𐓶' => '𐓎',
  '𐓷' => '𐓏',
  '𐓸' => '𐓐',
  '𐓹' => '𐓑',
  '𐓺' => '𐓒',
  '𐓻' => '𐓓',
  '𐳀' => '𐲀',
  '𐳁' => '𐲁',
  '𐳂' => '𐲂',
  '𐳃' => '𐲃',
  '𐳄' => '𐲄',
  '𐳅' => '𐲅',
  '𐳆' => '𐲆',
  '𐳇' => '𐲇',
  '𐳈' => '𐲈',
  '𐳉' => '𐲉',
  '𐳊' => '𐲊',
  '𐳋' => '𐲋',
  '𐳌' => '𐲌',
  '𐳍' => '𐲍',
  '𐳎' => '𐲎',
  '𐳏' => '𐲏',
  '𐳐' => '𐲐',
  '𐳑' => '𐲑',
  '𐳒' => '𐲒',
  '𐳓' => '𐲓',
  '𐳔' => '𐲔',
  '𐳕' => '𐲕',
  '𐳖' => '𐲖',
  '𐳗' => '𐲗',
  '𐳘' => '𐲘',
  '𐳙' => '𐲙',
  '𐳚' => '𐲚',
  '𐳛' => '𐲛',
  '𐳜' => '𐲜',
  '𐳝' => '𐲝',
  '𐳞' => '𐲞',
  '𐳟' => '𐲟',
  '𐳠' => '𐲠',
  '𐳡' => '𐲡',
  '𐳢' => '𐲢',
  '𐳣' => '𐲣',
  '𐳤' => '𐲤',
  '𐳥' => '𐲥',
  '𐳦' => '𐲦',
  '𐳧' => '𐲧',
  '𐳨' => '𐲨',
  '𐳩' => '𐲩',
  '𐳪' => '𐲪',
  '𐳫' => '𐲫',
  '𐳬' => '𐲬',
  '𐳭' => '𐲭',
  '𐳮' => '𐲮',
  '𐳯' => '𐲯',
  '𐳰' => '𐲰',
  '𐳱' => '𐲱',
  '𐳲' => '𐲲',
  '𑣀' => '𑢠',
  '𑣁' => '𑢡',
  '𑣂' => '𑢢',
  '𑣃' => '𑢣',
  '𑣄' => '𑢤',
  '𑣅' => '𑢥',
  '𑣆' => '𑢦',
  '𑣇' => '𑢧',
  '𑣈' => '𑢨',
  '𑣉' => '𑢩',
  '𑣊' => '𑢪',
  '𑣋' => '𑢫',
  '𑣌' => '𑢬',
  '𑣍' => '𑢭',
  '𑣎' => '𑢮',
  '𑣏' => '𑢯',
  '𑣐' => '𑢰',
  '𑣑' => '𑢱',
  '𑣒' => '𑢲',
  '𑣓' => '𑢳',
  '𑣔' => '𑢴',
  '𑣕' => '𑢵',
  '𑣖' => '𑢶',
  '𑣗' => '𑢷',
  '𑣘' => '𑢸',
  '𑣙' => '𑢹',
  '𑣚' => '𑢺',
  '𑣛' => '𑢻',
  '𑣜' => '𑢼',
  '𑣝' => '𑢽',
  '𑣞' => '𑢾',
  '𑣟' => '𑢿',
  '𖹠' => '𖹀',
  '𖹡' => '𖹁',
  '𖹢' => '𖹂',
  '𖹣' => '𖹃',
  '𖹤' => '𖹄',
  '𖹥' => '𖹅',
  '𖹦' => '𖹆',
  '𖹧' => '𖹇',
  '𖹨' => '𖹈',
  '𖹩' => '𖹉',
  '𖹪' => '𖹊',
  '𖹫' => '𖹋',
  '𖹬' => '𖹌',
  '𖹭' => '𖹍',
  '𖹮' => '𖹎',
  '𖹯' => '𖹏',
  '𖹰' => '𖹐',
  '𖹱' => '𖹑',
  '𖹲' => '𖹒',
  '𖹳' => '𖹓',
  '𖹴' => '𖹔',
  '𖹵' => '𖹕',
  '𖹶' => '𖹖',
  '𖹷' => '𖹗',
  '𖹸' => '𖹘',
  '𖹹' => '𖹙',
  '𖹺' => '𖹚',
  '𖹻' => '𖹛',
  '𖹼' => '𖹜',
  '𖹽' => '𖹝',
  '𖹾' => '𖹞',
  '𖹿' => '𖹟',
  '𞤢' => '𞤀',
  '𞤣' => '𞤁',
  '𞤤' => '𞤂',
  '𞤥' => '𞤃',
  '𞤦' => '𞤄',
  '𞤧' => '𞤅',
  '𞤨' => '𞤆',
  '𞤩' => '𞤇',
  '𞤪' => '𞤈',
  '𞤫' => '𞤉',
  '𞤬' => '𞤊',
  '𞤭' => '𞤋',
  '𞤮' => '𞤌',
  '𞤯' => '𞤍',
  '𞤰' => '𞤎',
  '𞤱' => '𞤏',
  '𞤲' => '𞤐',
  '𞤳' => '𞤑',
  '𞤴' => '𞤒',
  '𞤵' => '𞤓',
  '𞤶' => '𞤔',
  '𞤷' => '𞤕',
  '𞤸' => '𞤖',
  '𞤹' => '𞤗',
  '𞤺' => '𞤘',
  '𞤻' => '𞤙',
  '𞤼' => '𞤚',
  '𞤽' => '𞤛',
  '𞤾' => '𞤜',
  '𞤿' => '𞤝',
  '𞥀' => '𞤞',
  '𞥁' => '𞤟',
  '𞥂' => '𞤠',
  '𞥃' => '𞤡',
  'ß' => 'SS',
  'ff' => 'FF',
  'fi' => 'FI',
  'fl' => 'FL',
  'ffi' => 'FFI',
  'ffl' => 'FFL',
  'ſt' => 'ST',
  'st' => 'ST',
  'և' => 'ԵՒ',
  'ﬓ' => 'ՄՆ',
  'ﬔ' => 'ՄԵ',
  'ﬕ' => 'ՄԻ',
  'ﬖ' => 'ՎՆ',
  'ﬗ' => 'ՄԽ',
  'ʼn' => 'ʼN',
  'ΐ' => 'Ϊ́',
  'ΰ' => 'Ϋ́',
  'ǰ' => 'J̌',
  'ẖ' => 'H̱',
  'ẗ' => 'T̈',
  'ẘ' => 'W̊',
  'ẙ' => 'Y̊',
  'ẚ' => 'Aʾ',
  'ὐ' => 'Υ̓',
  'ὒ' => 'Υ̓̀',
  'ὔ' => 'Υ̓́',
  'ὖ' => 'Υ̓͂',
  'ᾶ' => 'Α͂',
  'ῆ' => 'Η͂',
  'ῒ' => 'Ϊ̀',
  'ΐ' => 'Ϊ́',
  'ῖ' => 'Ι͂',
  'ῗ' => 'Ϊ͂',
  'ῢ' => 'Ϋ̀',
  'ΰ' => 'Ϋ́',
  'ῤ' => 'Ρ̓',
  'ῦ' => 'Υ͂',
  'ῧ' => 'Ϋ͂',
  'ῶ' => 'Ω͂',
  'ᾈ' => 'ἈΙ',
  'ᾉ' => 'ἉΙ',
  'ᾊ' => 'ἊΙ',
  'ᾋ' => 'ἋΙ',
  'ᾌ' => 'ἌΙ',
  'ᾍ' => 'ἍΙ',
  'ᾎ' => 'ἎΙ',
  'ᾏ' => 'ἏΙ',
  'ᾘ' => 'ἨΙ',
  'ᾙ' => 'ἩΙ',
  'ᾚ' => 'ἪΙ',
  'ᾛ' => 'ἫΙ',
  'ᾜ' => 'ἬΙ',
  'ᾝ' => 'ἭΙ',
  'ᾞ' => 'ἮΙ',
  'ᾟ' => 'ἯΙ',
  'ᾨ' => 'ὨΙ',
  'ᾩ' => 'ὩΙ',
  'ᾪ' => 'ὪΙ',
  'ᾫ' => 'ὫΙ',
  'ᾬ' => 'ὬΙ',
  'ᾭ' => 'ὭΙ',
  'ᾮ' => 'ὮΙ',
  'ᾯ' => 'ὯΙ',
  'ᾼ' => 'ΑΙ',
  'ῌ' => 'ΗΙ',
  'ῼ' => 'ΩΙ',
  'ᾲ' => 'ᾺΙ',
  'ᾴ' => 'ΆΙ',
  'ῂ' => 'ῊΙ',
  'ῄ' => 'ΉΙ',
  'ῲ' => 'ῺΙ',
  'ῴ' => 'ΏΙ',
  'ᾷ' => 'Α͂Ι',
  'ῇ' => 'Η͂Ι',
  'ῷ' => 'Ω͂Ι',
);
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Mbstring;

/**
 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
 *
 * Implemented:
 * - mb_chr                  - Returns a specific character from its Unicode code point
 * - mb_convert_encoding     - Convert character encoding
 * - mb_convert_variables    - Convert character code in variable(s)
 * - mb_decode_mimeheader    - Decode string in MIME header field
 * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
 * - mb_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - mb_convert_case         - Perform case folding on a string
 * - mb_detect_encoding      - Detect character encoding
 * - mb_get_info             - Get internal settings of mbstring
 * - mb_http_input           - Detect HTTP input character encoding
 * - mb_http_output          - Set/Get HTTP output character encoding
 * - mb_internal_encoding    - Set/Get internal character encoding
 * - mb_list_encodings       - Returns an array of all supported encodings
 * - mb_ord                  - Returns the Unicode code point of a character
 * - mb_output_handler       - Callback function converts character encoding in output buffer
 * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
 * - mb_strlen               - Get string length
 * - mb_strpos               - Find position of first occurrence of string in a string
 * - mb_strrpos              - Find position of last occurrence of a string in a string
 * - mb_str_split            - Convert a string to an array
 * - mb_strtolower           - Make a string lowercase
 * - mb_strtoupper           - Make a string uppercase
 * - mb_substitute_character - Set/Get substitution character
 * - mb_substr               - Get part of string
 * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
 * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
 * - mb_strrchr              - Finds the last occurrence of a character in a string within another
 * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
 * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
 * - mb_strstr               - Finds first occurrence of a string within another
 * - mb_strwidth             - Return width of string
 * - mb_substr_count         - Count the number of substring occurrences
 *
 * Not implemented:
 * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
 * - mb_ereg_*               - Regular expression with multibyte support
 * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
 * - mb_preferred_mime_name  - Get MIME charset string
 * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
 * - mb_regex_set_options    - Set/Get the default options for mbregex functions
 * - mb_send_mail            - Send encoded mail
 * - mb_split                - Split multibyte string using regular expression
 * - mb_strcut               - Get part of string
 * - mb_strimwidth           - Get truncated string with specified width
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Mbstring
{
    public const MB_CASE_FOLD = \PHP_INT_MAX;

    private const CASE_FOLD = [
        ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
        ['μ', 's', 'ι',        'σ', 'β',        'θ',        'φ',        'π',        'κ',        'ρ',        'ε',        "\xE1\xB9\xA1", 'ι'],
    ];

    private static $encodingList = ['ASCII', 'UTF-8'];
    private static $language = 'neutral';
    private static $internalEncoding = 'UTF-8';

    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
    {
        if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
        } else {
            $fromEncoding = self::getEncoding($fromEncoding);
        }

        $toEncoding = self::getEncoding($toEncoding);

        if ('BASE64' === $fromEncoding) {
            $s = base64_decode($s);
            $fromEncoding = $toEncoding;
        }

        if ('BASE64' === $toEncoding) {
            return base64_encode($s);
        }

        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
                $fromEncoding = 'Windows-1252';
            }
            if ('UTF-8' !== $fromEncoding) {
                $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s);
            }

            return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
        }

        if ('HTML-ENTITIES' === $fromEncoding) {
            $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
            $fromEncoding = 'UTF-8';
        }

        return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
    }

    public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
    {
        $ok = true;
        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
            if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
                $ok = false;
            }
        });

        return $ok ? $fromEncoding : false;
    }

    public static function mb_decode_mimeheader($s)
    {
        return \iconv_mime_decode($s, 2, self::$internalEncoding);
    }

    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
    {
        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
    }

    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
    {
        if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);

            return null;
        }

        if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
            return false;
        }

        if (null !== $encoding && !is_scalar($encoding)) {
            trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);

            return '';  // Instead of null (cf. mb_encode_numericentity).
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $cnt = floor(\count($convmap) / 4) * 4;

        for ($i = 0; $i < $cnt; $i += 4) {
            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
            $convmap[$i] += $convmap[$i + 2];
            $convmap[$i + 1] += $convmap[$i + 2];
        }

        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
            for ($i = 0; $i < $cnt; $i += 4) {
                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
                    return self::mb_chr($c - $convmap[$i + 2]);
                }
            }

            return $m[0];
        }, $s);

        if (null === $encoding) {
            return $s;
        }

        return \iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
    {
        if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
            trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);

            return null;
        }

        if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
            return false;
        }

        if (null !== $encoding && !is_scalar($encoding)) {
            trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);

            return null;  // Instead of '' (cf. mb_decode_numericentity).
        }

        if (null !== $is_hex && !is_scalar($is_hex)) {
            trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);

            return null;
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

        $cnt = floor(\count($convmap) / 4) * 4;
        $i = 0;
        $len = \strlen($s);
        $result = '';

        while ($i < $len) {
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return \iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (\MB_CASE_TITLE == $mode) {
            static $titleRegexp = null;
            if (null === $titleRegexp) {
                $titleRegexp = self::getData('titleCaseRegexp');
            }
            $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
        } else {
            if (\MB_CASE_UPPER == $mode) {
                static $upper = null;
                if (null === $upper) {
                    $upper = self::getData('upperCase');
                }
                $map = $upper;
            } else {
                if (self::MB_CASE_FOLD === $mode) {
                    $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s);
                }

                static $lower = null;
                if (null === $lower) {
                    $lower = self::getData('lowerCase');
                }
                $map = $lower;
            }

            static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

            $i = 0;
            $len = \strlen($s);

            while ($i < $len) {
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = \strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return \iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $normalizedEncoding = self::getEncoding($encoding);

        if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
            self::$internalEncoding = $normalizedEncoding;

            return true;
        }

        if (80000 > \PHP_VERSION_ID) {
            return false;
        }

        throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
    }

    public static function mb_language($lang = null)
    {
        if (null === $lang) {
            return self::$language;
        }

        switch ($normalizedLang = strtolower($lang)) {
            case 'uni':
            case 'neutral':
                self::$language = $normalizedLang;

                return true;
        }

        if (80000 > \PHP_VERSION_ID) {
            return false;
        }

        throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
    }

    public static function mb_list_encodings()
    {
        return ['UTF-8'];
    }

    public static function mb_encoding_aliases($encoding)
    {
        switch (strtoupper($encoding)) {
            case 'UTF8':
            case 'UTF-8':
                return ['utf8'];
        }

        return false;
    }

    public static function mb_check_encoding($var = null, $encoding = null)
    {
        if (null === $encoding) {
            if (null === $var) {
                return false;
            }
            $encoding = self::$internalEncoding;
        }

        return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var);
    }

    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
    {
        if (null === $encodingList) {
            $encodingList = self::$encodingList;
        } else {
            if (!\is_array($encodingList)) {
                $encodingList = array_map('trim', explode(',', $encodingList));
            }
            $encodingList = array_map('strtoupper', $encodingList);
        }

        foreach ($encodingList as $enc) {
            switch ($enc) {
                case 'ASCII':
                    if (!preg_match('/[\x80-\xFF]/', $str)) {
                        return $enc;
                    }
                    break;

                case 'UTF8':
                case 'UTF-8':
                    if (preg_match('//u', $str)) {
                        return 'UTF-8';
                    }
                    break;

                default:
                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!\is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                    // no break
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return \strlen($s);
        }

        return @\iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);

                return false;
            }

            return 0;
        }

        return \iconv_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrpos($haystack, $needle, $offset);
        }

        if ($offset != (int) $offset) {
            $offset = 0;
        } elseif ($offset = (int) $offset) {
            if ($offset < 0) {
                if (0 > $offset += self::mb_strlen($needle)) {
                    $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                }
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
            }
        }

        $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
            ? \iconv_strrpos($haystack, $needle, $encoding)
            : self::mb_strlen($haystack, $encoding);

        return false !== $pos ? $offset + $pos : false;
    }

    public static function mb_str_split($string, $split_length = 1, $encoding = null)
    {
        if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
            trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);

            return null;
        }

        if (1 > $split_length = (int) $split_length) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
                return false;
            }

            throw new \ValueError('Argument #2 ($length) must be greater than 0');
        }

        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
            $rx = '/(';
            while (65535 < $split_length) {
                $rx .= '.{65535}';
                $split_length -= 65535;
            }
            $rx .= '.{'.$split_length.'})/us';

            return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
        }

        $result = [];
        $length = mb_strlen($string, $encoding);

        for ($i = 0; $i < $length; $i += $split_length) {
            $result[] = mb_substr($string, $i, $split_length, $encoding);
        }

        return $result;
    }

    public static function mb_strtolower($s, $encoding = null)
    {
        return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
    }

    public static function mb_strtoupper($s, $encoding = null)
    {
        return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
    }

    public static function mb_substitute_character($c = null)
    {
        if (null === $c) {
            return 'none';
        }
        if (0 === strcasecmp($c, 'none')) {
            return true;
        }
        if (80000 > \PHP_VERSION_ID) {
            return false;
        }

        throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
    }

    public static function mb_substr($s, $start, $length = null, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return (string) substr($s, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = \iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = \iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) \iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            $pos = strrpos($haystack, $needle);
        } else {
            $needle = self::mb_substr($needle, 0, 1, $encoding);
            $pos = \iconv_strrpos($haystack, $needle, $encoding);
        }

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = strpos($haystack, $needle);
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return substr($haystack, 0, $pos);
        }

        return substr($haystack, $pos);
    }

    public static function mb_get_info($type = 'all')
    {
        $info = [
            'internal_encoding' => self::$internalEncoding,
            'http_output' => 'pass',
            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
            'func_overload' => 0,
            'func_overload_list' => 'no overload',
            'mail_charset' => 'UTF-8',
            'mail_header_encoding' => 'BASE64',
            'mail_body_encoding' => 'BASE64',
            'illegal_chars' => 0,
            'encoding_translation' => 'Off',
            'language' => self::$language,
            'detect_order' => self::$encodingList,
            'substitute_character' => 'none',
            'strict_detection' => 'Off',
        ];

        if ('all' === $type) {
            return $info;
        }
        if (isset($info[$type])) {
            return $info[$type];
        }

        return false;
    }

    public static function mb_http_input($type = '')
    {
        return false;
    }

    public static function mb_http_output($encoding = null)
    {
        return null !== $encoding ? 'pass' === $encoding : 'pass';
    }

    public static function mb_strwidth($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);

        if ('UTF-8' !== $encoding) {
            $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);

        return ($wide << 1) + \iconv_strlen($s, 'UTF-8');
    }

    public static function mb_substr_count($haystack, $needle, $encoding = null)
    {
        return substr_count($haystack, $needle);
    }

    public static function mb_output_handler($contents, $status)
    {
        return $contents;
    }

    public static function mb_chr($code, $encoding = null)
    {
        if (0x80 > $code %= 0x200000) {
            $s = \chr($code);
        } elseif (0x800 > $code) {
            $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
        } elseif (0x10000 > $code) {
            $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        } else {
            $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        }

        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
        }

        if (1 === \strlen($s)) {
            return \ord($s);
        }

        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
        if (0xF0 <= $code) {
            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
        }
        if (0xE0 <= $code) {
            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
        }
        if (0xC0 <= $code) {
            return (($code - 0xC0) << 6) + $s[2] - 0x80;
        }

        return $code;
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= \chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    private static function title_case(array $s)
    {
        return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        if ('UTF-8' === $encoding) {
            return 'UTF-8';
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }

        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!function_exists('mb_convert_encoding')) {
    function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
    function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); }
}
if (!function_exists('mb_encode_mimeheader')) {
    function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
    function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
    function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); }
}
if (!function_exists('mb_convert_case')) {
    function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
    function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
    function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
    function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
    function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); }
}
if (!function_exists('mb_check_encoding')) {
    function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
    function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); }
}
if (!function_exists('mb_detect_order')) {
    function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
    function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
    function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); }
}
if (!function_exists('mb_strpos')) {
    function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
    function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
    function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
    function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
    function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
    function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
    function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
    function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
    function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
    function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
    function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
    function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
    function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
    function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
    function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
    function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); }
}
if (!function_exists('mb_http_input')) {
    function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); }
}

if (!function_exists('mb_convert_variables')) {
    function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); }
}

if (!function_exists('mb_ord')) {
    function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); }
}
if (!function_exists('mb_chr')) {
    function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
    function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
    function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); }
}

if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('mb_convert_encoding')) {
    function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
    function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!function_exists('mb_encode_mimeheader')) {
    function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
    function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
    function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
}
if (!function_exists('mb_convert_case')) {
    function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
    function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
    function mb_language($language = null) { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
}
if (!function_exists('mb_check_encoding')) {
    function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
    function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
}
if (!function_exists('mb_detect_order')) {
    function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
    function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
    function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
}
if (!function_exists('mb_strpos')) {
    function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
    function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
    function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
    function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
    function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
    function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
    function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
    function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
    function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
    function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
    function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
    function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
    function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
    function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
    function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
}
if (!function_exists('mb_http_input')) {
    function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
}

if (!function_exists('mb_convert_variables')) {
    function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
}

if (!function_exists('mb_ord')) {
    function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
}
if (!function_exists('mb_chr')) {
    function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
    function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
    function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}

if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php80;

/**
 * @author Ion Bazan <ion.bazan@gmail.com>
 * @author Nico Oelgart <nicoswd@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Php80
{
    public static function fdiv(float $dividend, float $divisor): float
    {
        return @($dividend / $divisor);
    }

    public static function get_debug_type($value): string
    {
        switch (true) {
            case null === $value: return 'null';
            case \is_bool($value): return 'bool';
            case \is_string($value): return 'string';
            case \is_array($value): return 'array';
            case \is_int($value): return 'int';
            case \is_float($value): return 'float';
            case \is_object($value): break;
            case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
            default:
                if (null === $type = @get_resource_type($value)) {
                    return 'unknown';
                }

                if ('Unknown' === $type) {
                    $type = 'closed';
                }

                return "resource ($type)";
        }

        $class = \get_class($value);

        if (false === strpos($class, '@')) {
            return $class;
        }

        return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
    }

    public static function get_resource_id($res): int
    {
        if (!\is_resource($res) && null === @get_resource_type($res)) {
            throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
        }

        return (int) $res;
    }

    public static function preg_last_error_msg(): string
    {
        switch (preg_last_error()) {
            case \PREG_INTERNAL_ERROR:
                return 'Internal error';
            case \PREG_BAD_UTF8_ERROR:
                return 'Malformed UTF-8 characters, possibly incorrectly encoded';
            case \PREG_BAD_UTF8_OFFSET_ERROR:
                return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
            case \PREG_BACKTRACK_LIMIT_ERROR:
                return 'Backtrack limit exhausted';
            case \PREG_RECURSION_LIMIT_ERROR:
                return 'Recursion limit exhausted';
            case \PREG_JIT_STACKLIMIT_ERROR:
                return 'JIT stack limit exhausted';
            case \PREG_NO_ERROR:
                return 'No error';
            default:
                return 'Unknown error';
        }
    }

    public static function str_contains(string $haystack, string $needle): bool
    {
        return '' === $needle || false !== strpos($haystack, $needle);
    }

    public static function str_starts_with(string $haystack, string $needle): bool
    {
        return 0 === strncmp($haystack, $needle, \strlen($needle));
    }

    public static function str_ends_with(string $haystack, string $needle): bool
    {
        return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));
    }
}
<?php

#[Attribute(Attribute::TARGET_CLASS)]
final class Attribute
{
    public const TARGET_CLASS = 1;
    public const TARGET_FUNCTION = 2;
    public const TARGET_METHOD = 4;
    public const TARGET_PROPERTY = 8;
    public const TARGET_CLASS_CONSTANT = 16;
    public const TARGET_PARAMETER = 32;
    public const TARGET_ALL = 63;
    public const IS_REPEATABLE = 64;

    /** @var int */
    public $flags;

    public function __construct(int $flags = self::TARGET_ALL)
    {
        $this->flags = $flags;
    }
}
<?php

if (\PHP_VERSION_ID < 80000) {
    interface Stringable
    {
        /**
         * @return string
         */
        public function __toString();
    }
}
<?php

class UnhandledMatchError extends Error
{
}
<?php

class ValueError extends Error
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php80 as p;

if (\PHP_VERSION_ID >= 80000) {
    return;
}

if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
    define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
}

if (!function_exists('fdiv')) {
    function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
}
if (!function_exists('preg_last_error_msg')) {
    function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
}
if (!function_exists('str_contains')) {
    function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); }
}
if (!function_exists('str_starts_with')) {
    function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); }
}
if (!function_exists('str_ends_with')) {
    function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); }
}
if (!function_exists('get_debug_type')) {
    function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
}
if (!function_exists('get_resource_id')) {
    function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

if (!\function_exists(u::class)) {
    function u(?string $string = ''): UnicodeString
    {
        return new UnicodeString($string ?? '');
    }
}

if (!\function_exists(b::class)) {
    function b(?string $string = ''): ByteString
    {
        return new ByteString($string ?? '');
    }
}

if (!\function_exists(s::class)) {
    /**
     * @return UnicodeString|ByteString
     */
    function s(?string $string = ''): AbstractString
    {
        $string = $string ?? '';

        return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
    }
}
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 14.0.0
 * Date: 2021-09-17T09:20:30+02:00
 */

return [
    [
        768,
        879,
    ],
    [
        1155,
        1159,
    ],
    [
        1160,
        1161,
    ],
    [
        1425,
        1469,
    ],
    [
        1471,
        1471,
    ],
    [
        1473,
        1474,
    ],
    [
        1476,
        1477,
    ],
    [
        1479,
        1479,
    ],
    [
        1552,
        1562,
    ],
    [
        1611,
        1631,
    ],
    [
        1648,
        1648,
    ],
    [
        1750,
        1756,
    ],
    [
        1759,
        1764,
    ],
    [
        1767,
        1768,
    ],
    [
        1770,
        1773,
    ],
    [
        1809,
        1809,
    ],
    [
        1840,
        1866,
    ],
    [
        1958,
        1968,
    ],
    [
        2027,
        2035,
    ],
    [
        2045,
        2045,
    ],
    [
        2070,
        2073,
    ],
    [
        2075,
        2083,
    ],
    [
        2085,
        2087,
    ],
    [
        2089,
        2093,
    ],
    [
        2137,
        2139,
    ],
    [
        2200,
        2207,
    ],
    [
        2250,
        2273,
    ],
    [
        2275,
        2306,
    ],
    [
        2362,
        2362,
    ],
    [
        2364,
        2364,
    ],
    [
        2369,
        2376,
    ],
    [
        2381,
        2381,
    ],
    [
        2385,
        2391,
    ],
    [
        2402,
        2403,
    ],
    [
        2433,
        2433,
    ],
    [
        2492,
        2492,
    ],
    [
        2497,
        2500,
    ],
    [
        2509,
        2509,
    ],
    [
        2530,
        2531,
    ],
    [
        2558,
        2558,
    ],
    [
        2561,
        2562,
    ],
    [
        2620,
        2620,
    ],
    [
        2625,
        2626,
    ],
    [
        2631,
        2632,
    ],
    [
        2635,
        2637,
    ],
    [
        2641,
        2641,
    ],
    [
        2672,
        2673,
    ],
    [
        2677,
        2677,
    ],
    [
        2689,
        2690,
    ],
    [
        2748,
        2748,
    ],
    [
        2753,
        2757,
    ],
    [
        2759,
        2760,
    ],
    [
        2765,
        2765,
    ],
    [
        2786,
        2787,
    ],
    [
        2810,
        2815,
    ],
    [
        2817,
        2817,
    ],
    [
        2876,
        2876,
    ],
    [
        2879,
        2879,
    ],
    [
        2881,
        2884,
    ],
    [
        2893,
        2893,
    ],
    [
        2901,
        2902,
    ],
    [
        2914,
        2915,
    ],
    [
        2946,
        2946,
    ],
    [
        3008,
        3008,
    ],
    [
        3021,
        3021,
    ],
    [
        3072,
        3072,
    ],
    [
        3076,
        3076,
    ],
    [
        3132,
        3132,
    ],
    [
        3134,
        3136,
    ],
    [
        3142,
        3144,
    ],
    [
        3146,
        3149,
    ],
    [
        3157,
        3158,
    ],
    [
        3170,
        3171,
    ],
    [
        3201,
        3201,
    ],
    [
        3260,
        3260,
    ],
    [
        3263,
        3263,
    ],
    [
        3270,
        3270,
    ],
    [
        3276,
        3277,
    ],
    [
        3298,
        3299,
    ],
    [
        3328,
        3329,
    ],
    [
        3387,
        3388,
    ],
    [
        3393,
        3396,
    ],
    [
        3405,
        3405,
    ],
    [
        3426,
        3427,
    ],
    [
        3457,
        3457,
    ],
    [
        3530,
        3530,
    ],
    [
        3538,
        3540,
    ],
    [
        3542,
        3542,
    ],
    [
        3633,
        3633,
    ],
    [
        3636,
        3642,
    ],
    [
        3655,
        3662,
    ],
    [
        3761,
        3761,
    ],
    [
        3764,
        3772,
    ],
    [
        3784,
        3789,
    ],
    [
        3864,
        3865,
    ],
    [
        3893,
        3893,
    ],
    [
        3895,
        3895,
    ],
    [
        3897,
        3897,
    ],
    [
        3953,
        3966,
    ],
    [
        3968,
        3972,
    ],
    [
        3974,
        3975,
    ],
    [
        3981,
        3991,
    ],
    [
        3993,
        4028,
    ],
    [
        4038,
        4038,
    ],
    [
        4141,
        4144,
    ],
    [
        4146,
        4151,
    ],
    [
        4153,
        4154,
    ],
    [
        4157,
        4158,
    ],
    [
        4184,
        4185,
    ],
    [
        4190,
        4192,
    ],
    [
        4209,
        4212,
    ],
    [
        4226,
        4226,
    ],
    [
        4229,
        4230,
    ],
    [
        4237,
        4237,
    ],
    [
        4253,
        4253,
    ],
    [
        4957,
        4959,
    ],
    [
        5906,
        5908,
    ],
    [
        5938,
        5939,
    ],
    [
        5970,
        5971,
    ],
    [
        6002,
        6003,
    ],
    [
        6068,
        6069,
    ],
    [
        6071,
        6077,
    ],
    [
        6086,
        6086,
    ],
    [
        6089,
        6099,
    ],
    [
        6109,
        6109,
    ],
    [
        6155,
        6157,
    ],
    [
        6159,
        6159,
    ],
    [
        6277,
        6278,
    ],
    [
        6313,
        6313,
    ],
    [
        6432,
        6434,
    ],
    [
        6439,
        6440,
    ],
    [
        6450,
        6450,
    ],
    [
        6457,
        6459,
    ],
    [
        6679,
        6680,
    ],
    [
        6683,
        6683,
    ],
    [
        6742,
        6742,
    ],
    [
        6744,
        6750,
    ],
    [
        6752,
        6752,
    ],
    [
        6754,
        6754,
    ],
    [
        6757,
        6764,
    ],
    [
        6771,
        6780,
    ],
    [
        6783,
        6783,
    ],
    [
        6832,
        6845,
    ],
    [
        6846,
        6846,
    ],
    [
        6847,
        6862,
    ],
    [
        6912,
        6915,
    ],
    [
        6964,
        6964,
    ],
    [
        6966,
        6970,
    ],
    [
        6972,
        6972,
    ],
    [
        6978,
        6978,
    ],
    [
        7019,
        7027,
    ],
    [
        7040,
        7041,
    ],
    [
        7074,
        7077,
    ],
    [
        7080,
        7081,
    ],
    [
        7083,
        7085,
    ],
    [
        7142,
        7142,
    ],
    [
        7144,
        7145,
    ],
    [
        7149,
        7149,
    ],
    [
        7151,
        7153,
    ],
    [
        7212,
        7219,
    ],
    [
        7222,
        7223,
    ],
    [
        7376,
        7378,
    ],
    [
        7380,
        7392,
    ],
    [
        7394,
        7400,
    ],
    [
        7405,
        7405,
    ],
    [
        7412,
        7412,
    ],
    [
        7416,
        7417,
    ],
    [
        7616,
        7679,
    ],
    [
        8400,
        8412,
    ],
    [
        8413,
        8416,
    ],
    [
        8417,
        8417,
    ],
    [
        8418,
        8420,
    ],
    [
        8421,
        8432,
    ],
    [
        11503,
        11505,
    ],
    [
        11647,
        11647,
    ],
    [
        11744,
        11775,
    ],
    [
        12330,
        12333,
    ],
    [
        12441,
        12442,
    ],
    [
        42607,
        42607,
    ],
    [
        42608,
        42610,
    ],
    [
        42612,
        42621,
    ],
    [
        42654,
        42655,
    ],
    [
        42736,
        42737,
    ],
    [
        43010,
        43010,
    ],
    [
        43014,
        43014,
    ],
    [
        43019,
        43019,
    ],
    [
        43045,
        43046,
    ],
    [
        43052,
        43052,
    ],
    [
        43204,
        43205,
    ],
    [
        43232,
        43249,
    ],
    [
        43263,
        43263,
    ],
    [
        43302,
        43309,
    ],
    [
        43335,
        43345,
    ],
    [
        43392,
        43394,
    ],
    [
        43443,
        43443,
    ],
    [
        43446,
        43449,
    ],
    [
        43452,
        43453,
    ],
    [
        43493,
        43493,
    ],
    [
        43561,
        43566,
    ],
    [
        43569,
        43570,
    ],
    [
        43573,
        43574,
    ],
    [
        43587,
        43587,
    ],
    [
        43596,
        43596,
    ],
    [
        43644,
        43644,
    ],
    [
        43696,
        43696,
    ],
    [
        43698,
        43700,
    ],
    [
        43703,
        43704,
    ],
    [
        43710,
        43711,
    ],
    [
        43713,
        43713,
    ],
    [
        43756,
        43757,
    ],
    [
        43766,
        43766,
    ],
    [
        44005,
        44005,
    ],
    [
        44008,
        44008,
    ],
    [
        44013,
        44013,
    ],
    [
        64286,
        64286,
    ],
    [
        65024,
        65039,
    ],
    [
        65056,
        65071,
    ],
    [
        66045,
        66045,
    ],
    [
        66272,
        66272,
    ],
    [
        66422,
        66426,
    ],
    [
        68097,
        68099,
    ],
    [
        68101,
        68102,
    ],
    [
        68108,
        68111,
    ],
    [
        68152,
        68154,
    ],
    [
        68159,
        68159,
    ],
    [
        68325,
        68326,
    ],
    [
        68900,
        68903,
    ],
    [
        69291,
        69292,
    ],
    [
        69446,
        69456,
    ],
    [
        69506,
        69509,
    ],
    [
        69633,
        69633,
    ],
    [
        69688,
        69702,
    ],
    [
        69744,
        69744,
    ],
    [
        69747,
        69748,
    ],
    [
        69759,
        69761,
    ],
    [
        69811,
        69814,
    ],
    [
        69817,
        69818,
    ],
    [
        69826,
        69826,
    ],
    [
        69888,
        69890,
    ],
    [
        69927,
        69931,
    ],
    [
        69933,
        69940,
    ],
    [
        70003,
        70003,
    ],
    [
        70016,
        70017,
    ],
    [
        70070,
        70078,
    ],
    [
        70089,
        70092,
    ],
    [
        70095,
        70095,
    ],
    [
        70191,
        70193,
    ],
    [
        70196,
        70196,
    ],
    [
        70198,
        70199,
    ],
    [
        70206,
        70206,
    ],
    [
        70367,
        70367,
    ],
    [
        70371,
        70378,
    ],
    [
        70400,
        70401,
    ],
    [
        70459,
        70460,
    ],
    [
        70464,
        70464,
    ],
    [
        70502,
        70508,
    ],
    [
        70512,
        70516,
    ],
    [
        70712,
        70719,
    ],
    [
        70722,
        70724,
    ],
    [
        70726,
        70726,
    ],
    [
        70750,
        70750,
    ],
    [
        70835,
        70840,
    ],
    [
        70842,
        70842,
    ],
    [
        70847,
        70848,
    ],
    [
        70850,
        70851,
    ],
    [
        71090,
        71093,
    ],
    [
        71100,
        71101,
    ],
    [
        71103,
        71104,
    ],
    [
        71132,
        71133,
    ],
    [
        71219,
        71226,
    ],
    [
        71229,
        71229,
    ],
    [
        71231,
        71232,
    ],
    [
        71339,
        71339,
    ],
    [
        71341,
        71341,
    ],
    [
        71344,
        71349,
    ],
    [
        71351,
        71351,
    ],
    [
        71453,
        71455,
    ],
    [
        71458,
        71461,
    ],
    [
        71463,
        71467,
    ],
    [
        71727,
        71735,
    ],
    [
        71737,
        71738,
    ],
    [
        71995,
        71996,
    ],
    [
        71998,
        71998,
    ],
    [
        72003,
        72003,
    ],
    [
        72148,
        72151,
    ],
    [
        72154,
        72155,
    ],
    [
        72160,
        72160,
    ],
    [
        72193,
        72202,
    ],
    [
        72243,
        72248,
    ],
    [
        72251,
        72254,
    ],
    [
        72263,
        72263,
    ],
    [
        72273,
        72278,
    ],
    [
        72281,
        72283,
    ],
    [
        72330,
        72342,
    ],
    [
        72344,
        72345,
    ],
    [
        72752,
        72758,
    ],
    [
        72760,
        72765,
    ],
    [
        72767,
        72767,
    ],
    [
        72850,
        72871,
    ],
    [
        72874,
        72880,
    ],
    [
        72882,
        72883,
    ],
    [
        72885,
        72886,
    ],
    [
        73009,
        73014,
    ],
    [
        73018,
        73018,
    ],
    [
        73020,
        73021,
    ],
    [
        73023,
        73029,
    ],
    [
        73031,
        73031,
    ],
    [
        73104,
        73105,
    ],
    [
        73109,
        73109,
    ],
    [
        73111,
        73111,
    ],
    [
        73459,
        73460,
    ],
    [
        92912,
        92916,
    ],
    [
        92976,
        92982,
    ],
    [
        94031,
        94031,
    ],
    [
        94095,
        94098,
    ],
    [
        94180,
        94180,
    ],
    [
        113821,
        113822,
    ],
    [
        118528,
        118573,
    ],
    [
        118576,
        118598,
    ],
    [
        119143,
        119145,
    ],
    [
        119163,
        119170,
    ],
    [
        119173,
        119179,
    ],
    [
        119210,
        119213,
    ],
    [
        119362,
        119364,
    ],
    [
        121344,
        121398,
    ],
    [
        121403,
        121452,
    ],
    [
        121461,
        121461,
    ],
    [
        121476,
        121476,
    ],
    [
        121499,
        121503,
    ],
    [
        121505,
        121519,
    ],
    [
        122880,
        122886,
    ],
    [
        122888,
        122904,
    ],
    [
        122907,
        122913,
    ],
    [
        122915,
        122916,
    ],
    [
        122918,
        122922,
    ],
    [
        123184,
        123190,
    ],
    [
        123566,
        123566,
    ],
    [
        123628,
        123631,
    ],
    [
        125136,
        125142,
    ],
    [
        125252,
        125258,
    ],
    [
        917760,
        917999,
    ],
];
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 14.0.0
 * Date: 2021-09-17T09:20:30+02:00
 */

return [
    [
        4352,
        4447,
    ],
    [
        8986,
        8987,
    ],
    [
        9001,
        9001,
    ],
    [
        9002,
        9002,
    ],
    [
        9193,
        9196,
    ],
    [
        9200,
        9200,
    ],
    [
        9203,
        9203,
    ],
    [
        9725,
        9726,
    ],
    [
        9748,
        9749,
    ],
    [
        9800,
        9811,
    ],
    [
        9855,
        9855,
    ],
    [
        9875,
        9875,
    ],
    [
        9889,
        9889,
    ],
    [
        9898,
        9899,
    ],
    [
        9917,
        9918,
    ],
    [
        9924,
        9925,
    ],
    [
        9934,
        9934,
    ],
    [
        9940,
        9940,
    ],
    [
        9962,
        9962,
    ],
    [
        9970,
        9971,
    ],
    [
        9973,
        9973,
    ],
    [
        9978,
        9978,
    ],
    [
        9981,
        9981,
    ],
    [
        9989,
        9989,
    ],
    [
        9994,
        9995,
    ],
    [
        10024,
        10024,
    ],
    [
        10060,
        10060,
    ],
    [
        10062,
        10062,
    ],
    [
        10067,
        10069,
    ],
    [
        10071,
        10071,
    ],
    [
        10133,
        10135,
    ],
    [
        10160,
        10160,
    ],
    [
        10175,
        10175,
    ],
    [
        11035,
        11036,
    ],
    [
        11088,
        11088,
    ],
    [
        11093,
        11093,
    ],
    [
        11904,
        11929,
    ],
    [
        11931,
        12019,
    ],
    [
        12032,
        12245,
    ],
    [
        12272,
        12283,
    ],
    [
        12288,
        12288,
    ],
    [
        12289,
        12291,
    ],
    [
        12292,
        12292,
    ],
    [
        12293,
        12293,
    ],
    [
        12294,
        12294,
    ],
    [
        12295,
        12295,
    ],
    [
        12296,
        12296,
    ],
    [
        12297,
        12297,
    ],
    [
        12298,
        12298,
    ],
    [
        12299,
        12299,
    ],
    [
        12300,
        12300,
    ],
    [
        12301,
        12301,
    ],
    [
        12302,
        12302,
    ],
    [
        12303,
        12303,
    ],
    [
        12304,
        12304,
    ],
    [
        12305,
        12305,
    ],
    [
        12306,
        12307,
    ],
    [
        12308,
        12308,
    ],
    [
        12309,
        12309,
    ],
    [
        12310,
        12310,
    ],
    [
        12311,
        12311,
    ],
    [
        12312,
        12312,
    ],
    [
        12313,
        12313,
    ],
    [
        12314,
        12314,
    ],
    [
        12315,
        12315,
    ],
    [
        12316,
        12316,
    ],
    [
        12317,
        12317,
    ],
    [
        12318,
        12319,
    ],
    [
        12320,
        12320,
    ],
    [
        12321,
        12329,
    ],
    [
        12330,
        12333,
    ],
    [
        12334,
        12335,
    ],
    [
        12336,
        12336,
    ],
    [
        12337,
        12341,
    ],
    [
        12342,
        12343,
    ],
    [
        12344,
        12346,
    ],
    [
        12347,
        12347,
    ],
    [
        12348,
        12348,
    ],
    [
        12349,
        12349,
    ],
    [
        12350,
        12350,
    ],
    [
        12353,
        12438,
    ],
    [
        12441,
        12442,
    ],
    [
        12443,
        12444,
    ],
    [
        12445,
        12446,
    ],
    [
        12447,
        12447,
    ],
    [
        12448,
        12448,
    ],
    [
        12449,
        12538,
    ],
    [
        12539,
        12539,
    ],
    [
        12540,
        12542,
    ],
    [
        12543,
        12543,
    ],
    [
        12549,
        12591,
    ],
    [
        12593,
        12686,
    ],
    [
        12688,
        12689,
    ],
    [
        12690,
        12693,
    ],
    [
        12694,
        12703,
    ],
    [
        12704,
        12735,
    ],
    [
        12736,
        12771,
    ],
    [
        12784,
        12799,
    ],
    [
        12800,
        12830,
    ],
    [
        12832,
        12841,
    ],
    [
        12842,
        12871,
    ],
    [
        12880,
        12880,
    ],
    [
        12881,
        12895,
    ],
    [
        12896,
        12927,
    ],
    [
        12928,
        12937,
    ],
    [
        12938,
        12976,
    ],
    [
        12977,
        12991,
    ],
    [
        12992,
        13055,
    ],
    [
        13056,
        13311,
    ],
    [
        13312,
        19903,
    ],
    [
        19968,
        40959,
    ],
    [
        40960,
        40980,
    ],
    [
        40981,
        40981,
    ],
    [
        40982,
        42124,
    ],
    [
        42128,
        42182,
    ],
    [
        43360,
        43388,
    ],
    [
        44032,
        55203,
    ],
    [
        63744,
        64109,
    ],
    [
        64110,
        64111,
    ],
    [
        64112,
        64217,
    ],
    [
        64218,
        64255,
    ],
    [
        65040,
        65046,
    ],
    [
        65047,
        65047,
    ],
    [
        65048,
        65048,
    ],
    [
        65049,
        65049,
    ],
    [
        65072,
        65072,
    ],
    [
        65073,
        65074,
    ],
    [
        65075,
        65076,
    ],
    [
        65077,
        65077,
    ],
    [
        65078,
        65078,
    ],
    [
        65079,
        65079,
    ],
    [
        65080,
        65080,
    ],
    [
        65081,
        65081,
    ],
    [
        65082,
        65082,
    ],
    [
        65083,
        65083,
    ],
    [
        65084,
        65084,
    ],
    [
        65085,
        65085,
    ],
    [
        65086,
        65086,
    ],
    [
        65087,
        65087,
    ],
    [
        65088,
        65088,
    ],
    [
        65089,
        65089,
    ],
    [
        65090,
        65090,
    ],
    [
        65091,
        65091,
    ],
    [
        65092,
        65092,
    ],
    [
        65093,
        65094,
    ],
    [
        65095,
        65095,
    ],
    [
        65096,
        65096,
    ],
    [
        65097,
        65100,
    ],
    [
        65101,
        65103,
    ],
    [
        65104,
        65106,
    ],
    [
        65108,
        65111,
    ],
    [
        65112,
        65112,
    ],
    [
        65113,
        65113,
    ],
    [
        65114,
        65114,
    ],
    [
        65115,
        65115,
    ],
    [
        65116,
        65116,
    ],
    [
        65117,
        65117,
    ],
    [
        65118,
        65118,
    ],
    [
        65119,
        65121,
    ],
    [
        65122,
        65122,
    ],
    [
        65123,
        65123,
    ],
    [
        65124,
        65126,
    ],
    [
        65128,
        65128,
    ],
    [
        65129,
        65129,
    ],
    [
        65130,
        65131,
    ],
    [
        65281,
        65283,
    ],
    [
        65284,
        65284,
    ],
    [
        65285,
        65287,
    ],
    [
        65288,
        65288,
    ],
    [
        65289,
        65289,
    ],
    [
        65290,
        65290,
    ],
    [
        65291,
        65291,
    ],
    [
        65292,
        65292,
    ],
    [
        65293,
        65293,
    ],
    [
        65294,
        65295,
    ],
    [
        65296,
        65305,
    ],
    [
        65306,
        65307,
    ],
    [
        65308,
        65310,
    ],
    [
        65311,
        65312,
    ],
    [
        65313,
        65338,
    ],
    [
        65339,
        65339,
    ],
    [
        65340,
        65340,
    ],
    [
        65341,
        65341,
    ],
    [
        65342,
        65342,
    ],
    [
        65343,
        65343,
    ],
    [
        65344,
        65344,
    ],
    [
        65345,
        65370,
    ],
    [
        65371,
        65371,
    ],
    [
        65372,
        65372,
    ],
    [
        65373,
        65373,
    ],
    [
        65374,
        65374,
    ],
    [
        65375,
        65375,
    ],
    [
        65376,
        65376,
    ],
    [
        65504,
        65505,
    ],
    [
        65506,
        65506,
    ],
    [
        65507,
        65507,
    ],
    [
        65508,
        65508,
    ],
    [
        65509,
        65510,
    ],
    [
        94176,
        94177,
    ],
    [
        94178,
        94178,
    ],
    [
        94179,
        94179,
    ],
    [
        94180,
        94180,
    ],
    [
        94192,
        94193,
    ],
    [
        94208,
        100343,
    ],
    [
        100352,
        101119,
    ],
    [
        101120,
        101589,
    ],
    [
        101632,
        101640,
    ],
    [
        110576,
        110579,
    ],
    [
        110581,
        110587,
    ],
    [
        110589,
        110590,
    ],
    [
        110592,
        110847,
    ],
    [
        110848,
        110882,
    ],
    [
        110928,
        110930,
    ],
    [
        110948,
        110951,
    ],
    [
        110960,
        111355,
    ],
    [
        126980,
        126980,
    ],
    [
        127183,
        127183,
    ],
    [
        127374,
        127374,
    ],
    [
        127377,
        127386,
    ],
    [
        127488,
        127490,
    ],
    [
        127504,
        127547,
    ],
    [
        127552,
        127560,
    ],
    [
        127568,
        127569,
    ],
    [
        127584,
        127589,
    ],
    [
        127744,
        127776,
    ],
    [
        127789,
        127797,
    ],
    [
        127799,
        127868,
    ],
    [
        127870,
        127891,
    ],
    [
        127904,
        127946,
    ],
    [
        127951,
        127955,
    ],
    [
        127968,
        127984,
    ],
    [
        127988,
        127988,
    ],
    [
        127992,
        127994,
    ],
    [
        127995,
        127999,
    ],
    [
        128000,
        128062,
    ],
    [
        128064,
        128064,
    ],
    [
        128066,
        128252,
    ],
    [
        128255,
        128317,
    ],
    [
        128331,
        128334,
    ],
    [
        128336,
        128359,
    ],
    [
        128378,
        128378,
    ],
    [
        128405,
        128406,
    ],
    [
        128420,
        128420,
    ],
    [
        128507,
        128511,
    ],
    [
        128512,
        128591,
    ],
    [
        128640,
        128709,
    ],
    [
        128716,
        128716,
    ],
    [
        128720,
        128722,
    ],
    [
        128725,
        128727,
    ],
    [
        128733,
        128735,
    ],
    [
        128747,
        128748,
    ],
    [
        128756,
        128764,
    ],
    [
        128992,
        129003,
    ],
    [
        129008,
        129008,
    ],
    [
        129292,
        129338,
    ],
    [
        129340,
        129349,
    ],
    [
        129351,
        129535,
    ],
    [
        129648,
        129652,
    ],
    [
        129656,
        129660,
    ],
    [
        129664,
        129670,
    ],
    [
        129680,
        129708,
    ],
    [
        129712,
        129722,
    ],
    [
        129728,
        129733,
    ],
    [
        129744,
        129753,
    ],
    [
        129760,
        129767,
    ],
    [
        129776,
        129782,
    ],
    [
        131072,
        173791,
    ],
    [
        173792,
        173823,
    ],
    [
        173824,
        177976,
    ],
    [
        177977,
        177983,
    ],
    [
        177984,
        178205,
    ],
    [
        178206,
        178207,
    ],
    [
        178208,
        183969,
    ],
    [
        183970,
        183983,
    ],
    [
        183984,
        191456,
    ],
    [
        191457,
        194559,
    ],
    [
        194560,
        195101,
    ],
    [
        195102,
        195103,
    ],
    [
        195104,
        196605,
    ],
    [
        196608,
        201546,
    ],
    [
        201547,
        262141,
    ],
];
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;

/**
 * Represents a string of Unicode code points encoded as UTF-8.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
class CodePointString extends AbstractUnicodeString
{
    public function __construct(string $string = '')
    {
        if ('' !== $string && !preg_match('//u', $string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $this->string = $string;
    }

    public function append(string ...$suffix): AbstractString
    {
        $str = clone $this;
        $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);

        if (!preg_match('//u', $str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function chunk(int $length = 1): array
    {
        if (1 > $length) {
            throw new InvalidArgumentException('The chunk length must be greater than zero.');
        }

        if ('' === $this->string) {
            return [];
        }

        $rx = '/(';
        while (65535 < $length) {
            $rx .= '.{65535}';
            $length -= 65535;
        }
        $rx .= '.{'.$length.'})/us';

        $str = clone $this;
        $chunks = [];

        foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
            $str->string = $chunk;
            $chunks[] = clone $str;
        }

        return $chunks;
    }

    public function codePointsAt(int $offset): array
    {
        $str = $offset ? $this->slice($offset, 1) : $this;

        return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')];
    }

    public function endsWith($suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
            return parent::endsWith($suffix);
        } else {
            $suffix = (string) $suffix;
        }

        if ('' === $suffix || !preg_match('//u', $suffix)) {
            return false;
        }

        if ($this->ignoreCase) {
            return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string);
        }

        return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix));
    }

    public function equalsTo($string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (\is_array($string) || $string instanceof \Traversable) {
            return parent::equalsTo($string);
        } else {
            $string = (string) $string;
        }

        if ('' !== $string && $this->ignoreCase) {
            return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
        }

        return $string === $this->string;
    }

    public function indexOf($needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOf($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        if ('' === $needle) {
            return null;
        }

        $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8');

        return false === $i ? null : $i;
    }

    public function indexOfLast($needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOfLast($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        if ('' === $needle) {
            return null;
        }

        $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8');

        return false === $i ? null : $i;
    }

    public function length(): int
    {
        return mb_strlen($this->string, 'UTF-8');
    }

    public function prepend(string ...$prefix): AbstractString
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;

        if (!preg_match('//u', $str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function replace(string $from, string $to): AbstractString
    {
        $str = clone $this;

        if ('' === $from || !preg_match('//u', $from)) {
            return $str;
        }

        if ('' !== $to && !preg_match('//u', $to)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        if ($this->ignoreCase) {
            $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string));
        } else {
            $str->string = str_replace($from, $to, $this->string);
        }

        return $str;
    }

    public function slice(int $start = 0, int $length = null): AbstractString
    {
        $str = clone $this;
        $str->string = mb_substr($this->string, $start, $length, 'UTF-8');

        return $str;
    }

    public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
    {
        if (!preg_match('//u', $replacement)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str = clone $this;
        $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0;
        $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length;
        $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);

        return $str;
    }

    public function split(string $delimiter, int $limit = null, int $flags = null): array
    {
        if (1 > $limit = $limit ?? \PHP_INT_MAX) {
            throw new InvalidArgumentException('Split limit must be a positive integer.');
        }

        if ('' === $delimiter) {
            throw new InvalidArgumentException('Split delimiter is empty.');
        }

        if (null !== $flags) {
            return parent::split($delimiter.'u', $limit, $flags);
        }

        if (!preg_match('//u', $delimiter)) {
            throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
        }

        $str = clone $this;
        $chunks = $this->ignoreCase
            ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit)
            : explode($delimiter, $this->string, $limit);

        foreach ($chunks as &$chunk) {
            $str->string = $chunk;
            $chunk = clone $str;
        }

        return $chunks;
    }

    public function startsWith($prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
            return parent::startsWith($prefix);
        } else {
            $prefix = (string) $prefix;
        }

        if ('' === $prefix || !preg_match('//u', $prefix)) {
            return false;
        }

        if ($this->ignoreCase) {
            return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8');
        }

        return 0 === strncmp($this->string, $prefix, \strlen($prefix));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Slugger;

use Symfony\Component\String\AbstractUnicodeString;
use Symfony\Component\String\UnicodeString;
use Symfony\Contracts\Translation\LocaleAwareInterface;

if (!interface_exists(LocaleAwareInterface::class)) {
    throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".');
}

/**
 * @author Titouan Galopin <galopintitouan@gmail.com>
 */
class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
{
    private const LOCALE_TO_TRANSLITERATOR_ID = [
        'am' => 'Amharic-Latin',
        'ar' => 'Arabic-Latin',
        'az' => 'Azerbaijani-Latin',
        'be' => 'Belarusian-Latin',
        'bg' => 'Bulgarian-Latin',
        'bn' => 'Bengali-Latin',
        'de' => 'de-ASCII',
        'el' => 'Greek-Latin',
        'fa' => 'Persian-Latin',
        'he' => 'Hebrew-Latin',
        'hy' => 'Armenian-Latin',
        'ka' => 'Georgian-Latin',
        'kk' => 'Kazakh-Latin',
        'ky' => 'Kirghiz-Latin',
        'ko' => 'Korean-Latin',
        'mk' => 'Macedonian-Latin',
        'mn' => 'Mongolian-Latin',
        'or' => 'Oriya-Latin',
        'ps' => 'Pashto-Latin',
        'ru' => 'Russian-Latin',
        'sr' => 'Serbian-Latin',
        'sr_Cyrl' => 'Serbian-Latin',
        'th' => 'Thai-Latin',
        'tk' => 'Turkmen-Latin',
        'uk' => 'Ukrainian-Latin',
        'uz' => 'Uzbek-Latin',
        'zh' => 'Han-Latin',
    ];

    private $defaultLocale;
    private $symbolsMap = [
        'en' => ['@' => 'at', '&' => 'and'],
    ];

    /**
     * Cache of transliterators per locale.
     *
     * @var \Transliterator[]
     */
    private $transliterators = [];

    /**
     * @param array|\Closure|null $symbolsMap
     */
    public function __construct(string $defaultLocale = null, $symbolsMap = null)
    {
        if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) {
            throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap)));
        }

        $this->defaultLocale = $defaultLocale;
        $this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
    }

    /**
     * {@inheritdoc}
     */
    public function setLocale($locale)
    {
        $this->defaultLocale = $locale;
    }

    /**
     * {@inheritdoc}
     */
    public function getLocale()
    {
        return $this->defaultLocale;
    }

    /**
     * {@inheritdoc}
     */
    public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString
    {
        $locale = $locale ?? $this->defaultLocale;

        $transliterator = [];
        if ($locale && ('de' === $locale || 0 === strpos($locale, 'de_'))) {
            // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
            $transliterator = ['de-ASCII'];
        } elseif (\function_exists('transliterator_transliterate') && $locale) {
            $transliterator = (array) $this->createTransliterator($locale);
        }

        if ($this->symbolsMap instanceof \Closure) {
            // If the symbols map is passed as a closure, there is no need to fallback to the parent locale
            // as the closure can just provide substitutions for all locales of interest.
            $symbolsMap = $this->symbolsMap;
            array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) {
                return $symbolsMap($s, $locale);
            });
        }

        $unicodeString = (new UnicodeString($string))->ascii($transliterator);

        if (\is_array($this->symbolsMap)) {
            $map = null;
            if (isset($this->symbolsMap[$locale])) {
                $map = $this->symbolsMap[$locale];
            } else {
                $parent = self::getParentLocale($locale);
                if ($parent && isset($this->symbolsMap[$parent])) {
                    $map = $this->symbolsMap[$parent];
                }
            }
            if ($map) {
                foreach ($map as $char => $replace) {
                    $unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
                }
            }
        }

        return $unicodeString
            ->replaceMatches('/[^A-Za-z0-9]++/', $separator)
            ->trim($separator)
        ;
    }

    private function createTransliterator(string $locale): ?\Transliterator
    {
        if (\array_key_exists($locale, $this->transliterators)) {
            return $this->transliterators[$locale];
        }

        // Exact locale supported, cache and return
        if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) {
            return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
        }

        // Locale not supported and no parent, fallback to any-latin
        if (!$parent = self::getParentLocale($locale)) {
            return $this->transliterators[$locale] = null;
        }

        // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
        if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
            $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
        }

        return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
    }

    private static function getParentLocale(?string $locale): ?string
    {
        if (!$locale) {
            return null;
        }
        if (false === $str = strrchr($locale, '_')) {
            // no parent locale
            return null;
        }

        return substr($locale, 0, -\strlen($str));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Slugger;

use Symfony\Component\String\AbstractUnicodeString;

/**
 * Creates a URL-friendly slug from a given string.
 *
 * @author Titouan Galopin <galopintitouan@gmail.com>
 */
interface SluggerInterface
{
    /**
     * Creates a slug for the given string and locale, using appropriate transliteration when needed.
     */
    public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Exception;

interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Exception;

class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Exception;

class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;

/**
 * Represents a string of abstract Unicode characters.
 *
 * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
 * This class is the abstract type to use as a type-hint when the logic you want to
 * implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @throws ExceptionInterface
 */
abstract class AbstractUnicodeString extends AbstractString
{
    public const NFC = \Normalizer::NFC;
    public const NFD = \Normalizer::NFD;
    public const NFKC = \Normalizer::NFKC;
    public const NFKD = \Normalizer::NFKD;

    // all ASCII letters sorted by typical frequency of occurrence
    private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";

    // the subset of folded case mappings that is not in lower case mappings
    private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
    private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];

    // the subset of upper case mappings that map one code point to many code points
    private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
    private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];

    // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
    private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
    private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];

    private static $transliterators = [];

    /**
     * @return static
     */
    public static function fromCodePoints(int ...$codes): self
    {
        $string = '';

        foreach ($codes as $code) {
            if (0x80 > $code %= 0x200000) {
                $string .= \chr($code);
            } elseif (0x800 > $code) {
                $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
            } elseif (0x10000 > $code) {
                $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
            } else {
                $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
            }
        }

        return new static($string);
    }

    /**
     * Generic UTF-8 to ASCII transliteration.
     *
     * Install the intl extension for best results.
     *
     * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
     */
    public function ascii(array $rules = []): self
    {
        $str = clone $this;
        $s = $str->string;
        $str->string = '';

        array_unshift($rules, 'nfd');
        $rules[] = 'latin-ascii';

        if (\function_exists('transliterator_transliterate')) {
            $rules[] = 'any-latin/bgn';
        }

        $rules[] = 'nfkd';
        $rules[] = '[:nonspacing mark:] remove';

        while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
            if (0 < --$i) {
                $str->string .= substr($s, 0, $i);
                $s = substr($s, $i);
            }

            if (!$rule = array_shift($rules)) {
                $rules = []; // An empty rule interrupts the next ones
            }

            if ($rule instanceof \Transliterator) {
                $s = $rule->transliterate($s);
            } elseif ($rule instanceof \Closure) {
                $s = $rule($s);
            } elseif ($rule) {
                if ('nfd' === $rule = strtolower($rule)) {
                    normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD);
                } elseif ('nfkd' === $rule) {
                    normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
                } elseif ('[:nonspacing mark:] remove' === $rule) {
                    $s = preg_replace('/\p{Mn}++/u', '', $s);
                } elseif ('latin-ascii' === $rule) {
                    $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
                } elseif ('de-ascii' === $rule) {
                    $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
                    $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
                } elseif (\function_exists('transliterator_transliterate')) {
                    if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) {
                        if ('any-latin/bgn' === $rule) {
                            $rule = 'any-latin';
                            $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule);
                        }

                        if (null === $transliterator) {
                            throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
                        }

                        self::$transliterators['any-latin/bgn'] = $transliterator;
                    }

                    $s = $transliterator->transliterate($s);
                }
            } elseif (!\function_exists('iconv')) {
                $s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
            } else {
                $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
                    $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);

                    if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
                        throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
                    }

                    return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
                }, $s);
            }
        }

        $str->string .= $s;

        return $str;
    }

    public function camel(): parent
    {
        $str = clone $this;
        $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) {
            return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
        }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));

        return $str;
    }

    /**
     * @return int[]
     */
    public function codePointsAt(int $offset): array
    {
        $str = $this->slice($offset, 1);

        if ('' === $str->string) {
            return [];
        }

        $codePoints = [];

        foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
            $codePoints[] = mb_ord($c, 'UTF-8');
        }

        return $codePoints;
    }

    public function folded(bool $compat = true): parent
    {
        $str = clone $this;

        if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) {
            $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
            $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
        } else {
            $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
        }

        return $str;
    }

    public function join(array $strings, string $lastGlue = null): parent
    {
        $str = clone $this;

        $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
        $str->string = implode($this->string, $strings).$tail;

        if (!preg_match('//u', $str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function lower(): parent
    {
        $str = clone $this;
        $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8');

        return $str;
    }

    public function match(string $regexp, int $flags = 0, int $offset = 0): array
    {
        $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';

        if ($this->ignoreCase) {
            $regexp .= 'i';
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                $lastError = preg_last_error();

                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
                    if ($lastError === $v && '_ERROR' === substr($k, -6)) {
                        throw new RuntimeException('Matching failed with '.$k.'.');
                    }
                }

                throw new RuntimeException('Matching failed with unknown error code.');
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    /**
     * @return static
     */
    public function normalize(int $form = self::NFC): self
    {
        if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
            throw new InvalidArgumentException('Unsupported normalization form.');
        }

        $str = clone $this;
        normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);

        return $str;
    }

    public function padBoth(int $length, string $padStr = ' '): parent
    {
        if ('' === $padStr || !preg_match('//u', $padStr)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $pad = clone $this;
        $pad->string = $padStr;

        return $this->pad($length, $pad, \STR_PAD_BOTH);
    }

    public function padEnd(int $length, string $padStr = ' '): parent
    {
        if ('' === $padStr || !preg_match('//u', $padStr)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $pad = clone $this;
        $pad->string = $padStr;

        return $this->pad($length, $pad, \STR_PAD_RIGHT);
    }

    public function padStart(int $length, string $padStr = ' '): parent
    {
        if ('' === $padStr || !preg_match('//u', $padStr)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $pad = clone $this;
        $pad->string = $padStr;

        return $this->pad($length, $pad, \STR_PAD_LEFT);
    }

    public function replaceMatches(string $fromRegexp, $to): parent
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        if (\is_array($to) || $to instanceof \Closure) {
            if (!\is_callable($to)) {
                throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
            }

            $replace = 'preg_replace_callback';
            $to = static function (array $m) use ($to): string {
                $to = $to($m);

                if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
                    throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
                }

                return $to;
            };
        } elseif ('' !== $to && !preg_match('//u', $to)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        } else {
            $replace = 'preg_replace';
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
                $lastError = preg_last_error();

                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
                    if ($lastError === $v && '_ERROR' === substr($k, -6)) {
                        throw new RuntimeException('Matching failed with '.$k.'.');
                    }
                }

                throw new RuntimeException('Matching failed with unknown error code.');
            }
        } finally {
            restore_error_handler();
        }

        $str = clone $this;
        $str->string = $string;

        return $str;
    }

    public function reverse(): parent
    {
        $str = clone $this;
        $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));

        return $str;
    }

    public function snake(): parent
    {
        $str = $this->camel()->title();
        $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8');

        return $str;
    }

    public function title(bool $allWords = false): parent
    {
        $str = clone $this;

        $limit = $allWords ? -1 : 1;

        $str->string = preg_replace_callback('/\b./u', static function (array $m): string {
            return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
        }, $str->string, $limit);

        return $str;
    }

    public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
    {
        if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
            throw new InvalidArgumentException('Invalid UTF-8 chars.');
        }
        $chars = preg_quote($chars);

        $str = clone $this;
        $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string);

        return $str;
    }

    public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
    {
        if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
            throw new InvalidArgumentException('Invalid UTF-8 chars.');
        }
        $chars = preg_quote($chars);

        $str = clone $this;
        $str->string = preg_replace("{[$chars]++$}uD", '', $str->string);

        return $str;
    }

    public function trimPrefix($prefix): parent
    {
        if (!$this->ignoreCase) {
            return parent::trimPrefix($prefix);
        }

        $str = clone $this;

        if ($prefix instanceof \Traversable) {
            $prefix = iterator_to_array($prefix, false);
        } elseif ($prefix instanceof parent) {
            $prefix = $prefix->string;
        }

        $prefix = implode('|', array_map('preg_quote', (array) $prefix));
        $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string);

        return $str;
    }

    public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
    {
        if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
            throw new InvalidArgumentException('Invalid UTF-8 chars.');
        }
        $chars = preg_quote($chars);

        $str = clone $this;
        $str->string = preg_replace("{^[$chars]++}uD", '', $str->string);

        return $str;
    }

    public function trimSuffix($suffix): parent
    {
        if (!$this->ignoreCase) {
            return parent::trimSuffix($suffix);
        }

        $str = clone $this;

        if ($suffix instanceof \Traversable) {
            $suffix = iterator_to_array($suffix, false);
        } elseif ($suffix instanceof parent) {
            $suffix = $suffix->string;
        }

        $suffix = implode('|', array_map('preg_quote', (array) $suffix));
        $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string);

        return $str;
    }

    public function upper(): parent
    {
        $str = clone $this;
        $str->string = mb_strtoupper($str->string, 'UTF-8');

        if (\PHP_VERSION_ID < 70300) {
            $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
        }

        return $str;
    }

    public function width(bool $ignoreAnsiDecoration = true): int
    {
        $width = 0;
        $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);

        if (false !== strpos($s, "\r")) {
            $s = str_replace(["\r\n", "\r"], "\n", $s);
        }

        if (!$ignoreAnsiDecoration) {
            $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s);
        }

        foreach (explode("\n", $s) as $s) {
            if ($ignoreAnsiDecoration) {
                $s = preg_replace('/(?:\x1B(?:
                    \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [0x40-\x7E]
                    | [P\]X^_] .*? \x1B\\\\
                    | [\x41-\x7E]
                )|[\p{Cc}\x7F]++)/xu', '', $s);
            }

            // Non printable characters have been dropped, so wcswidth cannot logically return -1.
            $width += $this->wcswidth($s);
        }

        return $width;
    }

    /**
     * @return static
     */
    private function pad(int $len, self $pad, int $type): parent
    {
        $sLen = $this->length();

        if ($len <= $sLen) {
            return clone $this;
        }

        $padLen = $pad->length();
        $freeLen = $len - $sLen;
        $len = $freeLen % $padLen;

        switch ($type) {
            case \STR_PAD_RIGHT:
                return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));

            case \STR_PAD_LEFT:
                return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));

            case \STR_PAD_BOTH:
                $freeLen /= 2;

                $rightLen = ceil($freeLen);
                $len = $rightLen % $padLen;
                $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : ''));

                $leftLen = floor($freeLen);
                $len = $leftLen % $padLen;

                return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : ''));

            default:
                throw new InvalidArgumentException('Invalid padding type.');
        }
    }

    /**
     * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
     */
    private function wcswidth(string $string): int
    {
        $width = 0;

        foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
            $codePoint = mb_ord($c, 'UTF-8');

            if (0 === $codePoint // NULL
                || 0x034F === $codePoint // COMBINING GRAPHEME JOINER
                || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK
                || 0x2028 === $codePoint // LINE SEPARATOR
                || 0x2029 === $codePoint // PARAGRAPH SEPARATOR
                || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE
                || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR
            ) {
                continue;
            }

            // Non printable characters
            if (32 > $codePoint // C0 control characters
                || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL
            ) {
                return -1;
            }

            static $tableZero;
            if (null === $tableZero) {
                $tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php';
            }

            if ($codePoint >= $tableZero[0][0] && $codePoint <= $tableZero[$ubound = \count($tableZero) - 1][1]) {
                $lbound = 0;
                while ($ubound >= $lbound) {
                    $mid = floor(($lbound + $ubound) / 2);

                    if ($codePoint > $tableZero[$mid][1]) {
                        $lbound = $mid + 1;
                    } elseif ($codePoint < $tableZero[$mid][0]) {
                        $ubound = $mid - 1;
                    } else {
                        continue 2;
                    }
                }
            }

            static $tableWide;
            if (null === $tableWide) {
                $tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php';
            }

            if ($codePoint >= $tableWide[0][0] && $codePoint <= $tableWide[$ubound = \count($tableWide) - 1][1]) {
                $lbound = 0;
                while ($ubound >= $lbound) {
                    $mid = floor(($lbound + $ubound) / 2);

                    if ($codePoint > $tableWide[$mid][1]) {
                        $lbound = $mid + 1;
                    } elseif ($codePoint < $tableWide[$mid][0]) {
                        $ubound = $mid - 1;
                    } else {
                        $width += 2;

                        continue 2;
                    }
                }
            }

            ++$width;
        }

        return $width;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

/**
 * A string whose value is computed lazily by a callback.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class LazyString implements \Stringable, \JsonSerializable
{
    private $value;

    /**
     * @param callable|array $callback A callable or a [Closure, method] lazy-callable
     *
     * @return static
     */
    public static function fromCallable($callback, ...$arguments): self
    {
        if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
            throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback)));
        }

        $lazyString = new static();
        $lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
            if (null !== $arguments) {
                if (!\is_callable($callback)) {
                    $callback[0] = $callback[0]();
                    $callback[1] = $callback[1] ?? '__invoke';
                }
                $value = $callback(...$arguments);
                $callback = self::getPrettyName($callback);
                $arguments = null;
            }

            return $value ?? '';
        };

        return $lazyString;
    }

    /**
     * @param string|int|float|bool|\Stringable $value
     *
     * @return static
     */
    public static function fromStringable($value): self
    {
        if (!self::isStringable($value)) {
            throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value)));
        }

        if (\is_object($value)) {
            return static::fromCallable([$value, '__toString']);
        }

        $lazyString = new static();
        $lazyString->value = (string) $value;

        return $lazyString;
    }

    /**
     * Tells whether the provided value can be cast to string.
     */
    final public static function isStringable($value): bool
    {
        return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : is_scalar($value));
    }

    /**
     * Casts scalars and stringable objects to strings.
     *
     * @param object|string|int|float|bool $value
     *
     * @throws \TypeError When the provided value is not stringable
     */
    final public static function resolve($value): string
    {
        return $value;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        if (\is_string($this->value)) {
            return $this->value;
        }

        try {
            return $this->value = ($this->value)();
        } catch (\Throwable $e) {
            if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
                $type = explode(', ', $e->getMessage());
                $type = substr(array_pop($type), 0, -\strlen(' returned'));
                $r = new \ReflectionFunction($this->value);
                $callback = $r->getStaticVariables()['callback'];

                $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
            }

            if (\PHP_VERSION_ID < 70400) {
                // leverage the ErrorHandler component with graceful fallback when it's not available
                return trigger_error($e, \E_USER_ERROR);
            }

            throw $e;
        }
    }

    public function __sleep(): array
    {
        $this->__toString();

        return ['value'];
    }

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

    private function __construct()
    {
    }

    private static function getPrettyName(callable $callback): string
    {
        if (\is_string($callback)) {
            return $callback;
        }

        if (\is_array($callback)) {
            $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0];
            $method = $callback[1];
        } elseif ($callback instanceof \Closure) {
            $r = new \ReflectionFunction($callback);

            if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) {
                return $r->name;
            }

            $class = $class->name;
            $method = $r->name;
        } else {
            $class = get_debug_type($callback);
            $method = '__invoke';
        }

        return $class.'::'.$method;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;

/**
 * Represents a string of Unicode grapheme clusters encoded as UTF-8.
 *
 * A letter followed by combining characters (accents typically) form what Unicode defines
 * as a grapheme cluster: a character as humans mean it in written texts. This class knows
 * about the concept and won't split a letter apart from its combining accents. It also
 * ensures all string comparisons happen on their canonically-composed representation,
 * ignoring e.g. the order in which accents are listed when a letter has many of them.
 *
 * @see https://unicode.org/reports/tr15/
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
class UnicodeString extends AbstractUnicodeString
{
    public function __construct(string $string = '')
    {
        $this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string);

        if (false === $this->string) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }
    }

    public function append(string ...$suffix): AbstractString
    {
        $str = clone $this;
        $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));
        normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

        if (false === $str->string) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function chunk(int $length = 1): array
    {
        if (1 > $length) {
            throw new InvalidArgumentException('The chunk length must be greater than zero.');
        }

        if ('' === $this->string) {
            return [];
        }

        $rx = '/(';
        while (65535 < $length) {
            $rx .= '\X{65535}';
            $length -= 65535;
        }
        $rx .= '\X{'.$length.'})/u';

        $str = clone $this;
        $chunks = [];

        foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
            $str->string = $chunk;
            $chunks[] = clone $str;
        }

        return $chunks;
    }

    public function endsWith($suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
            return parent::endsWith($suffix);
        } else {
            $suffix = (string) $suffix;
        }

        $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
        normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form);

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

        if ($this->ignoreCase) {
            return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8');
        }

        return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix));
    }

    public function equalsTo($string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (\is_array($string) || $string instanceof \Traversable) {
            return parent::equalsTo($string);
        } else {
            $string = (string) $string;
        }

        $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
        normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form);

        if ('' !== $string && false !== $string && $this->ignoreCase) {
            return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
        }

        return $string === $this->string;
    }

    public function indexOf($needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOf($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
        normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);

        if ('' === $needle || false === $needle) {
            return null;
        }

        try {
            $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset);
        } catch (\ValueError $e) {
            return null;
        }

        return false === $i ? null : $i;
    }

    public function indexOfLast($needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOfLast($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
        normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);

        if ('' === $needle || false === $needle) {
            return null;
        }

        $string = $this->string;

        if (0 > $offset) {
            // workaround https://bugs.php.net/74264
            if (0 > $offset += grapheme_strlen($needle)) {
                $string = grapheme_substr($string, 0, $offset);
            }
            $offset = 0;
        }

        $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset);

        return false === $i ? null : $i;
    }

    public function join(array $strings, string $lastGlue = null): AbstractString
    {
        $str = parent::join($strings, $lastGlue);
        normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

        return $str;
    }

    public function length(): int
    {
        return grapheme_strlen($this->string);
    }

    /**
     * @return static
     */
    public function normalize(int $form = self::NFC): parent
    {
        $str = clone $this;

        if (\in_array($form, [self::NFC, self::NFKC], true)) {
            normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
        } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) {
            throw new InvalidArgumentException('Unsupported normalization form.');
        } elseif (!normalizer_is_normalized($str->string, $form)) {
            $str->string = normalizer_normalize($str->string, $form);
            $str->ignoreCase = null;
        }

        return $str;
    }

    public function prepend(string ...$prefix): AbstractString
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
        normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

        if (false === $str->string) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function replace(string $from, string $to): AbstractString
    {
        $str = clone $this;
        normalizer_is_normalized($from) ?: $from = normalizer_normalize($from);

        if ('' !== $from && false !== $from) {
            $tail = $str->string;
            $result = '';
            $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';

            while ('' !== $tail && false !== $i = $indexOf($tail, $from)) {
                $slice = grapheme_substr($tail, 0, $i);
                $result .= $slice.$to;
                $tail = substr($tail, \strlen($slice) + \strlen($from));
            }

            $str->string = $result.$tail;
            normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

            if (false === $str->string) {
                throw new InvalidArgumentException('Invalid UTF-8 string.');
            }
        }

        return $str;
    }

    public function replaceMatches(string $fromRegexp, $to): AbstractString
    {
        $str = parent::replaceMatches($fromRegexp, $to);
        normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

        return $str;
    }

    public function slice(int $start = 0, int $length = null): AbstractString
    {
        $str = clone $this;

        if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
            $start = 0;
        }
        $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);

        return $str;
    }

    public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
    {
        $str = clone $this;

        if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
            $start = 0;
        }
        $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0;
        $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length;
        $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647);
        normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);

        if (false === $str->string) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        return $str;
    }

    public function split(string $delimiter, int $limit = null, int $flags = null): array
    {
        if (1 > $limit = $limit ?? 2147483647) {
            throw new InvalidArgumentException('Split limit must be a positive integer.');
        }

        if ('' === $delimiter) {
            throw new InvalidArgumentException('Split delimiter is empty.');
        }

        if (null !== $flags) {
            return parent::split($delimiter.'u', $limit, $flags);
        }

        normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter);

        if (false === $delimiter) {
            throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
        }

        $str = clone $this;
        $tail = $this->string;
        $chunks = [];
        $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';

        while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) {
            $str->string = grapheme_substr($tail, 0, $i);
            $chunks[] = clone $str;
            $tail = substr($tail, \strlen($str->string) + \strlen($delimiter));
            --$limit;
        }

        $str->string = $tail;
        $chunks[] = clone $str;

        return $chunks;
    }

    public function startsWith($prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
            return parent::startsWith($prefix);
        } else {
            $prefix = (string) $prefix;
        }

        $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
        normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form);

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

        if ($this->ignoreCase) {
            return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8');
        }

        return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES);
    }

    public function __wakeup()
    {
        if (!\is_string($this->string)) {
            throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
        }

        normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
    }

    public function __clone()
    {
        if (null === $this->ignoreCase) {
            normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
        }

        $this->ignoreCase = false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;

/**
 * Represents a string of abstract characters.
 *
 * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
 * This class is the abstract type to use as a type-hint when the logic you want to
 * implement doesn't care about the exact variant it deals with.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
abstract class AbstractString implements \Stringable, \JsonSerializable
{
    public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
    public const PREG_SET_ORDER = \PREG_SET_ORDER;
    public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
    public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;

    public const PREG_SPLIT = 0;
    public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
    public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
    public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;

    protected $string = '';
    protected $ignoreCase = false;

    abstract public function __construct(string $string = '');

    /**
     * Unwraps instances of AbstractString back to strings.
     *
     * @return string[]|array
     */
    public static function unwrap(array $values): array
    {
        foreach ($values as $k => $v) {
            if ($v instanceof self) {
                $values[$k] = $v->__toString();
            } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) {
                $values[$k] = $v;
            }
        }

        return $values;
    }

    /**
     * Wraps (and normalizes) strings in instances of AbstractString.
     *
     * @return static[]|array
     */
    public static function wrap(array $values): array
    {
        $i = 0;
        $keys = null;

        foreach ($values as $k => $v) {
            if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
                $keys = $keys ?? array_keys($values);
                $keys[$i] = $j;
            }

            if (\is_string($v)) {
                $values[$k] = new static($v);
            } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) {
                $values[$k] = $v;
            }

            ++$i;
        }

        return null !== $keys ? array_combine($keys, $values) : $values;
    }

    /**
     * @param string|string[] $needle
     *
     * @return static
     */
    public function after($needle, bool $includeNeedle = false, int $offset = 0): self
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        foreach ((array) $needle as $n) {
            $n = (string) $n;
            $j = $this->indexOf($n, $offset);

            if (null !== $j && $j < $i) {
                $i = $j;
                $str->string = $n;
            }
        }

        if (\PHP_INT_MAX === $i) {
            return $str;
        }

        if (!$includeNeedle) {
            $i += $str->length();
        }

        return $this->slice($i);
    }

    /**
     * @param string|string[] $needle
     *
     * @return static
     */
    public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self
    {
        $str = clone $this;
        $i = null;

        foreach ((array) $needle as $n) {
            $n = (string) $n;
            $j = $this->indexOfLast($n, $offset);

            if (null !== $j && $j >= $i) {
                $i = $offset = $j;
                $str->string = $n;
            }
        }

        if (null === $i) {
            return $str;
        }

        if (!$includeNeedle) {
            $i += $str->length();
        }

        return $this->slice($i);
    }

    /**
     * @return static
     */
    abstract public function append(string ...$suffix): self;

    /**
     * @param string|string[] $needle
     *
     * @return static
     */
    public function before($needle, bool $includeNeedle = false, int $offset = 0): self
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        foreach ((array) $needle as $n) {
            $n = (string) $n;
            $j = $this->indexOf($n, $offset);

            if (null !== $j && $j < $i) {
                $i = $j;
                $str->string = $n;
            }
        }

        if (\PHP_INT_MAX === $i) {
            return $str;
        }

        if ($includeNeedle) {
            $i += $str->length();
        }

        return $this->slice(0, $i);
    }

    /**
     * @param string|string[] $needle
     *
     * @return static
     */
    public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self
    {
        $str = clone $this;
        $i = null;

        foreach ((array) $needle as $n) {
            $n = (string) $n;
            $j = $this->indexOfLast($n, $offset);

            if (null !== $j && $j >= $i) {
                $i = $offset = $j;
                $str->string = $n;
            }
        }

        if (null === $i) {
            return $str;
        }

        if ($includeNeedle) {
            $i += $str->length();
        }

        return $this->slice(0, $i);
    }

    /**
     * @return int[]
     */
    public function bytesAt(int $offset): array
    {
        $str = $this->slice($offset, 1);

        return '' === $str->string ? [] : array_values(unpack('C*', $str->string));
    }

    /**
     * @return static
     */
    abstract public function camel(): self;

    /**
     * @return static[]
     */
    abstract public function chunk(int $length = 1): array;

    /**
     * @return static
     */
    public function collapseWhitespace(): self
    {
        $str = clone $this;
        $str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string));

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function containsAny($needle): bool
    {
        return null !== $this->indexOf($needle);
    }

    /**
     * @param string|string[] $suffix
     */
    public function endsWith($suffix): bool
    {
        if (!\is_array($suffix) && !$suffix instanceof \Traversable) {
            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
        }

        foreach ($suffix as $s) {
            if ($this->endsWith((string) $s)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return static
     */
    public function ensureEnd(string $suffix): self
    {
        if (!$this->endsWith($suffix)) {
            return $this->append($suffix);
        }

        $suffix = preg_quote($suffix);
        $regex = '{('.$suffix.')(?:'.$suffix.')++$}D';

        return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
    }

    /**
     * @return static
     */
    public function ensureStart(string $prefix): self
    {
        $prefix = new static($prefix);

        if (!$this->startsWith($prefix)) {
            return $this->prepend($prefix);
        }

        $str = clone $this;
        $i = $prefixLen = $prefix->length();

        while ($this->indexOf($prefix, $i) === $i) {
            $str = $str->slice($prefixLen);
            $i += $prefixLen;
        }

        return $str;
    }

    /**
     * @param string|string[] $string
     */
    public function equalsTo($string): bool
    {
        if (!\is_array($string) && !$string instanceof \Traversable) {
            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
        }

        foreach ($string as $s) {
            if ($this->equalsTo((string) $s)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return static
     */
    abstract public function folded(): self;

    /**
     * @return static
     */
    public function ignoreCase(): self
    {
        $str = clone $this;
        $str->ignoreCase = true;

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function indexOf($needle, int $offset = 0): ?int
    {
        if (!\is_array($needle) && !$needle instanceof \Traversable) {
            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
        }

        $i = \PHP_INT_MAX;

        foreach ($needle as $n) {
            $j = $this->indexOf((string) $n, $offset);

            if (null !== $j && $j < $i) {
                $i = $j;
            }
        }

        return \PHP_INT_MAX === $i ? null : $i;
    }

    /**
     * @param string|string[] $needle
     */
    public function indexOfLast($needle, int $offset = 0): ?int
    {
        if (!\is_array($needle) && !$needle instanceof \Traversable) {
            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
        }

        $i = null;

        foreach ($needle as $n) {
            $j = $this->indexOfLast((string) $n, $offset);

            if (null !== $j && $j >= $i) {
                $i = $offset = $j;
            }
        }

        return $i;
    }

    public function isEmpty(): bool
    {
        return '' === $this->string;
    }

    /**
     * @return static
     */
    abstract public function join(array $strings, string $lastGlue = null): self;

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

    abstract public function length(): int;

    /**
     * @return static
     */
    abstract public function lower(): self;

    /**
     * Matches the string using a regular expression.
     *
     * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression.
     *
     * @return array All matches in a multi-dimensional array ordered according to flags
     */
    abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array;

    /**
     * @return static
     */
    abstract public function padBoth(int $length, string $padStr = ' '): self;

    /**
     * @return static
     */
    abstract public function padEnd(int $length, string $padStr = ' '): self;

    /**
     * @return static
     */
    abstract public function padStart(int $length, string $padStr = ' '): self;

    /**
     * @return static
     */
    abstract public function prepend(string ...$prefix): self;

    /**
     * @return static
     */
    public function repeat(int $multiplier): self
    {
        if (0 > $multiplier) {
            throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier));
        }

        $str = clone $this;
        $str->string = str_repeat($str->string, $multiplier);

        return $str;
    }

    /**
     * @return static
     */
    abstract public function replace(string $from, string $to): self;

    /**
     * @param string|callable $to
     *
     * @return static
     */
    abstract public function replaceMatches(string $fromRegexp, $to): self;

    /**
     * @return static
     */
    abstract public function reverse(): self;

    /**
     * @return static
     */
    abstract public function slice(int $start = 0, int $length = null): self;

    /**
     * @return static
     */
    abstract public function snake(): self;

    /**
     * @return static
     */
    abstract public function splice(string $replacement, int $start = 0, int $length = null): self;

    /**
     * @return static[]
     */
    public function split(string $delimiter, int $limit = null, int $flags = null): array
    {
        if (null === $flags) {
            throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
        }

        if ($this->ignoreCase) {
            $delimiter .= 'i';
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
                $lastError = preg_last_error();

                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
                    if ($lastError === $v && '_ERROR' === substr($k, -6)) {
                        throw new RuntimeException('Splitting failed with '.$k.'.');
                    }
                }

                throw new RuntimeException('Splitting failed with unknown error code.');
            }
        } finally {
            restore_error_handler();
        }

        $str = clone $this;

        if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) {
            foreach ($chunks as &$chunk) {
                $str->string = $chunk[0];
                $chunk[0] = clone $str;
            }
        } else {
            foreach ($chunks as &$chunk) {
                $str->string = $chunk;
                $chunk = clone $str;
            }
        }

        return $chunks;
    }

    /**
     * @param string|string[] $prefix
     */
    public function startsWith($prefix): bool
    {
        if (!\is_array($prefix) && !$prefix instanceof \Traversable) {
            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
        }

        foreach ($prefix as $prefix) {
            if ($this->startsWith((string) $prefix)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return static
     */
    abstract public function title(bool $allWords = false): self;

    public function toByteString(string $toEncoding = null): ByteString
    {
        $b = new ByteString();

        $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding;

        if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') {
            $b->string = $this->string;

            return $b;
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            try {
                $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
            } catch (InvalidArgumentException $e) {
                if (!\function_exists('iconv')) {
                    throw $e;
                }

                $b->string = iconv('UTF-8', $toEncoding, $this->string);
            }
        } finally {
            restore_error_handler();
        }

        return $b;
    }

    public function toCodePointString(): CodePointString
    {
        return new CodePointString($this->string);
    }

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

    public function toUnicodeString(): UnicodeString
    {
        return new UnicodeString($this->string);
    }

    /**
     * @return static
     */
    abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;

    /**
     * @return static
     */
    abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;

    /**
     * @param string|string[] $prefix
     *
     * @return static
     */
    public function trimPrefix($prefix): self
    {
        if (\is_array($prefix) || $prefix instanceof \Traversable) {
            foreach ($prefix as $s) {
                $t = $this->trimPrefix($s);

                if ($t->string !== $this->string) {
                    return $t;
                }
            }

            return clone $this;
        }

        $str = clone $this;

        if ($prefix instanceof self) {
            $prefix = $prefix->string;
        } else {
            $prefix = (string) $prefix;
        }

        if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) {
            $str->string = substr($this->string, \strlen($prefix));
        }

        return $str;
    }

    /**
     * @return static
     */
    abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;

    /**
     * @param string|string[] $suffix
     *
     * @return static
     */
    public function trimSuffix($suffix): self
    {
        if (\is_array($suffix) || $suffix instanceof \Traversable) {
            foreach ($suffix as $s) {
                $t = $this->trimSuffix($s);

                if ($t->string !== $this->string) {
                    return $t;
                }
            }

            return clone $this;
        }

        $str = clone $this;

        if ($suffix instanceof self) {
            $suffix = $suffix->string;
        } else {
            $suffix = (string) $suffix;
        }

        if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) {
            $str->string = substr($this->string, 0, -\strlen($suffix));
        }

        return $str;
    }

    /**
     * @return static
     */
    public function truncate(int $length, string $ellipsis = '', bool $cut = true): self
    {
        $stringLength = $this->length();

        if ($stringLength <= $length) {
            return clone $this;
        }

        $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0;

        if ($length < $ellipsisLength) {
            $ellipsisLength = 0;
        }

        if (!$cut) {
            if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) {
                return clone $this;
            }

            $length += $ellipsisLength;
        }

        $str = $this->slice(0, $length - $ellipsisLength);

        return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str;
    }

    /**
     * @return static
     */
    abstract public function upper(): self;

    /**
     * Returns the printable length on a terminal.
     */
    abstract public function width(bool $ignoreAnsiDecoration = true): int;

    /**
     * @return static
     */
    public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self
    {
        $lines = '' !== $break ? $this->split($break) : [clone $this];
        $chars = [];
        $mask = '';

        if (1 === \count($lines) && '' === $lines[0]->string) {
            return $lines[0];
        }

        foreach ($lines as $i => $line) {
            if ($i) {
                $chars[] = $break;
                $mask .= '#';
            }

            foreach ($line->chunk() as $char) {
                $chars[] = $char->string;
                $mask .= ' ' === $char->string ? ' ' : '?';
            }
        }

        $string = '';
        $j = 0;
        $b = $i = -1;
        $mask = wordwrap($mask, $width, '#', $cut);

        while (false !== $b = strpos($mask, '#', $b + 1)) {
            for (++$i; $i < $b; ++$i) {
                $string .= $chars[$j];
                unset($chars[$j++]);
            }

            if ($break === $chars[$j] || ' ' === $chars[$j]) {
                unset($chars[$j++]);
            }

            $string .= $break;
        }

        $str = clone $this;
        $str->string = $string.implode('', $chars);

        return $str;
    }

    public function __sleep(): array
    {
        return ['string'];
    }

    public function __clone()
    {
        $this->ignoreCase = false;
    }

    public function __toString(): string
    {
        return $this->string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Inflector;

interface InflectorInterface
{
    /**
     * Returns the singular forms of a string.
     *
     * If the method can't determine the form with certainty, several possible singulars are returned.
     *
     * @return string[]
     */
    public function singularize(string $plural): array;

    /**
     * Returns the plural forms of a string.
     *
     * If the method can't determine the form with certainty, several possible plurals are returned.
     *
     * @return string[]
     */
    public function pluralize(string $singular): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Inflector;

final class EnglishInflector implements InflectorInterface
{
    /**
     * Map English plural to singular suffixes.
     *
     * @see http://english-zone.com/spelling/plurals.html
     */
    private const PLURAL_MAP = [
        // First entry: plural suffix, reversed
        // Second entry: length of plural suffix
        // Third entry: Whether the suffix may succeed a vocal
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: singular suffix, normal

        // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
        ['a', 1, true, true, ['on', 'um']],

        // nebulae (nebula)
        ['ea', 2, true, true, 'a'],

        // services (service)
        ['secivres', 8, true, true, 'service'],

        // mice (mouse), lice (louse)
        ['eci', 3, false, true, 'ouse'],

        // geese (goose)
        ['esee', 4, false, true, 'oose'],

        // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
        ['i', 1, true, true, 'us'],

        // men (man), women (woman)
        ['nem', 3, true, true, 'man'],

        // children (child)
        ['nerdlihc', 8, true, true, 'child'],

        // oxen (ox)
        ['nexo', 4, false, false, 'ox'],

        // indices (index), appendices (appendix), prices (price)
        ['seci', 4, false, true, ['ex', 'ix', 'ice']],

        // selfies (selfie)
        ['seifles', 7, true, true, 'selfie'],

        // zombies (zombie)
        ['seibmoz', 7, true, true, 'zombie'],

        // movies (movie)
        ['seivom', 6, true, true, 'movie'],

        // conspectuses (conspectus), prospectuses (prospectus)
        ['sesutcep', 8, true, true, 'pectus'],

        // feet (foot)
        ['teef', 4, true, true, 'foot'],

        // geese (goose)
        ['eseeg', 5, true, true, 'goose'],

        // teeth (tooth)
        ['hteet', 5, true, true, 'tooth'],

        // news (news)
        ['swen', 4, true, true, 'news'],

        // series (series)
        ['seires', 6, true, true, 'series'],

        // babies (baby)
        ['sei', 3, false, true, 'y'],

        // accesses (access), addresses (address), kisses (kiss)
        ['sess', 4, true, false, 'ss'],

        // analyses (analysis), ellipses (ellipsis), fungi (fungus),
        // neuroses (neurosis), theses (thesis), emphases (emphasis),
        // oases (oasis), crises (crisis), houses (house), bases (base),
        // atlases (atlas)
        ['ses', 3, true, true, ['s', 'se', 'sis']],

        // objectives (objective), alternative (alternatives)
        ['sevit', 5, true, true, 'tive'],

        // drives (drive)
        ['sevird', 6, false, true, 'drive'],

        // lives (life), wives (wife)
        ['sevi', 4, false, true, 'ife'],

        // moves (move)
        ['sevom', 5, true, true, 'move'],

        // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
        ['sev', 3, true, true, ['f', 've', 'ff']],

        // axes (axis), axes (ax), axes (axe)
        ['sexa', 4, false, false, ['ax', 'axe', 'axis']],

        // indexes (index), matrixes (matrix)
        ['sex', 3, true, false, 'x'],

        // quizzes (quiz)
        ['sezz', 4, true, false, 'z'],

        // bureaus (bureau)
        ['suae', 4, false, true, 'eau'],

        // fees (fee), trees (tree), employees (employee)
        ['see', 3, true, true, 'ee'],

        // edges (edge)
        ['segd', 4, true, true, 'dge'],

        // roses (rose), garages (garage), cassettes (cassette),
        // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
        // shoes (shoe)
        ['se', 2, true, true, ['', 'e']],

        // tags (tag)
        ['s', 1, true, true, ''],

        // chateaux (chateau)
        ['xuae', 4, false, true, 'eau'],

        // people (person)
        ['elpoep', 6, true, true, 'person'],
    ];

    /**
     * Map English singular to plural suffixes.
     *
     * @see http://english-zone.com/spelling/plurals.html
     */
    private const SINGULAR_MAP = [
        // First entry: singular suffix, reversed
        // Second entry: length of singular suffix
        // Third entry: Whether the suffix may succeed a vocal
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: plural suffix, normal

        // criterion (criteria)
        ['airetirc', 8, false, false, 'criterion'],

        // nebulae (nebula)
        ['aluben', 6, false, false, 'nebulae'],

        // children (child)
        ['dlihc', 5, true, true, 'children'],

        // prices (price)
        ['eci', 3, false, true, 'ices'],

        // services (service)
        ['ecivres', 7, true, true, 'services'],

        // lives (life), wives (wife)
        ['efi', 3, false, true, 'ives'],

        // selfies (selfie)
        ['eifles', 6, true, true, 'selfies'],

        // movies (movie)
        ['eivom', 5, true, true, 'movies'],

        // lice (louse)
        ['esuol', 5, false, true, 'lice'],

        // mice (mouse)
        ['esuom', 5, false, true, 'mice'],

        // geese (goose)
        ['esoo', 4, false, true, 'eese'],

        // houses (house), bases (base)
        ['es', 2, true, true, 'ses'],

        // geese (goose)
        ['esoog', 5, true, true, 'geese'],

        // caves (cave)
        ['ev', 2, true, true, 'ves'],

        // drives (drive)
        ['evird', 5, false, true, 'drives'],

        // objectives (objective), alternative (alternatives)
        ['evit', 4, true, true, 'tives'],

        // moves (move)
        ['evom', 4, true, true, 'moves'],

        // staves (staff)
        ['ffats', 5, true, true, 'staves'],

        // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
        ['ff', 2, true, true, 'ffs'],

        // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
        ['f', 1, true, true, ['fs', 'ves']],

        // arches (arch)
        ['hc', 2, true, true, 'ches'],

        // bushes (bush)
        ['hs', 2, true, true, 'shes'],

        // teeth (tooth)
        ['htoot', 5, true, true, 'teeth'],

        // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
        ['mu', 2, true, true, 'a'],

        // men (man), women (woman)
        ['nam', 3, true, true, 'men'],

        // people (person)
        ['nosrep', 6, true, true, ['persons', 'people']],

        // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
        ['noi', 3, true, true, 'ions'],

        // coupon (coupons)
        ['nop', 3, true, true, 'pons'],

        // seasons (season), treasons (treason), poisons (poison), lessons (lesson)
        ['nos', 3, true, true, 'sons'],

        // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
        ['no', 2, true, true, 'a'],

        // echoes (echo)
        ['ohce', 4, true, true, 'echoes'],

        // heroes (hero)
        ['oreh', 4, true, true, 'heroes'],

        // atlases (atlas)
        ['salta', 5, true, true, 'atlases'],

        // irises (iris)
        ['siri', 4, true, true, 'irises'],

        // analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
        // theses (thesis), emphases (emphasis), oases (oasis),
        // crises (crisis)
        ['sis', 3, true, true, 'ses'],

        // accesses (access), addresses (address), kisses (kiss)
        ['ss', 2, true, false, 'sses'],

        // syllabi (syllabus)
        ['suballys', 8, true, true, 'syllabi'],

        // buses (bus)
        ['sub', 3, true, true, 'buses'],

        // circuses (circus)
        ['suc', 3, true, true, 'cuses'],

        // conspectuses (conspectus), prospectuses (prospectus)
        ['sutcep', 6, true, true, 'pectuses'],

        // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
        ['su', 2, true, true, 'i'],

        // news (news)
        ['swen', 4, true, true, 'news'],

        // feet (foot)
        ['toof', 4, true, true, 'feet'],

        // chateaux (chateau), bureaus (bureau)
        ['uae', 3, false, true, ['eaus', 'eaux']],

        // oxen (ox)
        ['xo', 2, false, false, 'oxen'],

        // hoaxes (hoax)
        ['xaoh', 4, true, false, 'hoaxes'],

        // indices (index)
        ['xedni', 5, false, true, ['indicies', 'indexes']],

        // boxes (box)
        ['xo', 2, false, true, 'oxes'],

        // indexes (index), matrixes (matrix)
        ['x', 1, true, false, ['cies', 'xes']],

        // appendices (appendix)
        ['xi', 2, false, true, 'ices'],

        // babies (baby)
        ['y', 1, false, true, 'ies'],

        // quizzes (quiz)
        ['ziuq', 4, true, false, 'quizzes'],

        // waltzes (waltz)
        ['z', 1, true, true, 'zes'],
    ];

    /**
     * A list of words which should not be inflected, reversed.
     */
    private const UNINFLECTED = [
        '',

        // data
        'atad',

        // deer
        'reed',

        // feedback
        'kcabdeef',

        // fish
        'hsif',

        // info
        'ofni',

        // moose
        'esoom',

        // series
        'seires',

        // sheep
        'peehs',

        // species
        'seiceps',
    ];

    /**
     * {@inheritdoc}
     */
    public function singularize(string $plural): array
    {
        $pluralRev = strrev($plural);
        $lowerPluralRev = strtolower($pluralRev);
        $pluralLength = \strlen($lowerPluralRev);

        // Check if the word is one which is not inflected, return early if so
        if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) {
            return [$plural];
        }

        // The outer loop iterates over the entries of the plural table
        // The inner loop $j iterates over the characters of the plural suffix
        // in the plural table to compare them with the characters of the actual
        // given plural suffix
        foreach (self::PLURAL_MAP as $map) {
            $suffix = $map[0];
            $suffixLength = $map[1];
            $j = 0;

            // Compare characters in the plural table and of the suffix of the
            // given plural one by one
            while ($suffix[$j] === $lowerPluralRev[$j]) {
                // Let $j point to the next character
                ++$j;

                // Successfully compared the last character
                // Add an entry with the singular suffix to the singular array
                if ($j === $suffixLength) {
                    // Is there any character preceding the suffix in the plural string?
                    if ($j < $pluralLength) {
                        $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);

                        if (!$map[2] && $nextIsVocal) {
                            // suffix may not succeed a vocal but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVocal) {
                            // suffix may not succeed a consonant but next char is one
                            break;
                        }
                    }

                    $newBase = substr($plural, 0, $pluralLength - $suffixLength);
                    $newSuffix = $map[4];

                    // Check whether the first character in the plural suffix
                    // is uppercased. If yes, uppercase the first character in
                    // the singular suffix too
                    $firstUpper = ctype_upper($pluralRev[$j - 1]);

                    if (\is_array($newSuffix)) {
                        $singulars = [];

                        foreach ($newSuffix as $newSuffixEntry) {
                            $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
                        }

                        return $singulars;
                    }

                    return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
                }

                // Suffix is longer than word
                if ($j === $pluralLength) {
                    break;
                }
            }
        }

        // Assume that plural and singular is identical
        return [$plural];
    }

    /**
     * {@inheritdoc}
     */
    public function pluralize(string $singular): array
    {
        $singularRev = strrev($singular);
        $lowerSingularRev = strtolower($singularRev);
        $singularLength = \strlen($lowerSingularRev);

        // Check if the word is one which is not inflected, return early if so
        if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) {
            return [$singular];
        }

        // The outer loop iterates over the entries of the singular table
        // The inner loop $j iterates over the characters of the singular suffix
        // in the singular table to compare them with the characters of the actual
        // given singular suffix
        foreach (self::SINGULAR_MAP as $map) {
            $suffix = $map[0];
            $suffixLength = $map[1];
            $j = 0;

            // Compare characters in the singular table and of the suffix of the
            // given plural one by one

            while ($suffix[$j] === $lowerSingularRev[$j]) {
                // Let $j point to the next character
                ++$j;

                // Successfully compared the last character
                // Add an entry with the plural suffix to the plural array
                if ($j === $suffixLength) {
                    // Is there any character preceding the suffix in the plural string?
                    if ($j < $singularLength) {
                        $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);

                        if (!$map[2] && $nextIsVocal) {
                            // suffix may not succeed a vocal but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVocal) {
                            // suffix may not succeed a consonant but next char is one
                            break;
                        }
                    }

                    $newBase = substr($singular, 0, $singularLength - $suffixLength);
                    $newSuffix = $map[4];

                    // Check whether the first character in the singular suffix
                    // is uppercased. If yes, uppercase the first character in
                    // the singular suffix too
                    $firstUpper = ctype_upper($singularRev[$j - 1]);

                    if (\is_array($newSuffix)) {
                        $plurals = [];

                        foreach ($newSuffix as $newSuffixEntry) {
                            $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
                        }

                        return $plurals;
                    }

                    return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
                }

                // Suffix is longer than word
                if ($j === $singularLength) {
                    break;
                }
            }
        }

        // Assume that plural is singular with a trailing `s`
        return [$singular.'s'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String\Inflector;

/**
 * French inflector.
 *
 * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix".
 */
final class FrenchInflector implements InflectorInterface
{
    /**
     * A list of all rules for pluralise.
     *
     * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php
     */
    private const PLURALIZE_REGEXP = [
        // First entry: regexp
        // Second entry: replacement

        // Words finishing with "s", "x" or "z" are invariables
        // Les mots finissant par "s", "x" ou "z" sont invariables
        ['/(s|x|z)$/i', '\1'],

        // Words finishing with "eau" are pluralized with a "x"
        // Les mots finissant par "eau" prennent tous un "x" au pluriel
        ['/(eau)$/i', '\1x'],

        // Words finishing with "au" are pluralized with a "x" excepted "landau"
        // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
        ['/^(landau)$/i', '\1s'],
        ['/(au)$/i', '\1x'],

        // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
        // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
        ['/^(pneu|bleu|émeu)$/i', '\1s'],
        ['/(eu)$/i', '\1x'],

        // Words finishing with "al" are pluralized with a "aux" excepted
        // Les mots finissant en "al" se terminent en "aux" sauf
        ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'],
        ['/al$/i', '\1aux'],

        // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
        ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'],

        // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel
        ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'],

        // Invariable words
        ['/^(cinquante|soixante|mille)$/i', '\1'],

        // French titles
        ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'],
        ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'],
    ];

    /**
     * A list of all rules for singularize.
     */
    private const SINGULARIZE_REGEXP = [
        // First entry: regexp
        // Second entry: replacement

        // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
        ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'],

        // Words finishing with "eau" are pluralized with a "x"
        // Les mots finissant par "eau" prennent tous un "x" au pluriel
        ['/(eau)x$/i', '\1'],

        // Words finishing with "al" are pluralized with a "aux" expected
        // Les mots finissant en "al" se terminent en "aux" sauf
        ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'],

        // Words finishing with "au" are pluralized with a "x" excepted "landau"
        // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
        ['/(au)x$/i', '\1'],

        // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
        // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
        ['/(eu)x$/i', '\1'],

        //  Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou
        // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou
        ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'],

        // French titles
        ['/^mes(dame|demoiselle)s$/', 'ma\1'],
        ['/^Mes(dame|demoiselle)s$/', 'Ma\1'],
        ['/^mes(sieur|seigneur)s$/', 'mon\1'],
        ['/^Mes(sieur|seigneur)s$/', 'Mon\1'],

        //Default rule
        ['/s$/i', ''],
    ];

    /**
     * A list of words which should not be inflected.
     * This list is only used by singularize.
     */
    private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i';

    /**
     * {@inheritdoc}
     */
    public function singularize(string $plural): array
    {
        if ($this->isInflectedWord($plural)) {
            return [$plural];
        }

        foreach (self::SINGULARIZE_REGEXP as $rule) {
            [$regexp, $replace] = $rule;

            if (1 === preg_match($regexp, $plural)) {
                return [preg_replace($regexp, $replace, $plural)];
            }
        }

        return [$plural];
    }

    /**
     * {@inheritdoc}
     */
    public function pluralize(string $singular): array
    {
        if ($this->isInflectedWord($singular)) {
            return [$singular];
        }

        foreach (self::PLURALIZE_REGEXP as $rule) {
            [$regexp, $replace] = $rule;

            if (1 === preg_match($regexp, $singular)) {
                return [preg_replace($regexp, $replace, $singular)];
            }
        }

        return [$singular.'s'];
    }

    private function isInflectedWord(string $word): bool
    {
        return 1 === preg_match(self::UNINFLECTED, $word);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\String;

use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;

/**
 * Represents a binary-safe string of bytes.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
class ByteString extends AbstractString
{
    private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

    public function __construct(string $string = '')
    {
        $this->string = $string;
    }

    /*
     * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
     *
     * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
     *
     * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
     *
     * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
     */

    public static function fromRandom(int $length = 16, string $alphabet = null): self
    {
        if ($length <= 0) {
            throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
        }

        $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
        $alphabetSize = \strlen($alphabet);
        $bits = (int) ceil(log($alphabetSize, 2.0));
        if ($bits <= 0 || $bits > 56) {
            throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
        }

        $ret = '';
        while ($length > 0) {
            $urandomLength = (int) ceil(2 * $length * $bits / 8.0);
            $data = random_bytes($urandomLength);
            $unpackedData = 0;
            $unpackedBits = 0;
            for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
                // Unpack 8 bits
                $unpackedData = ($unpackedData << 8) | \ord($data[$i]);
                $unpackedBits += 8;

                // While we have enough bits to select a character from the alphabet, keep
                // consuming the random data
                for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
                    $index = ($unpackedData & ((1 << $bits) - 1));
                    $unpackedData >>= $bits;
                    // Unfortunately, the alphabet size is not necessarily a power of two.
                    // Worst case, it is 2^k + 1, which means we need (k+1) bits and we
                    // have around a 50% chance of missing as k gets larger
                    if ($index < $alphabetSize) {
                        $ret .= $alphabet[$index];
                        --$length;
                    }
                }
            }
        }

        return new static($ret);
    }

    public function bytesAt(int $offset): array
    {
        $str = $this->string[$offset] ?? '';

        return '' === $str ? [] : [\ord($str)];
    }

    public function append(string ...$suffix): parent
    {
        $str = clone $this;
        $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);

        return $str;
    }

    public function camel(): parent
    {
        $str = clone $this;
        $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));

        return $str;
    }

    public function chunk(int $length = 1): array
    {
        if (1 > $length) {
            throw new InvalidArgumentException('The chunk length must be greater than zero.');
        }

        if ('' === $this->string) {
            return [];
        }

        $str = clone $this;
        $chunks = [];

        foreach (str_split($this->string, $length) as $chunk) {
            $str->string = $chunk;
            $chunks[] = clone $str;
        }

        return $chunks;
    }

    public function endsWith($suffix): bool
    {
        if ($suffix instanceof parent) {
            $suffix = $suffix->string;
        } elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
            return parent::endsWith($suffix);
        } else {
            $suffix = (string) $suffix;
        }

        return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
    }

    public function equalsTo($string): bool
    {
        if ($string instanceof parent) {
            $string = $string->string;
        } elseif (\is_array($string) || $string instanceof \Traversable) {
            return parent::equalsTo($string);
        } else {
            $string = (string) $string;
        }

        if ('' !== $string && $this->ignoreCase) {
            return 0 === strcasecmp($string, $this->string);
        }

        return $string === $this->string;
    }

    public function folded(): parent
    {
        $str = clone $this;
        $str->string = strtolower($str->string);

        return $str;
    }

    public function indexOf($needle, int $offset = 0): ?int
    {
        if ($needle instanceof parent) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOf($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        if ('' === $needle) {
            return null;
        }

        $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);

        return false === $i ? null : $i;
    }

    public function indexOfLast($needle, int $offset = 0): ?int
    {
        if ($needle instanceof parent) {
            $needle = $needle->string;
        } elseif (\is_array($needle) || $needle instanceof \Traversable) {
            return parent::indexOfLast($needle, $offset);
        } else {
            $needle = (string) $needle;
        }

        if ('' === $needle) {
            return null;
        }

        $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);

        return false === $i ? null : $i;
    }

    public function isUtf8(): bool
    {
        return '' === $this->string || preg_match('//u', $this->string);
    }

    public function join(array $strings, string $lastGlue = null): parent
    {
        $str = clone $this;

        $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
        $str->string = implode($this->string, $strings).$tail;

        return $str;
    }

    public function length(): int
    {
        return \strlen($this->string);
    }

    public function lower(): parent
    {
        $str = clone $this;
        $str->string = strtolower($str->string);

        return $str;
    }

    public function match(string $regexp, int $flags = 0, int $offset = 0): array
    {
        $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';

        if ($this->ignoreCase) {
            $regexp .= 'i';
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                $lastError = preg_last_error();

                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
                    if ($lastError === $v && '_ERROR' === substr($k, -6)) {
                        throw new RuntimeException('Matching failed with '.$k.'.');
                    }
                }

                throw new RuntimeException('Matching failed with unknown error code.');
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    public function padBoth(int $length, string $padStr = ' '): parent
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);

        return $str;
    }

    public function padEnd(int $length, string $padStr = ' '): parent
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);

        return $str;
    }

    public function padStart(int $length, string $padStr = ' '): parent
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);

        return $str;
    }

    public function prepend(string ...$prefix): parent
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;

        return $str;
    }

    public function replace(string $from, string $to): parent
    {
        $str = clone $this;

        if ('' !== $from) {
            $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
        }

        return $str;
    }

    public function replaceMatches(string $fromRegexp, $to): parent
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        if (\is_array($to)) {
            if (!\is_callable($to)) {
                throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
            }

            $replace = 'preg_replace_callback';
        } else {
            $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            if (null === $string = $replace($fromRegexp, $to, $this->string)) {
                $lastError = preg_last_error();

                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
                    if ($lastError === $v && '_ERROR' === substr($k, -6)) {
                        throw new RuntimeException('Matching failed with '.$k.'.');
                    }
                }

                throw new RuntimeException('Matching failed with unknown error code.');
            }
        } finally {
            restore_error_handler();
        }

        $str = clone $this;
        $str->string = $string;

        return $str;
    }

    public function reverse(): parent
    {
        $str = clone $this;
        $str->string = strrev($str->string);

        return $str;
    }

    public function slice(int $start = 0, int $length = null): parent
    {
        $str = clone $this;
        $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);

        return $str;
    }

    public function snake(): parent
    {
        $str = $this->camel()->title();
        $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));

        return $str;
    }

    public function splice(string $replacement, int $start = 0, int $length = null): parent
    {
        $str = clone $this;
        $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);

        return $str;
    }

    public function split(string $delimiter, int $limit = null, int $flags = null): array
    {
        if (1 > $limit = $limit ?? \PHP_INT_MAX) {
            throw new InvalidArgumentException('Split limit must be a positive integer.');
        }

        if ('' === $delimiter) {
            throw new InvalidArgumentException('Split delimiter is empty.');
        }

        if (null !== $flags) {
            return parent::split($delimiter, $limit, $flags);
        }

        $str = clone $this;
        $chunks = $this->ignoreCase
            ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
            : explode($delimiter, $this->string, $limit);

        foreach ($chunks as &$chunk) {
            $str->string = $chunk;
            $chunk = clone $str;
        }

        return $chunks;
    }

    public function startsWith($prefix): bool
    {
        if ($prefix instanceof parent) {
            $prefix = $prefix->string;
        } elseif (!\is_string($prefix)) {
            return parent::startsWith($prefix);
        }

        return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
    }

    public function title(bool $allWords = false): parent
    {
        $str = clone $this;
        $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);

        return $str;
    }

    public function toUnicodeString(string $fromEncoding = null): UnicodeString
    {
        return new UnicodeString($this->toCodePointString($fromEncoding)->string);
    }

    public function toCodePointString(string $fromEncoding = null): CodePointString
    {
        $u = new CodePointString();

        if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
            $u->string = $this->string;

            return $u;
        }

        set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });

        try {
            try {
                $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
            } catch (InvalidArgumentException $e) {
                if (!\function_exists('iconv')) {
                    throw $e;
                }

                $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);

                return $u;
            }
        } finally {
            restore_error_handler();
        }

        if (!$validEncoding) {
            throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
        }

        $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');

        return $u;
    }

    public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent
    {
        $str = clone $this;
        $str->string = trim($str->string, $chars);

        return $str;
    }

    public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent
    {
        $str = clone $this;
        $str->string = rtrim($str->string, $chars);

        return $str;
    }

    public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent
    {
        $str = clone $this;
        $str->string = ltrim($str->string, $chars);

        return $str;
    }

    public function upper(): parent
    {
        $str = clone $this;
        $str->string = strtoupper($str->string);

        return $str;
    }

    public function width(bool $ignoreAnsiDecoration = true): int
    {
        $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);

        return (new CodePointString($string))->width($ignoreAnsiDecoration);
    }
}
<?php

class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
{
    /**
     * @deprecated since ICU 56 and removed in PHP 8
     */
    public const NONE = 2;
    public const FORM_D = 4;
    public const FORM_KD = 8;
    public const FORM_C = 16;
    public const FORM_KC = 32;
    public const NFD = 4;
    public const NFKD = 8;
    public const NFC = 16;
    public const NFKC = 32;
}
<?php

return array (
  '̀' => 230,
  '́' => 230,
  '̂' => 230,
  '̃' => 230,
  '̄' => 230,
  '̅' => 230,
  '̆' => 230,
  '̇' => 230,
  '̈' => 230,
  '̉' => 230,
  '̊' => 230,
  '̋' => 230,
  '̌' => 230,
  '̍' => 230,
  '̎' => 230,
  '̏' => 230,
  '̐' => 230,
  '̑' => 230,
  '̒' => 230,
  '̓' => 230,
  '̔' => 230,
  '̕' => 232,
  '̖' => 220,
  '̗' => 220,
  '̘' => 220,
  '̙' => 220,
  '̚' => 232,
  '̛' => 216,
  '̜' => 220,
  '̝' => 220,
  '̞' => 220,
  '̟' => 220,
  '̠' => 220,
  '̡' => 202,
  '̢' => 202,
  '̣' => 220,
  '̤' => 220,
  '̥' => 220,
  '̦' => 220,
  '̧' => 202,
  '̨' => 202,
  '̩' => 220,
  '̪' => 220,
  '̫' => 220,
  '̬' => 220,
  '̭' => 220,
  '̮' => 220,
  '̯' => 220,
  '̰' => 220,
  '̱' => 220,
  '̲' => 220,
  '̳' => 220,
  '̴' => 1,
  '̵' => 1,
  '̶' => 1,
  '̷' => 1,
  '̸' => 1,
  '̹' => 220,
  '̺' => 220,
  '̻' => 220,
  '̼' => 220,
  '̽' => 230,
  '̾' => 230,
  '̿' => 230,
  '̀' => 230,
  '́' => 230,
  '͂' => 230,
  '̓' => 230,
  '̈́' => 230,
  'ͅ' => 240,
  '͆' => 230,
  '͇' => 220,
  '͈' => 220,
  '͉' => 220,
  '͊' => 230,
  '͋' => 230,
  '͌' => 230,
  '͍' => 220,
  '͎' => 220,
  '͐' => 230,
  '͑' => 230,
  '͒' => 230,
  '͓' => 220,
  '͔' => 220,
  '͕' => 220,
  '͖' => 220,
  '͗' => 230,
  '͘' => 232,
  '͙' => 220,
  '͚' => 220,
  '͛' => 230,
  '͜' => 233,
  '͝' => 234,
  '͞' => 234,
  '͟' => 233,
  '͠' => 234,
  '͡' => 234,
  '͢' => 233,
  'ͣ' => 230,
  'ͤ' => 230,
  'ͥ' => 230,
  'ͦ' => 230,
  'ͧ' => 230,
  'ͨ' => 230,
  'ͩ' => 230,
  'ͪ' => 230,
  'ͫ' => 230,
  'ͬ' => 230,
  'ͭ' => 230,
  'ͮ' => 230,
  'ͯ' => 230,
  '҃' => 230,
  '҄' => 230,
  '҅' => 230,
  '҆' => 230,
  '҇' => 230,
  '֑' => 220,
  '֒' => 230,
  '֓' => 230,
  '֔' => 230,
  '֕' => 230,
  '֖' => 220,
  '֗' => 230,
  '֘' => 230,
  '֙' => 230,
  '֚' => 222,
  '֛' => 220,
  '֜' => 230,
  '֝' => 230,
  '֞' => 230,
  '֟' => 230,
  '֠' => 230,
  '֡' => 230,
  '֢' => 220,
  '֣' => 220,
  '֤' => 220,
  '֥' => 220,
  '֦' => 220,
  '֧' => 220,
  '֨' => 230,
  '֩' => 230,
  '֪' => 220,
  '֫' => 230,
  '֬' => 230,
  '֭' => 222,
  '֮' => 228,
  '֯' => 230,
  'ְ' => 10,
  'ֱ' => 11,
  'ֲ' => 12,
  'ֳ' => 13,
  'ִ' => 14,
  'ֵ' => 15,
  'ֶ' => 16,
  'ַ' => 17,
  'ָ' => 18,
  'ֹ' => 19,
  'ֺ' => 19,
  'ֻ' => 20,
  'ּ' => 21,
  'ֽ' => 22,
  'ֿ' => 23,
  'ׁ' => 24,
  'ׂ' => 25,
  'ׄ' => 230,
  'ׅ' => 220,
  'ׇ' => 18,
  'ؐ' => 230,
  'ؑ' => 230,
  'ؒ' => 230,
  'ؓ' => 230,
  'ؔ' => 230,
  'ؕ' => 230,
  'ؖ' => 230,
  'ؗ' => 230,
  'ؘ' => 30,
  'ؙ' => 31,
  'ؚ' => 32,
  'ً' => 27,
  'ٌ' => 28,
  'ٍ' => 29,
  'َ' => 30,
  'ُ' => 31,
  'ِ' => 32,
  'ّ' => 33,
  'ْ' => 34,
  'ٓ' => 230,
  'ٔ' => 230,
  'ٕ' => 220,
  'ٖ' => 220,
  'ٗ' => 230,
  '٘' => 230,
  'ٙ' => 230,
  'ٚ' => 230,
  'ٛ' => 230,
  'ٜ' => 220,
  'ٝ' => 230,
  'ٞ' => 230,
  'ٟ' => 220,
  'ٰ' => 35,
  'ۖ' => 230,
  'ۗ' => 230,
  'ۘ' => 230,
  'ۙ' => 230,
  'ۚ' => 230,
  'ۛ' => 230,
  'ۜ' => 230,
  '۟' => 230,
  '۠' => 230,
  'ۡ' => 230,
  'ۢ' => 230,
  'ۣ' => 220,
  'ۤ' => 230,
  'ۧ' => 230,
  'ۨ' => 230,
  '۪' => 220,
  '۫' => 230,
  '۬' => 230,
  'ۭ' => 220,
  'ܑ' => 36,
  'ܰ' => 230,
  'ܱ' => 220,
  'ܲ' => 230,
  'ܳ' => 230,
  'ܴ' => 220,
  'ܵ' => 230,
  'ܶ' => 230,
  'ܷ' => 220,
  'ܸ' => 220,
  'ܹ' => 220,
  'ܺ' => 230,
  'ܻ' => 220,
  'ܼ' => 220,
  'ܽ' => 230,
  'ܾ' => 220,
  'ܿ' => 230,
  '݀' => 230,
  '݁' => 230,
  '݂' => 220,
  '݃' => 230,
  '݄' => 220,
  '݅' => 230,
  '݆' => 220,
  '݇' => 230,
  '݈' => 220,
  '݉' => 230,
  '݊' => 230,
  '߫' => 230,
  '߬' => 230,
  '߭' => 230,
  '߮' => 230,
  '߯' => 230,
  '߰' => 230,
  '߱' => 230,
  '߲' => 220,
  '߳' => 230,
  '߽' => 220,
  'ࠖ' => 230,
  'ࠗ' => 230,
  '࠘' => 230,
  '࠙' => 230,
  'ࠛ' => 230,
  'ࠜ' => 230,
  'ࠝ' => 230,
  'ࠞ' => 230,
  'ࠟ' => 230,
  'ࠠ' => 230,
  'ࠡ' => 230,
  'ࠢ' => 230,
  'ࠣ' => 230,
  'ࠥ' => 230,
  'ࠦ' => 230,
  'ࠧ' => 230,
  'ࠩ' => 230,
  'ࠪ' => 230,
  'ࠫ' => 230,
  'ࠬ' => 230,
  '࠭' => 230,
  '࡙' => 220,
  '࡚' => 220,
  '࡛' => 220,
  '࣓' => 220,
  'ࣔ' => 230,
  'ࣕ' => 230,
  'ࣖ' => 230,
  'ࣗ' => 230,
  'ࣘ' => 230,
  'ࣙ' => 230,
  'ࣚ' => 230,
  'ࣛ' => 230,
  'ࣜ' => 230,
  'ࣝ' => 230,
  'ࣞ' => 230,
  'ࣟ' => 230,
  '࣠' => 230,
  '࣡' => 230,
  'ࣣ' => 220,
  'ࣤ' => 230,
  'ࣥ' => 230,
  'ࣦ' => 220,
  'ࣧ' => 230,
  'ࣨ' => 230,
  'ࣩ' => 220,
  '࣪' => 230,
  '࣫' => 230,
  '࣬' => 230,
  '࣭' => 220,
  '࣮' => 220,
  '࣯' => 220,
  'ࣰ' => 27,
  'ࣱ' => 28,
  'ࣲ' => 29,
  'ࣳ' => 230,
  'ࣴ' => 230,
  'ࣵ' => 230,
  'ࣶ' => 220,
  'ࣷ' => 230,
  'ࣸ' => 230,
  'ࣹ' => 220,
  'ࣺ' => 220,
  'ࣻ' => 230,
  'ࣼ' => 230,
  'ࣽ' => 230,
  'ࣾ' => 230,
  'ࣿ' => 230,
  '़' => 7,
  '्' => 9,
  '॑' => 230,
  '॒' => 220,
  '॓' => 230,
  '॔' => 230,
  '়' => 7,
  '্' => 9,
  '৾' => 230,
  '਼' => 7,
  '੍' => 9,
  '઼' => 7,
  '્' => 9,
  '଼' => 7,
  '୍' => 9,
  '்' => 9,
  '్' => 9,
  'ౕ' => 84,
  'ౖ' => 91,
  '಼' => 7,
  '್' => 9,
  '഻' => 9,
  '഼' => 9,
  '്' => 9,
  '්' => 9,
  'ุ' => 103,
  'ู' => 103,
  'ฺ' => 9,
  '่' => 107,
  '้' => 107,
  '๊' => 107,
  '๋' => 107,
  'ຸ' => 118,
  'ູ' => 118,
  '຺' => 9,
  '່' => 122,
  '້' => 122,
  '໊' => 122,
  '໋' => 122,
  '༘' => 220,
  '༙' => 220,
  '༵' => 220,
  '༷' => 220,
  '༹' => 216,
  'ཱ' => 129,
  'ི' => 130,
  'ུ' => 132,
  'ེ' => 130,
  'ཻ' => 130,
  'ོ' => 130,
  'ཽ' => 130,
  'ྀ' => 130,
  'ྂ' => 230,
  'ྃ' => 230,
  '྄' => 9,
  '྆' => 230,
  '྇' => 230,
  '࿆' => 220,
  '့' => 7,
  '္' => 9,
  '်' => 9,
  'ႍ' => 220,
  '፝' => 230,
  '፞' => 230,
  '፟' => 230,
  '᜔' => 9,
  '᜴' => 9,
  '្' => 9,
  '៝' => 230,
  'ᢩ' => 228,
  '᤹' => 222,
  '᤺' => 230,
  '᤻' => 220,
  'ᨗ' => 230,
  'ᨘ' => 220,
  '᩠' => 9,
  '᩵' => 230,
  '᩶' => 230,
  '᩷' => 230,
  '᩸' => 230,
  '᩹' => 230,
  '᩺' => 230,
  '᩻' => 230,
  '᩼' => 230,
  '᩿' => 220,
  '᪰' => 230,
  '᪱' => 230,
  '᪲' => 230,
  '᪳' => 230,
  '᪴' => 230,
  '᪵' => 220,
  '᪶' => 220,
  '᪷' => 220,
  '᪸' => 220,
  '᪹' => 220,
  '᪺' => 220,
  '᪻' => 230,
  '᪼' => 230,
  '᪽' => 220,
  'ᪿ' => 220,
  'ᫀ' => 220,
  '᬴' => 7,
  '᭄' => 9,
  '᭫' => 230,
  '᭬' => 220,
  '᭭' => 230,
  '᭮' => 230,
  '᭯' => 230,
  '᭰' => 230,
  '᭱' => 230,
  '᭲' => 230,
  '᭳' => 230,
  '᮪' => 9,
  '᮫' => 9,
  '᯦' => 7,
  '᯲' => 9,
  '᯳' => 9,
  '᰷' => 7,
  '᳐' => 230,
  '᳑' => 230,
  '᳒' => 230,
  '᳔' => 1,
  '᳕' => 220,
  '᳖' => 220,
  '᳗' => 220,
  '᳘' => 220,
  '᳙' => 220,
  '᳚' => 230,
  '᳛' => 230,
  '᳜' => 220,
  '᳝' => 220,
  '᳞' => 220,
  '᳟' => 220,
  '᳠' => 230,
  '᳢' => 1,
  '᳣' => 1,
  '᳤' => 1,
  '᳥' => 1,
  '᳦' => 1,
  '᳧' => 1,
  '᳨' => 1,
  '᳭' => 220,
  '᳴' => 230,
  '᳸' => 230,
  '᳹' => 230,
  '᷀' => 230,
  '᷁' => 230,
  '᷂' => 220,
  '᷃' => 230,
  '᷄' => 230,
  '᷅' => 230,
  '᷆' => 230,
  '᷇' => 230,
  '᷈' => 230,
  '᷉' => 230,
  '᷊' => 220,
  '᷋' => 230,
  '᷌' => 230,
  '᷍' => 234,
  '᷎' => 214,
  '᷏' => 220,
  '᷐' => 202,
  '᷑' => 230,
  '᷒' => 230,
  'ᷓ' => 230,
  'ᷔ' => 230,
  'ᷕ' => 230,
  'ᷖ' => 230,
  'ᷗ' => 230,
  'ᷘ' => 230,
  'ᷙ' => 230,
  'ᷚ' => 230,
  'ᷛ' => 230,
  'ᷜ' => 230,
  'ᷝ' => 230,
  'ᷞ' => 230,
  'ᷟ' => 230,
  'ᷠ' => 230,
  'ᷡ' => 230,
  'ᷢ' => 230,
  'ᷣ' => 230,
  'ᷤ' => 230,
  'ᷥ' => 230,
  'ᷦ' => 230,
  'ᷧ' => 230,
  'ᷨ' => 230,
  'ᷩ' => 230,
  'ᷪ' => 230,
  'ᷫ' => 230,
  'ᷬ' => 230,
  'ᷭ' => 230,
  'ᷮ' => 230,
  'ᷯ' => 230,
  'ᷰ' => 230,
  'ᷱ' => 230,
  'ᷲ' => 230,
  'ᷳ' => 230,
  'ᷴ' => 230,
  '᷵' => 230,
  '᷶' => 232,
  '᷷' => 228,
  '᷸' => 228,
  '᷹' => 220,
  '᷻' => 230,
  '᷼' => 233,
  '᷽' => 220,
  '᷾' => 230,
  '᷿' => 220,
  '⃐' => 230,
  '⃑' => 230,
  '⃒' => 1,
  '⃓' => 1,
  '⃔' => 230,
  '⃕' => 230,
  '⃖' => 230,
  '⃗' => 230,
  '⃘' => 1,
  '⃙' => 1,
  '⃚' => 1,
  '⃛' => 230,
  '⃜' => 230,
  '⃡' => 230,
  '⃥' => 1,
  '⃦' => 1,
  '⃧' => 230,
  '⃨' => 220,
  '⃩' => 230,
  '⃪' => 1,
  '⃫' => 1,
  '⃬' => 220,
  '⃭' => 220,
  '⃮' => 220,
  '⃯' => 220,
  '⃰' => 230,
  '⳯' => 230,
  '⳰' => 230,
  '⳱' => 230,
  '⵿' => 9,
  'ⷠ' => 230,
  'ⷡ' => 230,
  'ⷢ' => 230,
  'ⷣ' => 230,
  'ⷤ' => 230,
  'ⷥ' => 230,
  'ⷦ' => 230,
  'ⷧ' => 230,
  'ⷨ' => 230,
  'ⷩ' => 230,
  'ⷪ' => 230,
  'ⷫ' => 230,
  'ⷬ' => 230,
  'ⷭ' => 230,
  'ⷮ' => 230,
  'ⷯ' => 230,
  'ⷰ' => 230,
  'ⷱ' => 230,
  'ⷲ' => 230,
  'ⷳ' => 230,
  'ⷴ' => 230,
  'ⷵ' => 230,
  'ⷶ' => 230,
  'ⷷ' => 230,
  'ⷸ' => 230,
  'ⷹ' => 230,
  'ⷺ' => 230,
  'ⷻ' => 230,
  'ⷼ' => 230,
  'ⷽ' => 230,
  'ⷾ' => 230,
  'ⷿ' => 230,
  '〪' => 218,
  '〫' => 228,
  '〬' => 232,
  '〭' => 222,
  '〮' => 224,
  '〯' => 224,
  '゙' => 8,
  '゚' => 8,
  '꙯' => 230,
  'ꙴ' => 230,
  'ꙵ' => 230,
  'ꙶ' => 230,
  'ꙷ' => 230,
  'ꙸ' => 230,
  'ꙹ' => 230,
  'ꙺ' => 230,
  'ꙻ' => 230,
  '꙼' => 230,
  '꙽' => 230,
  'ꚞ' => 230,
  'ꚟ' => 230,
  '꛰' => 230,
  '꛱' => 230,
  '꠆' => 9,
  '꠬' => 9,
  '꣄' => 9,
  '꣠' => 230,
  '꣡' => 230,
  '꣢' => 230,
  '꣣' => 230,
  '꣤' => 230,
  '꣥' => 230,
  '꣦' => 230,
  '꣧' => 230,
  '꣨' => 230,
  '꣩' => 230,
  '꣪' => 230,
  '꣫' => 230,
  '꣬' => 230,
  '꣭' => 230,
  '꣮' => 230,
  '꣯' => 230,
  '꣰' => 230,
  '꣱' => 230,
  '꤫' => 220,
  '꤬' => 220,
  '꤭' => 220,
  '꥓' => 9,
  '꦳' => 7,
  '꧀' => 9,
  'ꪰ' => 230,
  'ꪲ' => 230,
  'ꪳ' => 230,
  'ꪴ' => 220,
  'ꪷ' => 230,
  'ꪸ' => 230,
  'ꪾ' => 230,
  '꪿' => 230,
  '꫁' => 230,
  '꫶' => 9,
  '꯭' => 9,
  'ﬞ' => 26,
  '︠' => 230,
  '︡' => 230,
  '︢' => 230,
  '︣' => 230,
  '︤' => 230,
  '︥' => 230,
  '︦' => 230,
  '︧' => 220,
  '︨' => 220,
  '︩' => 220,
  '︪' => 220,
  '︫' => 220,
  '︬' => 220,
  '︭' => 220,
  '︮' => 230,
  '︯' => 230,
  '𐇽' => 220,
  '𐋠' => 220,
  '𐍶' => 230,
  '𐍷' => 230,
  '𐍸' => 230,
  '𐍹' => 230,
  '𐍺' => 230,
  '𐨍' => 220,
  '𐨏' => 230,
  '𐨸' => 230,
  '𐨹' => 1,
  '𐨺' => 220,
  '𐨿' => 9,
  '𐫥' => 230,
  '𐫦' => 220,
  '𐴤' => 230,
  '𐴥' => 230,
  '𐴦' => 230,
  '𐴧' => 230,
  '𐺫' => 230,
  '𐺬' => 230,
  '𐽆' => 220,
  '𐽇' => 220,
  '𐽈' => 230,
  '𐽉' => 230,
  '𐽊' => 230,
  '𐽋' => 220,
  '𐽌' => 230,
  '𐽍' => 220,
  '𐽎' => 220,
  '𐽏' => 220,
  '𐽐' => 220,
  '𑁆' => 9,
  '𑁿' => 9,
  '𑂹' => 9,
  '𑂺' => 7,
  '𑄀' => 230,
  '𑄁' => 230,
  '𑄂' => 230,
  '𑄳' => 9,
  '𑄴' => 9,
  '𑅳' => 7,
  '𑇀' => 9,
  '𑇊' => 7,
  '𑈵' => 9,
  '𑈶' => 7,
  '𑋩' => 7,
  '𑋪' => 9,
  '𑌻' => 7,
  '𑌼' => 7,
  '𑍍' => 9,
  '𑍦' => 230,
  '𑍧' => 230,
  '𑍨' => 230,
  '𑍩' => 230,
  '𑍪' => 230,
  '𑍫' => 230,
  '𑍬' => 230,
  '𑍰' => 230,
  '𑍱' => 230,
  '𑍲' => 230,
  '𑍳' => 230,
  '𑍴' => 230,
  '𑑂' => 9,
  '𑑆' => 7,
  '𑑞' => 230,
  '𑓂' => 9,
  '𑓃' => 7,
  '𑖿' => 9,
  '𑗀' => 7,
  '𑘿' => 9,
  '𑚶' => 9,
  '𑚷' => 7,
  '𑜫' => 9,
  '𑠹' => 9,
  '𑠺' => 7,
  '𑤽' => 9,
  '𑤾' => 9,
  '𑥃' => 7,
  '𑧠' => 9,
  '𑨴' => 9,
  '𑩇' => 9,
  '𑪙' => 9,
  '𑰿' => 9,
  '𑵂' => 7,
  '𑵄' => 9,
  '𑵅' => 9,
  '𑶗' => 9,
  '𖫰' => 1,
  '𖫱' => 1,
  '𖫲' => 1,
  '𖫳' => 1,
  '𖫴' => 1,
  '𖬰' => 230,
  '𖬱' => 230,
  '𖬲' => 230,
  '𖬳' => 230,
  '𖬴' => 230,
  '𖬵' => 230,
  '𖬶' => 230,
  '𖿰' => 6,
  '𖿱' => 6,
  '𛲞' => 1,
  '𝅥' => 216,
  '𝅦' => 216,
  '𝅧' => 1,
  '𝅨' => 1,
  '𝅩' => 1,
  '𝅭' => 226,
  '𝅮' => 216,
  '𝅯' => 216,
  '𝅰' => 216,
  '𝅱' => 216,
  '𝅲' => 216,
  '𝅻' => 220,
  '𝅼' => 220,
  '𝅽' => 220,
  '𝅾' => 220,
  '𝅿' => 220,
  '𝆀' => 220,
  '𝆁' => 220,
  '𝆂' => 220,
  '𝆅' => 230,
  '𝆆' => 230,
  '𝆇' => 230,
  '𝆈' => 230,
  '𝆉' => 230,
  '𝆊' => 220,
  '𝆋' => 220,
  '𝆪' => 230,
  '𝆫' => 230,
  '𝆬' => 230,
  '𝆭' => 230,
  '𝉂' => 230,
  '𝉃' => 230,
  '𝉄' => 230,
  '𞀀' => 230,
  '𞀁' => 230,
  '𞀂' => 230,
  '𞀃' => 230,
  '𞀄' => 230,
  '𞀅' => 230,
  '𞀆' => 230,
  '𞀈' => 230,
  '𞀉' => 230,
  '𞀊' => 230,
  '𞀋' => 230,
  '𞀌' => 230,
  '𞀍' => 230,
  '𞀎' => 230,
  '𞀏' => 230,
  '𞀐' => 230,
  '𞀑' => 230,
  '𞀒' => 230,
  '𞀓' => 230,
  '𞀔' => 230,
  '𞀕' => 230,
  '𞀖' => 230,
  '𞀗' => 230,
  '𞀘' => 230,
  '𞀛' => 230,
  '𞀜' => 230,
  '𞀝' => 230,
  '𞀞' => 230,
  '𞀟' => 230,
  '𞀠' => 230,
  '𞀡' => 230,
  '𞀣' => 230,
  '𞀤' => 230,
  '𞀦' => 230,
  '𞀧' => 230,
  '𞀨' => 230,
  '𞀩' => 230,
  '𞀪' => 230,
  '𞄰' => 230,
  '𞄱' => 230,
  '𞄲' => 230,
  '𞄳' => 230,
  '𞄴' => 230,
  '𞄵' => 230,
  '𞄶' => 230,
  '𞋬' => 230,
  '𞋭' => 230,
  '𞋮' => 230,
  '𞋯' => 230,
  '𞣐' => 220,
  '𞣑' => 220,
  '𞣒' => 220,
  '𞣓' => 220,
  '𞣔' => 220,
  '𞣕' => 220,
  '𞣖' => 220,
  '𞥄' => 230,
  '𞥅' => 230,
  '𞥆' => 230,
  '𞥇' => 230,
  '𞥈' => 230,
  '𞥉' => 230,
  '𞥊' => 7,
);
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '̀' => '̀',
  '́' => '́',
  '̓' => '̓',
  '̈́' => '̈́',
  'ʹ' => 'ʹ',
  ';' => ';',
  '΅' => '΅',
  'Ά' => 'Ά',
  '·' => '·',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'क़' => 'क़',
  'ख़' => 'ख़',
  'ग़' => 'ग़',
  'ज़' => 'ज़',
  'ड़' => 'ड़',
  'ढ़' => 'ढ़',
  'फ़' => 'फ़',
  'य़' => 'य़',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ড়' => 'ড়',
  'ঢ়' => 'ঢ়',
  'য়' => 'য়',
  'ਲ਼' => 'ਲ਼',
  'ਸ਼' => 'ਸ਼',
  'ਖ਼' => 'ਖ਼',
  'ਗ਼' => 'ਗ਼',
  'ਜ਼' => 'ਜ਼',
  'ਫ਼' => 'ਫ਼',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ଡ଼' => 'ଡ଼',
  'ଢ଼' => 'ଢ଼',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'གྷ' => 'གྷ',
  'ཌྷ' => 'ཌྷ',
  'དྷ' => 'དྷ',
  'བྷ' => 'བྷ',
  'ཛྷ' => 'ཛྷ',
  'ཀྵ' => 'ཀྵ',
  'ཱི' => 'ཱི',
  'ཱུ' => 'ཱུ',
  'ྲྀ' => 'ྲྀ',
  'ླྀ' => 'ླྀ',
  'ཱྀ' => 'ཱྀ',
  'ྒྷ' => 'ྒྷ',
  'ྜྷ' => 'ྜྷ',
  'ྡྷ' => 'ྡྷ',
  'ྦྷ' => 'ྦྷ',
  'ྫྷ' => 'ྫྷ',
  'ྐྵ' => 'ྐྵ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ά' => 'ά',
  'ὲ' => 'ὲ',
  'έ' => 'έ',
  'ὴ' => 'ὴ',
  'ή' => 'ή',
  'ὶ' => 'ὶ',
  'ί' => 'ί',
  'ὸ' => 'ὸ',
  'ό' => 'ό',
  'ὺ' => 'ὺ',
  'ύ' => 'ύ',
  'ὼ' => 'ὼ',
  'ώ' => 'ώ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'Ά' => 'Ά',
  'ᾼ' => 'ᾼ',
  'ι' => 'ι',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Έ' => 'Έ',
  'Ὴ' => 'Ὴ',
  'Ή' => 'Ή',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ΐ' => 'ΐ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  'Ί' => 'Ί',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ΰ' => 'ΰ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ύ' => 'Ύ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  '΅' => '΅',
  '`' => '`',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ό' => 'Ό',
  'Ὼ' => 'Ὼ',
  'Ώ' => 'Ώ',
  'ῼ' => 'ῼ',
  '´' => '´',
  ' ' => ' ',
  ' ' => ' ',
  'Ω' => 'Ω',
  'K' => 'K',
  'Å' => 'Å',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  '〈' => '〈',
  '〉' => '〉',
  '⫝̸' => '⫝̸',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '豈' => '豈',
  '更' => '更',
  '車' => '車',
  '賈' => '賈',
  '滑' => '滑',
  '串' => '串',
  '句' => '句',
  '龜' => '龜',
  '龜' => '龜',
  '契' => '契',
  '金' => '金',
  '喇' => '喇',
  '奈' => '奈',
  '懶' => '懶',
  '癩' => '癩',
  '羅' => '羅',
  '蘿' => '蘿',
  '螺' => '螺',
  '裸' => '裸',
  '邏' => '邏',
  '樂' => '樂',
  '洛' => '洛',
  '烙' => '烙',
  '珞' => '珞',
  '落' => '落',
  '酪' => '酪',
  '駱' => '駱',
  '亂' => '亂',
  '卵' => '卵',
  '欄' => '欄',
  '爛' => '爛',
  '蘭' => '蘭',
  '鸞' => '鸞',
  '嵐' => '嵐',
  '濫' => '濫',
  '藍' => '藍',
  '襤' => '襤',
  '拉' => '拉',
  '臘' => '臘',
  '蠟' => '蠟',
  '廊' => '廊',
  '朗' => '朗',
  '浪' => '浪',
  '狼' => '狼',
  '郎' => '郎',
  '來' => '來',
  '冷' => '冷',
  '勞' => '勞',
  '擄' => '擄',
  '櫓' => '櫓',
  '爐' => '爐',
  '盧' => '盧',
  '老' => '老',
  '蘆' => '蘆',
  '虜' => '虜',
  '路' => '路',
  '露' => '露',
  '魯' => '魯',
  '鷺' => '鷺',
  '碌' => '碌',
  '祿' => '祿',
  '綠' => '綠',
  '菉' => '菉',
  '錄' => '錄',
  '鹿' => '鹿',
  '論' => '論',
  '壟' => '壟',
  '弄' => '弄',
  '籠' => '籠',
  '聾' => '聾',
  '牢' => '牢',
  '磊' => '磊',
  '賂' => '賂',
  '雷' => '雷',
  '壘' => '壘',
  '屢' => '屢',
  '樓' => '樓',
  '淚' => '淚',
  '漏' => '漏',
  '累' => '累',
  '縷' => '縷',
  '陋' => '陋',
  '勒' => '勒',
  '肋' => '肋',
  '凜' => '凜',
  '凌' => '凌',
  '稜' => '稜',
  '綾' => '綾',
  '菱' => '菱',
  '陵' => '陵',
  '讀' => '讀',
  '拏' => '拏',
  '樂' => '樂',
  '諾' => '諾',
  '丹' => '丹',
  '寧' => '寧',
  '怒' => '怒',
  '率' => '率',
  '異' => '異',
  '北' => '北',
  '磻' => '磻',
  '便' => '便',
  '復' => '復',
  '不' => '不',
  '泌' => '泌',
  '數' => '數',
  '索' => '索',
  '參' => '參',
  '塞' => '塞',
  '省' => '省',
  '葉' => '葉',
  '說' => '說',
  '殺' => '殺',
  '辰' => '辰',
  '沈' => '沈',
  '拾' => '拾',
  '若' => '若',
  '掠' => '掠',
  '略' => '略',
  '亮' => '亮',
  '兩' => '兩',
  '凉' => '凉',
  '梁' => '梁',
  '糧' => '糧',
  '良' => '良',
  '諒' => '諒',
  '量' => '量',
  '勵' => '勵',
  '呂' => '呂',
  '女' => '女',
  '廬' => '廬',
  '旅' => '旅',
  '濾' => '濾',
  '礪' => '礪',
  '閭' => '閭',
  '驪' => '驪',
  '麗' => '麗',
  '黎' => '黎',
  '力' => '力',
  '曆' => '曆',
  '歷' => '歷',
  '轢' => '轢',
  '年' => '年',
  '憐' => '憐',
  '戀' => '戀',
  '撚' => '撚',
  '漣' => '漣',
  '煉' => '煉',
  '璉' => '璉',
  '秊' => '秊',
  '練' => '練',
  '聯' => '聯',
  '輦' => '輦',
  '蓮' => '蓮',
  '連' => '連',
  '鍊' => '鍊',
  '列' => '列',
  '劣' => '劣',
  '咽' => '咽',
  '烈' => '烈',
  '裂' => '裂',
  '說' => '說',
  '廉' => '廉',
  '念' => '念',
  '捻' => '捻',
  '殮' => '殮',
  '簾' => '簾',
  '獵' => '獵',
  '令' => '令',
  '囹' => '囹',
  '寧' => '寧',
  '嶺' => '嶺',
  '怜' => '怜',
  '玲' => '玲',
  '瑩' => '瑩',
  '羚' => '羚',
  '聆' => '聆',
  '鈴' => '鈴',
  '零' => '零',
  '靈' => '靈',
  '領' => '領',
  '例' => '例',
  '禮' => '禮',
  '醴' => '醴',
  '隸' => '隸',
  '惡' => '惡',
  '了' => '了',
  '僚' => '僚',
  '寮' => '寮',
  '尿' => '尿',
  '料' => '料',
  '樂' => '樂',
  '燎' => '燎',
  '療' => '療',
  '蓼' => '蓼',
  '遼' => '遼',
  '龍' => '龍',
  '暈' => '暈',
  '阮' => '阮',
  '劉' => '劉',
  '杻' => '杻',
  '柳' => '柳',
  '流' => '流',
  '溜' => '溜',
  '琉' => '琉',
  '留' => '留',
  '硫' => '硫',
  '紐' => '紐',
  '類' => '類',
  '六' => '六',
  '戮' => '戮',
  '陸' => '陸',
  '倫' => '倫',
  '崙' => '崙',
  '淪' => '淪',
  '輪' => '輪',
  '律' => '律',
  '慄' => '慄',
  '栗' => '栗',
  '率' => '率',
  '隆' => '隆',
  '利' => '利',
  '吏' => '吏',
  '履' => '履',
  '易' => '易',
  '李' => '李',
  '梨' => '梨',
  '泥' => '泥',
  '理' => '理',
  '痢' => '痢',
  '罹' => '罹',
  '裏' => '裏',
  '裡' => '裡',
  '里' => '里',
  '離' => '離',
  '匿' => '匿',
  '溺' => '溺',
  '吝' => '吝',
  '燐' => '燐',
  '璘' => '璘',
  '藺' => '藺',
  '隣' => '隣',
  '鱗' => '鱗',
  '麟' => '麟',
  '林' => '林',
  '淋' => '淋',
  '臨' => '臨',
  '立' => '立',
  '笠' => '笠',
  '粒' => '粒',
  '狀' => '狀',
  '炙' => '炙',
  '識' => '識',
  '什' => '什',
  '茶' => '茶',
  '刺' => '刺',
  '切' => '切',
  '度' => '度',
  '拓' => '拓',
  '糖' => '糖',
  '宅' => '宅',
  '洞' => '洞',
  '暴' => '暴',
  '輻' => '輻',
  '行' => '行',
  '降' => '降',
  '見' => '見',
  '廓' => '廓',
  '兀' => '兀',
  '嗀' => '嗀',
  '塚' => '塚',
  '晴' => '晴',
  '凞' => '凞',
  '猪' => '猪',
  '益' => '益',
  '礼' => '礼',
  '神' => '神',
  '祥' => '祥',
  '福' => '福',
  '靖' => '靖',
  '精' => '精',
  '羽' => '羽',
  '蘒' => '蘒',
  '諸' => '諸',
  '逸' => '逸',
  '都' => '都',
  '飯' => '飯',
  '飼' => '飼',
  '館' => '館',
  '鶴' => '鶴',
  '郞' => '郞',
  '隷' => '隷',
  '侮' => '侮',
  '僧' => '僧',
  '免' => '免',
  '勉' => '勉',
  '勤' => '勤',
  '卑' => '卑',
  '喝' => '喝',
  '嘆' => '嘆',
  '器' => '器',
  '塀' => '塀',
  '墨' => '墨',
  '層' => '層',
  '屮' => '屮',
  '悔' => '悔',
  '慨' => '慨',
  '憎' => '憎',
  '懲' => '懲',
  '敏' => '敏',
  '既' => '既',
  '暑' => '暑',
  '梅' => '梅',
  '海' => '海',
  '渚' => '渚',
  '漢' => '漢',
  '煮' => '煮',
  '爫' => '爫',
  '琢' => '琢',
  '碑' => '碑',
  '社' => '社',
  '祉' => '祉',
  '祈' => '祈',
  '祐' => '祐',
  '祖' => '祖',
  '祝' => '祝',
  '禍' => '禍',
  '禎' => '禎',
  '穀' => '穀',
  '突' => '突',
  '節' => '節',
  '練' => '練',
  '縉' => '縉',
  '繁' => '繁',
  '署' => '署',
  '者' => '者',
  '臭' => '臭',
  '艹' => '艹',
  '艹' => '艹',
  '著' => '著',
  '褐' => '褐',
  '視' => '視',
  '謁' => '謁',
  '謹' => '謹',
  '賓' => '賓',
  '贈' => '贈',
  '辶' => '辶',
  '逸' => '逸',
  '難' => '難',
  '響' => '響',
  '頻' => '頻',
  '恵' => '恵',
  '𤋮' => '𤋮',
  '舘' => '舘',
  '並' => '並',
  '况' => '况',
  '全' => '全',
  '侀' => '侀',
  '充' => '充',
  '冀' => '冀',
  '勇' => '勇',
  '勺' => '勺',
  '喝' => '喝',
  '啕' => '啕',
  '喙' => '喙',
  '嗢' => '嗢',
  '塚' => '塚',
  '墳' => '墳',
  '奄' => '奄',
  '奔' => '奔',
  '婢' => '婢',
  '嬨' => '嬨',
  '廒' => '廒',
  '廙' => '廙',
  '彩' => '彩',
  '徭' => '徭',
  '惘' => '惘',
  '慎' => '慎',
  '愈' => '愈',
  '憎' => '憎',
  '慠' => '慠',
  '懲' => '懲',
  '戴' => '戴',
  '揄' => '揄',
  '搜' => '搜',
  '摒' => '摒',
  '敖' => '敖',
  '晴' => '晴',
  '朗' => '朗',
  '望' => '望',
  '杖' => '杖',
  '歹' => '歹',
  '殺' => '殺',
  '流' => '流',
  '滛' => '滛',
  '滋' => '滋',
  '漢' => '漢',
  '瀞' => '瀞',
  '煮' => '煮',
  '瞧' => '瞧',
  '爵' => '爵',
  '犯' => '犯',
  '猪' => '猪',
  '瑱' => '瑱',
  '甆' => '甆',
  '画' => '画',
  '瘝' => '瘝',
  '瘟' => '瘟',
  '益' => '益',
  '盛' => '盛',
  '直' => '直',
  '睊' => '睊',
  '着' => '着',
  '磌' => '磌',
  '窱' => '窱',
  '節' => '節',
  '类' => '类',
  '絛' => '絛',
  '練' => '練',
  '缾' => '缾',
  '者' => '者',
  '荒' => '荒',
  '華' => '華',
  '蝹' => '蝹',
  '襁' => '襁',
  '覆' => '覆',
  '視' => '視',
  '調' => '調',
  '諸' => '諸',
  '請' => '請',
  '謁' => '謁',
  '諾' => '諾',
  '諭' => '諭',
  '謹' => '謹',
  '變' => '變',
  '贈' => '贈',
  '輸' => '輸',
  '遲' => '遲',
  '醙' => '醙',
  '鉶' => '鉶',
  '陼' => '陼',
  '難' => '難',
  '靖' => '靖',
  '韛' => '韛',
  '響' => '響',
  '頋' => '頋',
  '頻' => '頻',
  '鬒' => '鬒',
  '龜' => '龜',
  '𢡊' => '𢡊',
  '𢡄' => '𢡄',
  '𣏕' => '𣏕',
  '㮝' => '㮝',
  '䀘' => '䀘',
  '䀹' => '䀹',
  '𥉉' => '𥉉',
  '𥳐' => '𥳐',
  '𧻓' => '𧻓',
  '齃' => '齃',
  '龎' => '龎',
  'יִ' => 'יִ',
  'ײַ' => 'ײַ',
  'שׁ' => 'שׁ',
  'שׂ' => 'שׂ',
  'שּׁ' => 'שּׁ',
  'שּׂ' => 'שּׂ',
  'אַ' => 'אַ',
  'אָ' => 'אָ',
  'אּ' => 'אּ',
  'בּ' => 'בּ',
  'גּ' => 'גּ',
  'דּ' => 'דּ',
  'הּ' => 'הּ',
  'וּ' => 'וּ',
  'זּ' => 'זּ',
  'טּ' => 'טּ',
  'יּ' => 'יּ',
  'ךּ' => 'ךּ',
  'כּ' => 'כּ',
  'לּ' => 'לּ',
  'מּ' => 'מּ',
  'נּ' => 'נּ',
  'סּ' => 'סּ',
  'ףּ' => 'ףּ',
  'פּ' => 'פּ',
  'צּ' => 'צּ',
  'קּ' => 'קּ',
  'רּ' => 'רּ',
  'שּ' => 'שּ',
  'תּ' => 'תּ',
  'וֹ' => 'וֹ',
  'בֿ' => 'בֿ',
  'כֿ' => 'כֿ',
  'פֿ' => 'פֿ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
  '𝅗𝅥' => '𝅗𝅥',
  '𝅘𝅥' => '𝅘𝅥',
  '𝅘𝅥𝅮' => '𝅘𝅥𝅮',
  '𝅘𝅥𝅯' => '𝅘𝅥𝅯',
  '𝅘𝅥𝅰' => '𝅘𝅥𝅰',
  '𝅘𝅥𝅱' => '𝅘𝅥𝅱',
  '𝅘𝅥𝅲' => '𝅘𝅥𝅲',
  '𝆹𝅥' => '𝆹𝅥',
  '𝆺𝅥' => '𝆺𝅥',
  '𝆹𝅥𝅮' => '𝆹𝅥𝅮',
  '𝆺𝅥𝅮' => '𝆺𝅥𝅮',
  '𝆹𝅥𝅯' => '𝆹𝅥𝅯',
  '𝆺𝅥𝅯' => '𝆺𝅥𝅯',
  '丽' => '丽',
  '丸' => '丸',
  '乁' => '乁',
  '𠄢' => '𠄢',
  '你' => '你',
  '侮' => '侮',
  '侻' => '侻',
  '倂' => '倂',
  '偺' => '偺',
  '備' => '備',
  '僧' => '僧',
  '像' => '像',
  '㒞' => '㒞',
  '𠘺' => '𠘺',
  '免' => '免',
  '兔' => '兔',
  '兤' => '兤',
  '具' => '具',
  '𠔜' => '𠔜',
  '㒹' => '㒹',
  '內' => '內',
  '再' => '再',
  '𠕋' => '𠕋',
  '冗' => '冗',
  '冤' => '冤',
  '仌' => '仌',
  '冬' => '冬',
  '况' => '况',
  '𩇟' => '𩇟',
  '凵' => '凵',
  '刃' => '刃',
  '㓟' => '㓟',
  '刻' => '刻',
  '剆' => '剆',
  '割' => '割',
  '剷' => '剷',
  '㔕' => '㔕',
  '勇' => '勇',
  '勉' => '勉',
  '勤' => '勤',
  '勺' => '勺',
  '包' => '包',
  '匆' => '匆',
  '北' => '北',
  '卉' => '卉',
  '卑' => '卑',
  '博' => '博',
  '即' => '即',
  '卽' => '卽',
  '卿' => '卿',
  '卿' => '卿',
  '卿' => '卿',
  '𠨬' => '𠨬',
  '灰' => '灰',
  '及' => '及',
  '叟' => '叟',
  '𠭣' => '𠭣',
  '叫' => '叫',
  '叱' => '叱',
  '吆' => '吆',
  '咞' => '咞',
  '吸' => '吸',
  '呈' => '呈',
  '周' => '周',
  '咢' => '咢',
  '哶' => '哶',
  '唐' => '唐',
  '啓' => '啓',
  '啣' => '啣',
  '善' => '善',
  '善' => '善',
  '喙' => '喙',
  '喫' => '喫',
  '喳' => '喳',
  '嗂' => '嗂',
  '圖' => '圖',
  '嘆' => '嘆',
  '圗' => '圗',
  '噑' => '噑',
  '噴' => '噴',
  '切' => '切',
  '壮' => '壮',
  '城' => '城',
  '埴' => '埴',
  '堍' => '堍',
  '型' => '型',
  '堲' => '堲',
  '報' => '報',
  '墬' => '墬',
  '𡓤' => '𡓤',
  '売' => '売',
  '壷' => '壷',
  '夆' => '夆',
  '多' => '多',
  '夢' => '夢',
  '奢' => '奢',
  '𡚨' => '𡚨',
  '𡛪' => '𡛪',
  '姬' => '姬',
  '娛' => '娛',
  '娧' => '娧',
  '姘' => '姘',
  '婦' => '婦',
  '㛮' => '㛮',
  '㛼' => '㛼',
  '嬈' => '嬈',
  '嬾' => '嬾',
  '嬾' => '嬾',
  '𡧈' => '𡧈',
  '寃' => '寃',
  '寘' => '寘',
  '寧' => '寧',
  '寳' => '寳',
  '𡬘' => '𡬘',
  '寿' => '寿',
  '将' => '将',
  '当' => '当',
  '尢' => '尢',
  '㞁' => '㞁',
  '屠' => '屠',
  '屮' => '屮',
  '峀' => '峀',
  '岍' => '岍',
  '𡷤' => '𡷤',
  '嵃' => '嵃',
  '𡷦' => '𡷦',
  '嵮' => '嵮',
  '嵫' => '嵫',
  '嵼' => '嵼',
  '巡' => '巡',
  '巢' => '巢',
  '㠯' => '㠯',
  '巽' => '巽',
  '帨' => '帨',
  '帽' => '帽',
  '幩' => '幩',
  '㡢' => '㡢',
  '𢆃' => '𢆃',
  '㡼' => '㡼',
  '庰' => '庰',
  '庳' => '庳',
  '庶' => '庶',
  '廊' => '廊',
  '𪎒' => '𪎒',
  '廾' => '廾',
  '𢌱' => '𢌱',
  '𢌱' => '𢌱',
  '舁' => '舁',
  '弢' => '弢',
  '弢' => '弢',
  '㣇' => '㣇',
  '𣊸' => '𣊸',
  '𦇚' => '𦇚',
  '形' => '形',
  '彫' => '彫',
  '㣣' => '㣣',
  '徚' => '徚',
  '忍' => '忍',
  '志' => '志',
  '忹' => '忹',
  '悁' => '悁',
  '㤺' => '㤺',
  '㤜' => '㤜',
  '悔' => '悔',
  '𢛔' => '𢛔',
  '惇' => '惇',
  '慈' => '慈',
  '慌' => '慌',
  '慎' => '慎',
  '慌' => '慌',
  '慺' => '慺',
  '憎' => '憎',
  '憲' => '憲',
  '憤' => '憤',
  '憯' => '憯',
  '懞' => '懞',
  '懲' => '懲',
  '懶' => '懶',
  '成' => '成',
  '戛' => '戛',
  '扝' => '扝',
  '抱' => '抱',
  '拔' => '拔',
  '捐' => '捐',
  '𢬌' => '𢬌',
  '挽' => '挽',
  '拼' => '拼',
  '捨' => '捨',
  '掃' => '掃',
  '揤' => '揤',
  '𢯱' => '𢯱',
  '搢' => '搢',
  '揅' => '揅',
  '掩' => '掩',
  '㨮' => '㨮',
  '摩' => '摩',
  '摾' => '摾',
  '撝' => '撝',
  '摷' => '摷',
  '㩬' => '㩬',
  '敏' => '敏',
  '敬' => '敬',
  '𣀊' => '𣀊',
  '旣' => '旣',
  '書' => '書',
  '晉' => '晉',
  '㬙' => '㬙',
  '暑' => '暑',
  '㬈' => '㬈',
  '㫤' => '㫤',
  '冒' => '冒',
  '冕' => '冕',
  '最' => '最',
  '暜' => '暜',
  '肭' => '肭',
  '䏙' => '䏙',
  '朗' => '朗',
  '望' => '望',
  '朡' => '朡',
  '杞' => '杞',
  '杓' => '杓',
  '𣏃' => '𣏃',
  '㭉' => '㭉',
  '柺' => '柺',
  '枅' => '枅',
  '桒' => '桒',
  '梅' => '梅',
  '𣑭' => '𣑭',
  '梎' => '梎',
  '栟' => '栟',
  '椔' => '椔',
  '㮝' => '㮝',
  '楂' => '楂',
  '榣' => '榣',
  '槪' => '槪',
  '檨' => '檨',
  '𣚣' => '𣚣',
  '櫛' => '櫛',
  '㰘' => '㰘',
  '次' => '次',
  '𣢧' => '𣢧',
  '歔' => '歔',
  '㱎' => '㱎',
  '歲' => '歲',
  '殟' => '殟',
  '殺' => '殺',
  '殻' => '殻',
  '𣪍' => '𣪍',
  '𡴋' => '𡴋',
  '𣫺' => '𣫺',
  '汎' => '汎',
  '𣲼' => '𣲼',
  '沿' => '沿',
  '泍' => '泍',
  '汧' => '汧',
  '洖' => '洖',
  '派' => '派',
  '海' => '海',
  '流' => '流',
  '浩' => '浩',
  '浸' => '浸',
  '涅' => '涅',
  '𣴞' => '𣴞',
  '洴' => '洴',
  '港' => '港',
  '湮' => '湮',
  '㴳' => '㴳',
  '滋' => '滋',
  '滇' => '滇',
  '𣻑' => '𣻑',
  '淹' => '淹',
  '潮' => '潮',
  '𣽞' => '𣽞',
  '𣾎' => '𣾎',
  '濆' => '濆',
  '瀹' => '瀹',
  '瀞' => '瀞',
  '瀛' => '瀛',
  '㶖' => '㶖',
  '灊' => '灊',
  '災' => '災',
  '灷' => '灷',
  '炭' => '炭',
  '𠔥' => '𠔥',
  '煅' => '煅',
  '𤉣' => '𤉣',
  '熜' => '熜',
  '𤎫' => '𤎫',
  '爨' => '爨',
  '爵' => '爵',
  '牐' => '牐',
  '𤘈' => '𤘈',
  '犀' => '犀',
  '犕' => '犕',
  '𤜵' => '𤜵',
  '𤠔' => '𤠔',
  '獺' => '獺',
  '王' => '王',
  '㺬' => '㺬',
  '玥' => '玥',
  '㺸' => '㺸',
  '㺸' => '㺸',
  '瑇' => '瑇',
  '瑜' => '瑜',
  '瑱' => '瑱',
  '璅' => '璅',
  '瓊' => '瓊',
  '㼛' => '㼛',
  '甤' => '甤',
  '𤰶' => '𤰶',
  '甾' => '甾',
  '𤲒' => '𤲒',
  '異' => '異',
  '𢆟' => '𢆟',
  '瘐' => '瘐',
  '𤾡' => '𤾡',
  '𤾸' => '𤾸',
  '𥁄' => '𥁄',
  '㿼' => '㿼',
  '䀈' => '䀈',
  '直' => '直',
  '𥃳' => '𥃳',
  '𥃲' => '𥃲',
  '𥄙' => '𥄙',
  '𥄳' => '𥄳',
  '眞' => '眞',
  '真' => '真',
  '真' => '真',
  '睊' => '睊',
  '䀹' => '䀹',
  '瞋' => '瞋',
  '䁆' => '䁆',
  '䂖' => '䂖',
  '𥐝' => '𥐝',
  '硎' => '硎',
  '碌' => '碌',
  '磌' => '磌',
  '䃣' => '䃣',
  '𥘦' => '𥘦',
  '祖' => '祖',
  '𥚚' => '𥚚',
  '𥛅' => '𥛅',
  '福' => '福',
  '秫' => '秫',
  '䄯' => '䄯',
  '穀' => '穀',
  '穊' => '穊',
  '穏' => '穏',
  '𥥼' => '𥥼',
  '𥪧' => '𥪧',
  '𥪧' => '𥪧',
  '竮' => '竮',
  '䈂' => '䈂',
  '𥮫' => '𥮫',
  '篆' => '篆',
  '築' => '築',
  '䈧' => '䈧',
  '𥲀' => '𥲀',
  '糒' => '糒',
  '䊠' => '䊠',
  '糨' => '糨',
  '糣' => '糣',
  '紀' => '紀',
  '𥾆' => '𥾆',
  '絣' => '絣',
  '䌁' => '䌁',
  '緇' => '緇',
  '縂' => '縂',
  '繅' => '繅',
  '䌴' => '䌴',
  '𦈨' => '𦈨',
  '𦉇' => '𦉇',
  '䍙' => '䍙',
  '𦋙' => '𦋙',
  '罺' => '罺',
  '𦌾' => '𦌾',
  '羕' => '羕',
  '翺' => '翺',
  '者' => '者',
  '𦓚' => '𦓚',
  '𦔣' => '𦔣',
  '聠' => '聠',
  '𦖨' => '𦖨',
  '聰' => '聰',
  '𣍟' => '𣍟',
  '䏕' => '䏕',
  '育' => '育',
  '脃' => '脃',
  '䐋' => '䐋',
  '脾' => '脾',
  '媵' => '媵',
  '𦞧' => '𦞧',
  '𦞵' => '𦞵',
  '𣎓' => '𣎓',
  '𣎜' => '𣎜',
  '舁' => '舁',
  '舄' => '舄',
  '辞' => '辞',
  '䑫' => '䑫',
  '芑' => '芑',
  '芋' => '芋',
  '芝' => '芝',
  '劳' => '劳',
  '花' => '花',
  '芳' => '芳',
  '芽' => '芽',
  '苦' => '苦',
  '𦬼' => '𦬼',
  '若' => '若',
  '茝' => '茝',
  '荣' => '荣',
  '莭' => '莭',
  '茣' => '茣',
  '莽' => '莽',
  '菧' => '菧',
  '著' => '著',
  '荓' => '荓',
  '菊' => '菊',
  '菌' => '菌',
  '菜' => '菜',
  '𦰶' => '𦰶',
  '𦵫' => '𦵫',
  '𦳕' => '𦳕',
  '䔫' => '䔫',
  '蓱' => '蓱',
  '蓳' => '蓳',
  '蔖' => '蔖',
  '𧏊' => '𧏊',
  '蕤' => '蕤',
  '𦼬' => '𦼬',
  '䕝' => '䕝',
  '䕡' => '䕡',
  '𦾱' => '𦾱',
  '𧃒' => '𧃒',
  '䕫' => '䕫',
  '虐' => '虐',
  '虜' => '虜',
  '虧' => '虧',
  '虩' => '虩',
  '蚩' => '蚩',
  '蚈' => '蚈',
  '蜎' => '蜎',
  '蛢' => '蛢',
  '蝹' => '蝹',
  '蜨' => '蜨',
  '蝫' => '蝫',
  '螆' => '螆',
  '䗗' => '䗗',
  '蟡' => '蟡',
  '蠁' => '蠁',
  '䗹' => '䗹',
  '衠' => '衠',
  '衣' => '衣',
  '𧙧' => '𧙧',
  '裗' => '裗',
  '裞' => '裞',
  '䘵' => '䘵',
  '裺' => '裺',
  '㒻' => '㒻',
  '𧢮' => '𧢮',
  '𧥦' => '𧥦',
  '䚾' => '䚾',
  '䛇' => '䛇',
  '誠' => '誠',
  '諭' => '諭',
  '變' => '變',
  '豕' => '豕',
  '𧲨' => '𧲨',
  '貫' => '貫',
  '賁' => '賁',
  '贛' => '贛',
  '起' => '起',
  '𧼯' => '𧼯',
  '𠠄' => '𠠄',
  '跋' => '跋',
  '趼' => '趼',
  '跰' => '跰',
  '𠣞' => '𠣞',
  '軔' => '軔',
  '輸' => '輸',
  '𨗒' => '𨗒',
  '𨗭' => '𨗭',
  '邔' => '邔',
  '郱' => '郱',
  '鄑' => '鄑',
  '𨜮' => '𨜮',
  '鄛' => '鄛',
  '鈸' => '鈸',
  '鋗' => '鋗',
  '鋘' => '鋘',
  '鉼' => '鉼',
  '鏹' => '鏹',
  '鐕' => '鐕',
  '𨯺' => '𨯺',
  '開' => '開',
  '䦕' => '䦕',
  '閷' => '閷',
  '𨵷' => '𨵷',
  '䧦' => '䧦',
  '雃' => '雃',
  '嶲' => '嶲',
  '霣' => '霣',
  '𩅅' => '𩅅',
  '𩈚' => '𩈚',
  '䩮' => '䩮',
  '䩶' => '䩶',
  '韠' => '韠',
  '𩐊' => '𩐊',
  '䪲' => '䪲',
  '𩒖' => '𩒖',
  '頋' => '頋',
  '頋' => '頋',
  '頩' => '頩',
  '𩖶' => '𩖶',
  '飢' => '飢',
  '䬳' => '䬳',
  '餩' => '餩',
  '馧' => '馧',
  '駂' => '駂',
  '駾' => '駾',
  '䯎' => '䯎',
  '𩬰' => '𩬰',
  '鬒' => '鬒',
  '鱀' => '鱀',
  '鳽' => '鳽',
  '䳎' => '䳎',
  '䳭' => '䳭',
  '鵧' => '鵧',
  '𪃎' => '𪃎',
  '䳸' => '䳸',
  '𪄅' => '𪄅',
  '𪈎' => '𪈎',
  '𪊑' => '𪊑',
  '麻' => '麻',
  '䵖' => '䵖',
  '黹' => '黹',
  '黾' => '黾',
  '鼅' => '鼅',
  '鼏' => '鼏',
  '鼖' => '鼖',
  '鼻' => '鼻',
  '𪘀' => '𪘀',
);
<?php

return array (
  ' ' => ' ',
  '¨' => ' ̈',
  'ª' => 'a',
  '¯' => ' ̄',
  '²' => '2',
  '³' => '3',
  '´' => ' ́',
  'µ' => 'μ',
  '¸' => ' ̧',
  '¹' => '1',
  'º' => 'o',
  '¼' => '1⁄4',
  '½' => '1⁄2',
  '¾' => '3⁄4',
  'IJ' => 'IJ',
  'ij' => 'ij',
  'Ŀ' => 'L·',
  'ŀ' => 'l·',
  'ʼn' => 'ʼn',
  'ſ' => 's',
  'DŽ' => 'DŽ',
  'Dž' => 'Dž',
  'dž' => 'dž',
  'LJ' => 'LJ',
  'Lj' => 'Lj',
  'lj' => 'lj',
  'NJ' => 'NJ',
  'Nj' => 'Nj',
  'nj' => 'nj',
  'DZ' => 'DZ',
  'Dz' => 'Dz',
  'dz' => 'dz',
  'ʰ' => 'h',
  'ʱ' => 'ɦ',
  'ʲ' => 'j',
  'ʳ' => 'r',
  'ʴ' => 'ɹ',
  'ʵ' => 'ɻ',
  'ʶ' => 'ʁ',
  'ʷ' => 'w',
  'ʸ' => 'y',
  '˘' => ' ̆',
  '˙' => ' ̇',
  '˚' => ' ̊',
  '˛' => ' ̨',
  '˜' => ' ̃',
  '˝' => ' ̋',
  'ˠ' => 'ɣ',
  'ˡ' => 'l',
  'ˢ' => 's',
  'ˣ' => 'x',
  'ˤ' => 'ʕ',
  'ͺ' => ' ͅ',
  '΄' => ' ́',
  '΅' => ' ̈́',
  'ϐ' => 'β',
  'ϑ' => 'θ',
  'ϒ' => 'Υ',
  'ϓ' => 'Ύ',
  'ϔ' => 'Ϋ',
  'ϕ' => 'φ',
  'ϖ' => 'π',
  'ϰ' => 'κ',
  'ϱ' => 'ρ',
  'ϲ' => 'ς',
  'ϴ' => 'Θ',
  'ϵ' => 'ε',
  'Ϲ' => 'Σ',
  'և' => 'եւ',
  'ٵ' => 'اٴ',
  'ٶ' => 'وٴ',
  'ٷ' => 'ۇٴ',
  'ٸ' => 'يٴ',
  'ำ' => 'ํา',
  'ຳ' => 'ໍາ',
  'ໜ' => 'ຫນ',
  'ໝ' => 'ຫມ',
  '༌' => '་',
  'ཷ' => 'ྲཱྀ',
  'ཹ' => 'ླཱྀ',
  'ჼ' => 'ნ',
  'ᴬ' => 'A',
  'ᴭ' => 'Æ',
  'ᴮ' => 'B',
  'ᴰ' => 'D',
  'ᴱ' => 'E',
  'ᴲ' => 'Ǝ',
  'ᴳ' => 'G',
  'ᴴ' => 'H',
  'ᴵ' => 'I',
  'ᴶ' => 'J',
  'ᴷ' => 'K',
  'ᴸ' => 'L',
  'ᴹ' => 'M',
  'ᴺ' => 'N',
  'ᴼ' => 'O',
  'ᴽ' => 'Ȣ',
  'ᴾ' => 'P',
  'ᴿ' => 'R',
  'ᵀ' => 'T',
  'ᵁ' => 'U',
  'ᵂ' => 'W',
  'ᵃ' => 'a',
  'ᵄ' => 'ɐ',
  'ᵅ' => 'ɑ',
  'ᵆ' => 'ᴂ',
  'ᵇ' => 'b',
  'ᵈ' => 'd',
  'ᵉ' => 'e',
  'ᵊ' => 'ə',
  'ᵋ' => 'ɛ',
  'ᵌ' => 'ɜ',
  'ᵍ' => 'g',
  'ᵏ' => 'k',
  'ᵐ' => 'm',
  'ᵑ' => 'ŋ',
  'ᵒ' => 'o',
  'ᵓ' => 'ɔ',
  'ᵔ' => 'ᴖ',
  'ᵕ' => 'ᴗ',
  'ᵖ' => 'p',
  'ᵗ' => 't',
  'ᵘ' => 'u',
  'ᵙ' => 'ᴝ',
  'ᵚ' => 'ɯ',
  'ᵛ' => 'v',
  'ᵜ' => 'ᴥ',
  'ᵝ' => 'β',
  'ᵞ' => 'γ',
  'ᵟ' => 'δ',
  'ᵠ' => 'φ',
  'ᵡ' => 'χ',
  'ᵢ' => 'i',
  'ᵣ' => 'r',
  'ᵤ' => 'u',
  'ᵥ' => 'v',
  'ᵦ' => 'β',
  'ᵧ' => 'γ',
  'ᵨ' => 'ρ',
  'ᵩ' => 'φ',
  'ᵪ' => 'χ',
  'ᵸ' => 'н',
  'ᶛ' => 'ɒ',
  'ᶜ' => 'c',
  'ᶝ' => 'ɕ',
  'ᶞ' => 'ð',
  'ᶟ' => 'ɜ',
  'ᶠ' => 'f',
  'ᶡ' => 'ɟ',
  'ᶢ' => 'ɡ',
  'ᶣ' => 'ɥ',
  'ᶤ' => 'ɨ',
  'ᶥ' => 'ɩ',
  'ᶦ' => 'ɪ',
  'ᶧ' => 'ᵻ',
  'ᶨ' => 'ʝ',
  'ᶩ' => 'ɭ',
  'ᶪ' => 'ᶅ',
  'ᶫ' => 'ʟ',
  'ᶬ' => 'ɱ',
  'ᶭ' => 'ɰ',
  'ᶮ' => 'ɲ',
  'ᶯ' => 'ɳ',
  'ᶰ' => 'ɴ',
  'ᶱ' => 'ɵ',
  'ᶲ' => 'ɸ',
  'ᶳ' => 'ʂ',
  'ᶴ' => 'ʃ',
  'ᶵ' => 'ƫ',
  'ᶶ' => 'ʉ',
  'ᶷ' => 'ʊ',
  'ᶸ' => 'ᴜ',
  'ᶹ' => 'ʋ',
  'ᶺ' => 'ʌ',
  'ᶻ' => 'z',
  'ᶼ' => 'ʐ',
  'ᶽ' => 'ʑ',
  'ᶾ' => 'ʒ',
  'ᶿ' => 'θ',
  'ẚ' => 'aʾ',
  'ẛ' => 'ṡ',
  '᾽' => ' ̓',
  '᾿' => ' ̓',
  '῀' => ' ͂',
  '῁' => ' ̈͂',
  '῍' => ' ̓̀',
  '῎' => ' ̓́',
  '῏' => ' ̓͂',
  '῝' => ' ̔̀',
  '῞' => ' ̔́',
  '῟' => ' ̔͂',
  '῭' => ' ̈̀',
  '΅' => ' ̈́',
  '´' => ' ́',
  '῾' => ' ̔',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  '‑' => '‐',
  '‗' => ' ̳',
  '․' => '.',
  '‥' => '..',
  '…' => '...',
  ' ' => ' ',
  '″' => '′′',
  '‴' => '′′′',
  '‶' => '‵‵',
  '‷' => '‵‵‵',
  '‼' => '!!',
  '‾' => ' ̅',
  '⁇' => '??',
  '⁈' => '?!',
  '⁉' => '!?',
  '⁗' => '′′′′',
  ' ' => ' ',
  '⁰' => '0',
  'ⁱ' => 'i',
  '⁴' => '4',
  '⁵' => '5',
  '⁶' => '6',
  '⁷' => '7',
  '⁸' => '8',
  '⁹' => '9',
  '⁺' => '+',
  '⁻' => '−',
  '⁼' => '=',
  '⁽' => '(',
  '⁾' => ')',
  'ⁿ' => 'n',
  '₀' => '0',
  '₁' => '1',
  '₂' => '2',
  '₃' => '3',
  '₄' => '4',
  '₅' => '5',
  '₆' => '6',
  '₇' => '7',
  '₈' => '8',
  '₉' => '9',
  '₊' => '+',
  '₋' => '−',
  '₌' => '=',
  '₍' => '(',
  '₎' => ')',
  'ₐ' => 'a',
  'ₑ' => 'e',
  'ₒ' => 'o',
  'ₓ' => 'x',
  'ₔ' => 'ə',
  'ₕ' => 'h',
  'ₖ' => 'k',
  'ₗ' => 'l',
  'ₘ' => 'm',
  'ₙ' => 'n',
  'ₚ' => 'p',
  'ₛ' => 's',
  'ₜ' => 't',
  '₨' => 'Rs',
  '℀' => 'a/c',
  '℁' => 'a/s',
  'ℂ' => 'C',
  '℃' => '°C',
  '℅' => 'c/o',
  '℆' => 'c/u',
  'ℇ' => 'Ɛ',
  '℉' => '°F',
  'ℊ' => 'g',
  'ℋ' => 'H',
  'ℌ' => 'H',
  'ℍ' => 'H',
  'ℎ' => 'h',
  'ℏ' => 'ħ',
  'ℐ' => 'I',
  'ℑ' => 'I',
  'ℒ' => 'L',
  'ℓ' => 'l',
  'ℕ' => 'N',
  '№' => 'No',
  'ℙ' => 'P',
  'ℚ' => 'Q',
  'ℛ' => 'R',
  'ℜ' => 'R',
  'ℝ' => 'R',
  '℠' => 'SM',
  '℡' => 'TEL',
  '™' => 'TM',
  'ℤ' => 'Z',
  'ℨ' => 'Z',
  'ℬ' => 'B',
  'ℭ' => 'C',
  'ℯ' => 'e',
  'ℰ' => 'E',
  'ℱ' => 'F',
  'ℳ' => 'M',
  'ℴ' => 'o',
  'ℵ' => 'א',
  'ℶ' => 'ב',
  'ℷ' => 'ג',
  'ℸ' => 'ד',
  'ℹ' => 'i',
  '℻' => 'FAX',
  'ℼ' => 'π',
  'ℽ' => 'γ',
  'ℾ' => 'Γ',
  'ℿ' => 'Π',
  '⅀' => '∑',
  'ⅅ' => 'D',
  'ⅆ' => 'd',
  'ⅇ' => 'e',
  'ⅈ' => 'i',
  'ⅉ' => 'j',
  '⅐' => '1⁄7',
  '⅑' => '1⁄9',
  '⅒' => '1⁄10',
  '⅓' => '1⁄3',
  '⅔' => '2⁄3',
  '⅕' => '1⁄5',
  '⅖' => '2⁄5',
  '⅗' => '3⁄5',
  '⅘' => '4⁄5',
  '⅙' => '1⁄6',
  '⅚' => '5⁄6',
  '⅛' => '1⁄8',
  '⅜' => '3⁄8',
  '⅝' => '5⁄8',
  '⅞' => '7⁄8',
  '⅟' => '1⁄',
  'Ⅰ' => 'I',
  'Ⅱ' => 'II',
  'Ⅲ' => 'III',
  'Ⅳ' => 'IV',
  'Ⅴ' => 'V',
  'Ⅵ' => 'VI',
  'Ⅶ' => 'VII',
  'Ⅷ' => 'VIII',
  'Ⅸ' => 'IX',
  'Ⅹ' => 'X',
  'Ⅺ' => 'XI',
  'Ⅻ' => 'XII',
  'Ⅼ' => 'L',
  'Ⅽ' => 'C',
  'Ⅾ' => 'D',
  'Ⅿ' => 'M',
  'ⅰ' => 'i',
  'ⅱ' => 'ii',
  'ⅲ' => 'iii',
  'ⅳ' => 'iv',
  'ⅴ' => 'v',
  'ⅵ' => 'vi',
  'ⅶ' => 'vii',
  'ⅷ' => 'viii',
  'ⅸ' => 'ix',
  'ⅹ' => 'x',
  'ⅺ' => 'xi',
  'ⅻ' => 'xii',
  'ⅼ' => 'l',
  'ⅽ' => 'c',
  'ⅾ' => 'd',
  'ⅿ' => 'm',
  '↉' => '0⁄3',
  '∬' => '∫∫',
  '∭' => '∫∫∫',
  '∯' => '∮∮',
  '∰' => '∮∮∮',
  '①' => '1',
  '②' => '2',
  '③' => '3',
  '④' => '4',
  '⑤' => '5',
  '⑥' => '6',
  '⑦' => '7',
  '⑧' => '8',
  '⑨' => '9',
  '⑩' => '10',
  '⑪' => '11',
  '⑫' => '12',
  '⑬' => '13',
  '⑭' => '14',
  '⑮' => '15',
  '⑯' => '16',
  '⑰' => '17',
  '⑱' => '18',
  '⑲' => '19',
  '⑳' => '20',
  '⑴' => '(1)',
  '⑵' => '(2)',
  '⑶' => '(3)',
  '⑷' => '(4)',
  '⑸' => '(5)',
  '⑹' => '(6)',
  '⑺' => '(7)',
  '⑻' => '(8)',
  '⑼' => '(9)',
  '⑽' => '(10)',
  '⑾' => '(11)',
  '⑿' => '(12)',
  '⒀' => '(13)',
  '⒁' => '(14)',
  '⒂' => '(15)',
  '⒃' => '(16)',
  '⒄' => '(17)',
  '⒅' => '(18)',
  '⒆' => '(19)',
  '⒇' => '(20)',
  '⒈' => '1.',
  '⒉' => '2.',
  '⒊' => '3.',
  '⒋' => '4.',
  '⒌' => '5.',
  '⒍' => '6.',
  '⒎' => '7.',
  '⒏' => '8.',
  '⒐' => '9.',
  '⒑' => '10.',
  '⒒' => '11.',
  '⒓' => '12.',
  '⒔' => '13.',
  '⒕' => '14.',
  '⒖' => '15.',
  '⒗' => '16.',
  '⒘' => '17.',
  '⒙' => '18.',
  '⒚' => '19.',
  '⒛' => '20.',
  '⒜' => '(a)',
  '⒝' => '(b)',
  '⒞' => '(c)',
  '⒟' => '(d)',
  '⒠' => '(e)',
  '⒡' => '(f)',
  '⒢' => '(g)',
  '⒣' => '(h)',
  '⒤' => '(i)',
  '⒥' => '(j)',
  '⒦' => '(k)',
  '⒧' => '(l)',
  '⒨' => '(m)',
  '⒩' => '(n)',
  '⒪' => '(o)',
  '⒫' => '(p)',
  '⒬' => '(q)',
  '⒭' => '(r)',
  '⒮' => '(s)',
  '⒯' => '(t)',
  '⒰' => '(u)',
  '⒱' => '(v)',
  '⒲' => '(w)',
  '⒳' => '(x)',
  '⒴' => '(y)',
  '⒵' => '(z)',
  'Ⓐ' => 'A',
  'Ⓑ' => 'B',
  'Ⓒ' => 'C',
  'Ⓓ' => 'D',
  'Ⓔ' => 'E',
  'Ⓕ' => 'F',
  'Ⓖ' => 'G',
  'Ⓗ' => 'H',
  'Ⓘ' => 'I',
  'Ⓙ' => 'J',
  'Ⓚ' => 'K',
  'Ⓛ' => 'L',
  'Ⓜ' => 'M',
  'Ⓝ' => 'N',
  'Ⓞ' => 'O',
  'Ⓟ' => 'P',
  'Ⓠ' => 'Q',
  'Ⓡ' => 'R',
  'Ⓢ' => 'S',
  'Ⓣ' => 'T',
  'Ⓤ' => 'U',
  'Ⓥ' => 'V',
  'Ⓦ' => 'W',
  'Ⓧ' => 'X',
  'Ⓨ' => 'Y',
  'Ⓩ' => 'Z',
  'ⓐ' => 'a',
  'ⓑ' => 'b',
  'ⓒ' => 'c',
  'ⓓ' => 'd',
  'ⓔ' => 'e',
  'ⓕ' => 'f',
  'ⓖ' => 'g',
  'ⓗ' => 'h',
  'ⓘ' => 'i',
  'ⓙ' => 'j',
  'ⓚ' => 'k',
  'ⓛ' => 'l',
  'ⓜ' => 'm',
  'ⓝ' => 'n',
  'ⓞ' => 'o',
  'ⓟ' => 'p',
  'ⓠ' => 'q',
  'ⓡ' => 'r',
  'ⓢ' => 's',
  'ⓣ' => 't',
  'ⓤ' => 'u',
  'ⓥ' => 'v',
  'ⓦ' => 'w',
  'ⓧ' => 'x',
  'ⓨ' => 'y',
  'ⓩ' => 'z',
  '⓪' => '0',
  '⨌' => '∫∫∫∫',
  '⩴' => '::=',
  '⩵' => '==',
  '⩶' => '===',
  'ⱼ' => 'j',
  'ⱽ' => 'V',
  'ⵯ' => 'ⵡ',
  '⺟' => '母',
  '⻳' => '龟',
  '⼀' => '一',
  '⼁' => '丨',
  '⼂' => '丶',
  '⼃' => '丿',
  '⼄' => '乙',
  '⼅' => '亅',
  '⼆' => '二',
  '⼇' => '亠',
  '⼈' => '人',
  '⼉' => '儿',
  '⼊' => '入',
  '⼋' => '八',
  '⼌' => '冂',
  '⼍' => '冖',
  '⼎' => '冫',
  '⼏' => '几',
  '⼐' => '凵',
  '⼑' => '刀',
  '⼒' => '力',
  '⼓' => '勹',
  '⼔' => '匕',
  '⼕' => '匚',
  '⼖' => '匸',
  '⼗' => '十',
  '⼘' => '卜',
  '⼙' => '卩',
  '⼚' => '厂',
  '⼛' => '厶',
  '⼜' => '又',
  '⼝' => '口',
  '⼞' => '囗',
  '⼟' => '土',
  '⼠' => '士',
  '⼡' => '夂',
  '⼢' => '夊',
  '⼣' => '夕',
  '⼤' => '大',
  '⼥' => '女',
  '⼦' => '子',
  '⼧' => '宀',
  '⼨' => '寸',
  '⼩' => '小',
  '⼪' => '尢',
  '⼫' => '尸',
  '⼬' => '屮',
  '⼭' => '山',
  '⼮' => '巛',
  '⼯' => '工',
  '⼰' => '己',
  '⼱' => '巾',
  '⼲' => '干',
  '⼳' => '幺',
  '⼴' => '广',
  '⼵' => '廴',
  '⼶' => '廾',
  '⼷' => '弋',
  '⼸' => '弓',
  '⼹' => '彐',
  '⼺' => '彡',
  '⼻' => '彳',
  '⼼' => '心',
  '⼽' => '戈',
  '⼾' => '戶',
  '⼿' => '手',
  '⽀' => '支',
  '⽁' => '攴',
  '⽂' => '文',
  '⽃' => '斗',
  '⽄' => '斤',
  '⽅' => '方',
  '⽆' => '无',
  '⽇' => '日',
  '⽈' => '曰',
  '⽉' => '月',
  '⽊' => '木',
  '⽋' => '欠',
  '⽌' => '止',
  '⽍' => '歹',
  '⽎' => '殳',
  '⽏' => '毋',
  '⽐' => '比',
  '⽑' => '毛',
  '⽒' => '氏',
  '⽓' => '气',
  '⽔' => '水',
  '⽕' => '火',
  '⽖' => '爪',
  '⽗' => '父',
  '⽘' => '爻',
  '⽙' => '爿',
  '⽚' => '片',
  '⽛' => '牙',
  '⽜' => '牛',
  '⽝' => '犬',
  '⽞' => '玄',
  '⽟' => '玉',
  '⽠' => '瓜',
  '⽡' => '瓦',
  '⽢' => '甘',
  '⽣' => '生',
  '⽤' => '用',
  '⽥' => '田',
  '⽦' => '疋',
  '⽧' => '疒',
  '⽨' => '癶',
  '⽩' => '白',
  '⽪' => '皮',
  '⽫' => '皿',
  '⽬' => '目',
  '⽭' => '矛',
  '⽮' => '矢',
  '⽯' => '石',
  '⽰' => '示',
  '⽱' => '禸',
  '⽲' => '禾',
  '⽳' => '穴',
  '⽴' => '立',
  '⽵' => '竹',
  '⽶' => '米',
  '⽷' => '糸',
  '⽸' => '缶',
  '⽹' => '网',
  '⽺' => '羊',
  '⽻' => '羽',
  '⽼' => '老',
  '⽽' => '而',
  '⽾' => '耒',
  '⽿' => '耳',
  '⾀' => '聿',
  '⾁' => '肉',
  '⾂' => '臣',
  '⾃' => '自',
  '⾄' => '至',
  '⾅' => '臼',
  '⾆' => '舌',
  '⾇' => '舛',
  '⾈' => '舟',
  '⾉' => '艮',
  '⾊' => '色',
  '⾋' => '艸',
  '⾌' => '虍',
  '⾍' => '虫',
  '⾎' => '血',
  '⾏' => '行',
  '⾐' => '衣',
  '⾑' => '襾',
  '⾒' => '見',
  '⾓' => '角',
  '⾔' => '言',
  '⾕' => '谷',
  '⾖' => '豆',
  '⾗' => '豕',
  '⾘' => '豸',
  '⾙' => '貝',
  '⾚' => '赤',
  '⾛' => '走',
  '⾜' => '足',
  '⾝' => '身',
  '⾞' => '車',
  '⾟' => '辛',
  '⾠' => '辰',
  '⾡' => '辵',
  '⾢' => '邑',
  '⾣' => '酉',
  '⾤' => '釆',
  '⾥' => '里',
  '⾦' => '金',
  '⾧' => '長',
  '⾨' => '門',
  '⾩' => '阜',
  '⾪' => '隶',
  '⾫' => '隹',
  '⾬' => '雨',
  '⾭' => '靑',
  '⾮' => '非',
  '⾯' => '面',
  '⾰' => '革',
  '⾱' => '韋',
  '⾲' => '韭',
  '⾳' => '音',
  '⾴' => '頁',
  '⾵' => '風',
  '⾶' => '飛',
  '⾷' => '食',
  '⾸' => '首',
  '⾹' => '香',
  '⾺' => '馬',
  '⾻' => '骨',
  '⾼' => '高',
  '⾽' => '髟',
  '⾾' => '鬥',
  '⾿' => '鬯',
  '⿀' => '鬲',
  '⿁' => '鬼',
  '⿂' => '魚',
  '⿃' => '鳥',
  '⿄' => '鹵',
  '⿅' => '鹿',
  '⿆' => '麥',
  '⿇' => '麻',
  '⿈' => '黃',
  '⿉' => '黍',
  '⿊' => '黑',
  '⿋' => '黹',
  '⿌' => '黽',
  '⿍' => '鼎',
  '⿎' => '鼓',
  '⿏' => '鼠',
  '⿐' => '鼻',
  '⿑' => '齊',
  '⿒' => '齒',
  '⿓' => '龍',
  '⿔' => '龜',
  '⿕' => '龠',
  ' ' => ' ',
  '〶' => '〒',
  '〸' => '十',
  '〹' => '卄',
  '〺' => '卅',
  '゛' => ' ゙',
  '゜' => ' ゚',
  'ゟ' => 'より',
  'ヿ' => 'コト',
  'ㄱ' => 'ᄀ',
  'ㄲ' => 'ᄁ',
  'ㄳ' => 'ᆪ',
  'ㄴ' => 'ᄂ',
  'ㄵ' => 'ᆬ',
  'ㄶ' => 'ᆭ',
  'ㄷ' => 'ᄃ',
  'ㄸ' => 'ᄄ',
  'ㄹ' => 'ᄅ',
  'ㄺ' => 'ᆰ',
  'ㄻ' => 'ᆱ',
  'ㄼ' => 'ᆲ',
  'ㄽ' => 'ᆳ',
  'ㄾ' => 'ᆴ',
  'ㄿ' => 'ᆵ',
  'ㅀ' => 'ᄚ',
  'ㅁ' => 'ᄆ',
  'ㅂ' => 'ᄇ',
  'ㅃ' => 'ᄈ',
  'ㅄ' => 'ᄡ',
  'ㅅ' => 'ᄉ',
  'ㅆ' => 'ᄊ',
  'ㅇ' => 'ᄋ',
  'ㅈ' => 'ᄌ',
  'ㅉ' => 'ᄍ',
  'ㅊ' => 'ᄎ',
  'ㅋ' => 'ᄏ',
  'ㅌ' => 'ᄐ',
  'ㅍ' => 'ᄑ',
  'ㅎ' => 'ᄒ',
  'ㅏ' => 'ᅡ',
  'ㅐ' => 'ᅢ',
  'ㅑ' => 'ᅣ',
  'ㅒ' => 'ᅤ',
  'ㅓ' => 'ᅥ',
  'ㅔ' => 'ᅦ',
  'ㅕ' => 'ᅧ',
  'ㅖ' => 'ᅨ',
  'ㅗ' => 'ᅩ',
  'ㅘ' => 'ᅪ',
  'ㅙ' => 'ᅫ',
  'ㅚ' => 'ᅬ',
  'ㅛ' => 'ᅭ',
  'ㅜ' => 'ᅮ',
  'ㅝ' => 'ᅯ',
  'ㅞ' => 'ᅰ',
  'ㅟ' => 'ᅱ',
  'ㅠ' => 'ᅲ',
  'ㅡ' => 'ᅳ',
  'ㅢ' => 'ᅴ',
  'ㅣ' => 'ᅵ',
  'ㅤ' => 'ᅠ',
  'ㅥ' => 'ᄔ',
  'ㅦ' => 'ᄕ',
  'ㅧ' => 'ᇇ',
  'ㅨ' => 'ᇈ',
  'ㅩ' => 'ᇌ',
  'ㅪ' => 'ᇎ',
  'ㅫ' => 'ᇓ',
  'ㅬ' => 'ᇗ',
  'ㅭ' => 'ᇙ',
  'ㅮ' => 'ᄜ',
  'ㅯ' => 'ᇝ',
  'ㅰ' => 'ᇟ',
  'ㅱ' => 'ᄝ',
  'ㅲ' => 'ᄞ',
  'ㅳ' => 'ᄠ',
  'ㅴ' => 'ᄢ',
  'ㅵ' => 'ᄣ',
  'ㅶ' => 'ᄧ',
  'ㅷ' => 'ᄩ',
  'ㅸ' => 'ᄫ',
  'ㅹ' => 'ᄬ',
  'ㅺ' => 'ᄭ',
  'ㅻ' => 'ᄮ',
  'ㅼ' => 'ᄯ',
  'ㅽ' => 'ᄲ',
  'ㅾ' => 'ᄶ',
  'ㅿ' => 'ᅀ',
  'ㆀ' => 'ᅇ',
  'ㆁ' => 'ᅌ',
  'ㆂ' => 'ᇱ',
  'ㆃ' => 'ᇲ',
  'ㆄ' => 'ᅗ',
  'ㆅ' => 'ᅘ',
  'ㆆ' => 'ᅙ',
  'ㆇ' => 'ᆄ',
  'ㆈ' => 'ᆅ',
  'ㆉ' => 'ᆈ',
  'ㆊ' => 'ᆑ',
  'ㆋ' => 'ᆒ',
  'ㆌ' => 'ᆔ',
  'ㆍ' => 'ᆞ',
  'ㆎ' => 'ᆡ',
  '㆒' => '一',
  '㆓' => '二',
  '㆔' => '三',
  '㆕' => '四',
  '㆖' => '上',
  '㆗' => '中',
  '㆘' => '下',
  '㆙' => '甲',
  '㆚' => '乙',
  '㆛' => '丙',
  '㆜' => '丁',
  '㆝' => '天',
  '㆞' => '地',
  '㆟' => '人',
  '㈀' => '(ᄀ)',
  '㈁' => '(ᄂ)',
  '㈂' => '(ᄃ)',
  '㈃' => '(ᄅ)',
  '㈄' => '(ᄆ)',
  '㈅' => '(ᄇ)',
  '㈆' => '(ᄉ)',
  '㈇' => '(ᄋ)',
  '㈈' => '(ᄌ)',
  '㈉' => '(ᄎ)',
  '㈊' => '(ᄏ)',
  '㈋' => '(ᄐ)',
  '㈌' => '(ᄑ)',
  '㈍' => '(ᄒ)',
  '㈎' => '(가)',
  '㈏' => '(나)',
  '㈐' => '(다)',
  '㈑' => '(라)',
  '㈒' => '(마)',
  '㈓' => '(바)',
  '㈔' => '(사)',
  '㈕' => '(아)',
  '㈖' => '(자)',
  '㈗' => '(차)',
  '㈘' => '(카)',
  '㈙' => '(타)',
  '㈚' => '(파)',
  '㈛' => '(하)',
  '㈜' => '(주)',
  '㈝' => '(오전)',
  '㈞' => '(오후)',
  '㈠' => '(一)',
  '㈡' => '(二)',
  '㈢' => '(三)',
  '㈣' => '(四)',
  '㈤' => '(五)',
  '㈥' => '(六)',
  '㈦' => '(七)',
  '㈧' => '(八)',
  '㈨' => '(九)',
  '㈩' => '(十)',
  '㈪' => '(月)',
  '㈫' => '(火)',
  '㈬' => '(水)',
  '㈭' => '(木)',
  '㈮' => '(金)',
  '㈯' => '(土)',
  '㈰' => '(日)',
  '㈱' => '(株)',
  '㈲' => '(有)',
  '㈳' => '(社)',
  '㈴' => '(名)',
  '㈵' => '(特)',
  '㈶' => '(財)',
  '㈷' => '(祝)',
  '㈸' => '(労)',
  '㈹' => '(代)',
  '㈺' => '(呼)',
  '㈻' => '(学)',
  '㈼' => '(監)',
  '㈽' => '(企)',
  '㈾' => '(資)',
  '㈿' => '(協)',
  '㉀' => '(祭)',
  '㉁' => '(休)',
  '㉂' => '(自)',
  '㉃' => '(至)',
  '㉄' => '問',
  '㉅' => '幼',
  '㉆' => '文',
  '㉇' => '箏',
  '㉐' => 'PTE',
  '㉑' => '21',
  '㉒' => '22',
  '㉓' => '23',
  '㉔' => '24',
  '㉕' => '25',
  '㉖' => '26',
  '㉗' => '27',
  '㉘' => '28',
  '㉙' => '29',
  '㉚' => '30',
  '㉛' => '31',
  '㉜' => '32',
  '㉝' => '33',
  '㉞' => '34',
  '㉟' => '35',
  '㉠' => 'ᄀ',
  '㉡' => 'ᄂ',
  '㉢' => 'ᄃ',
  '㉣' => 'ᄅ',
  '㉤' => 'ᄆ',
  '㉥' => 'ᄇ',
  '㉦' => 'ᄉ',
  '㉧' => 'ᄋ',
  '㉨' => 'ᄌ',
  '㉩' => 'ᄎ',
  '㉪' => 'ᄏ',
  '㉫' => 'ᄐ',
  '㉬' => 'ᄑ',
  '㉭' => 'ᄒ',
  '㉮' => '가',
  '㉯' => '나',
  '㉰' => '다',
  '㉱' => '라',
  '㉲' => '마',
  '㉳' => '바',
  '㉴' => '사',
  '㉵' => '아',
  '㉶' => '자',
  '㉷' => '차',
  '㉸' => '카',
  '㉹' => '타',
  '㉺' => '파',
  '㉻' => '하',
  '㉼' => '참고',
  '㉽' => '주의',
  '㉾' => '우',
  '㊀' => '一',
  '㊁' => '二',
  '㊂' => '三',
  '㊃' => '四',
  '㊄' => '五',
  '㊅' => '六',
  '㊆' => '七',
  '㊇' => '八',
  '㊈' => '九',
  '㊉' => '十',
  '㊊' => '月',
  '㊋' => '火',
  '㊌' => '水',
  '㊍' => '木',
  '㊎' => '金',
  '㊏' => '土',
  '㊐' => '日',
  '㊑' => '株',
  '㊒' => '有',
  '㊓' => '社',
  '㊔' => '名',
  '㊕' => '特',
  '㊖' => '財',
  '㊗' => '祝',
  '㊘' => '労',
  '㊙' => '秘',
  '㊚' => '男',
  '㊛' => '女',
  '㊜' => '適',
  '㊝' => '優',
  '㊞' => '印',
  '㊟' => '注',
  '㊠' => '項',
  '㊡' => '休',
  '㊢' => '写',
  '㊣' => '正',
  '㊤' => '上',
  '㊥' => '中',
  '㊦' => '下',
  '㊧' => '左',
  '㊨' => '右',
  '㊩' => '医',
  '㊪' => '宗',
  '㊫' => '学',
  '㊬' => '監',
  '㊭' => '企',
  '㊮' => '資',
  '㊯' => '協',
  '㊰' => '夜',
  '㊱' => '36',
  '㊲' => '37',
  '㊳' => '38',
  '㊴' => '39',
  '㊵' => '40',
  '㊶' => '41',
  '㊷' => '42',
  '㊸' => '43',
  '㊹' => '44',
  '㊺' => '45',
  '㊻' => '46',
  '㊼' => '47',
  '㊽' => '48',
  '㊾' => '49',
  '㊿' => '50',
  '㋀' => '1月',
  '㋁' => '2月',
  '㋂' => '3月',
  '㋃' => '4月',
  '㋄' => '5月',
  '㋅' => '6月',
  '㋆' => '7月',
  '㋇' => '8月',
  '㋈' => '9月',
  '㋉' => '10月',
  '㋊' => '11月',
  '㋋' => '12月',
  '㋌' => 'Hg',
  '㋍' => 'erg',
  '㋎' => 'eV',
  '㋏' => 'LTD',
  '㋐' => 'ア',
  '㋑' => 'イ',
  '㋒' => 'ウ',
  '㋓' => 'エ',
  '㋔' => 'オ',
  '㋕' => 'カ',
  '㋖' => 'キ',
  '㋗' => 'ク',
  '㋘' => 'ケ',
  '㋙' => 'コ',
  '㋚' => 'サ',
  '㋛' => 'シ',
  '㋜' => 'ス',
  '㋝' => 'セ',
  '㋞' => 'ソ',
  '㋟' => 'タ',
  '㋠' => 'チ',
  '㋡' => 'ツ',
  '㋢' => 'テ',
  '㋣' => 'ト',
  '㋤' => 'ナ',
  '㋥' => 'ニ',
  '㋦' => 'ヌ',
  '㋧' => 'ネ',
  '㋨' => 'ノ',
  '㋩' => 'ハ',
  '㋪' => 'ヒ',
  '㋫' => 'フ',
  '㋬' => 'ヘ',
  '㋭' => 'ホ',
  '㋮' => 'マ',
  '㋯' => 'ミ',
  '㋰' => 'ム',
  '㋱' => 'メ',
  '㋲' => 'モ',
  '㋳' => 'ヤ',
  '㋴' => 'ユ',
  '㋵' => 'ヨ',
  '㋶' => 'ラ',
  '㋷' => 'リ',
  '㋸' => 'ル',
  '㋹' => 'レ',
  '㋺' => 'ロ',
  '㋻' => 'ワ',
  '㋼' => 'ヰ',
  '㋽' => 'ヱ',
  '㋾' => 'ヲ',
  '㋿' => '令和',
  '㌀' => 'アパート',
  '㌁' => 'アルファ',
  '㌂' => 'アンペア',
  '㌃' => 'アール',
  '㌄' => 'イニング',
  '㌅' => 'インチ',
  '㌆' => 'ウォン',
  '㌇' => 'エスクード',
  '㌈' => 'エーカー',
  '㌉' => 'オンス',
  '㌊' => 'オーム',
  '㌋' => 'カイリ',
  '㌌' => 'カラット',
  '㌍' => 'カロリー',
  '㌎' => 'ガロン',
  '㌏' => 'ガンマ',
  '㌐' => 'ギガ',
  '㌑' => 'ギニー',
  '㌒' => 'キュリー',
  '㌓' => 'ギルダー',
  '㌔' => 'キロ',
  '㌕' => 'キログラム',
  '㌖' => 'キロメートル',
  '㌗' => 'キロワット',
  '㌘' => 'グラム',
  '㌙' => 'グラムトン',
  '㌚' => 'クルゼイロ',
  '㌛' => 'クローネ',
  '㌜' => 'ケース',
  '㌝' => 'コルナ',
  '㌞' => 'コーポ',
  '㌟' => 'サイクル',
  '㌠' => 'サンチーム',
  '㌡' => 'シリング',
  '㌢' => 'センチ',
  '㌣' => 'セント',
  '㌤' => 'ダース',
  '㌥' => 'デシ',
  '㌦' => 'ドル',
  '㌧' => 'トン',
  '㌨' => 'ナノ',
  '㌩' => 'ノット',
  '㌪' => 'ハイツ',
  '㌫' => 'パーセント',
  '㌬' => 'パーツ',
  '㌭' => 'バーレル',
  '㌮' => 'ピアストル',
  '㌯' => 'ピクル',
  '㌰' => 'ピコ',
  '㌱' => 'ビル',
  '㌲' => 'ファラッド',
  '㌳' => 'フィート',
  '㌴' => 'ブッシェル',
  '㌵' => 'フラン',
  '㌶' => 'ヘクタール',
  '㌷' => 'ペソ',
  '㌸' => 'ペニヒ',
  '㌹' => 'ヘルツ',
  '㌺' => 'ペンス',
  '㌻' => 'ページ',
  '㌼' => 'ベータ',
  '㌽' => 'ポイント',
  '㌾' => 'ボルト',
  '㌿' => 'ホン',
  '㍀' => 'ポンド',
  '㍁' => 'ホール',
  '㍂' => 'ホーン',
  '㍃' => 'マイクロ',
  '㍄' => 'マイル',
  '㍅' => 'マッハ',
  '㍆' => 'マルク',
  '㍇' => 'マンション',
  '㍈' => 'ミクロン',
  '㍉' => 'ミリ',
  '㍊' => 'ミリバール',
  '㍋' => 'メガ',
  '㍌' => 'メガトン',
  '㍍' => 'メートル',
  '㍎' => 'ヤード',
  '㍏' => 'ヤール',
  '㍐' => 'ユアン',
  '㍑' => 'リットル',
  '㍒' => 'リラ',
  '㍓' => 'ルピー',
  '㍔' => 'ルーブル',
  '㍕' => 'レム',
  '㍖' => 'レントゲン',
  '㍗' => 'ワット',
  '㍘' => '0点',
  '㍙' => '1点',
  '㍚' => '2点',
  '㍛' => '3点',
  '㍜' => '4点',
  '㍝' => '5点',
  '㍞' => '6点',
  '㍟' => '7点',
  '㍠' => '8点',
  '㍡' => '9点',
  '㍢' => '10点',
  '㍣' => '11点',
  '㍤' => '12点',
  '㍥' => '13点',
  '㍦' => '14点',
  '㍧' => '15点',
  '㍨' => '16点',
  '㍩' => '17点',
  '㍪' => '18点',
  '㍫' => '19点',
  '㍬' => '20点',
  '㍭' => '21点',
  '㍮' => '22点',
  '㍯' => '23点',
  '㍰' => '24点',
  '㍱' => 'hPa',
  '㍲' => 'da',
  '㍳' => 'AU',
  '㍴' => 'bar',
  '㍵' => 'oV',
  '㍶' => 'pc',
  '㍷' => 'dm',
  '㍸' => 'dm2',
  '㍹' => 'dm3',
  '㍺' => 'IU',
  '㍻' => '平成',
  '㍼' => '昭和',
  '㍽' => '大正',
  '㍾' => '明治',
  '㍿' => '株式会社',
  '㎀' => 'pA',
  '㎁' => 'nA',
  '㎂' => 'μA',
  '㎃' => 'mA',
  '㎄' => 'kA',
  '㎅' => 'KB',
  '㎆' => 'MB',
  '㎇' => 'GB',
  '㎈' => 'cal',
  '㎉' => 'kcal',
  '㎊' => 'pF',
  '㎋' => 'nF',
  '㎌' => 'μF',
  '㎍' => 'μg',
  '㎎' => 'mg',
  '㎏' => 'kg',
  '㎐' => 'Hz',
  '㎑' => 'kHz',
  '㎒' => 'MHz',
  '㎓' => 'GHz',
  '㎔' => 'THz',
  '㎕' => 'μl',
  '㎖' => 'ml',
  '㎗' => 'dl',
  '㎘' => 'kl',
  '㎙' => 'fm',
  '㎚' => 'nm',
  '㎛' => 'μm',
  '㎜' => 'mm',
  '㎝' => 'cm',
  '㎞' => 'km',
  '㎟' => 'mm2',
  '㎠' => 'cm2',
  '㎡' => 'm2',
  '㎢' => 'km2',
  '㎣' => 'mm3',
  '㎤' => 'cm3',
  '㎥' => 'm3',
  '㎦' => 'km3',
  '㎧' => 'm∕s',
  '㎨' => 'm∕s2',
  '㎩' => 'Pa',
  '㎪' => 'kPa',
  '㎫' => 'MPa',
  '㎬' => 'GPa',
  '㎭' => 'rad',
  '㎮' => 'rad∕s',
  '㎯' => 'rad∕s2',
  '㎰' => 'ps',
  '㎱' => 'ns',
  '㎲' => 'μs',
  '㎳' => 'ms',
  '㎴' => 'pV',
  '㎵' => 'nV',
  '㎶' => 'μV',
  '㎷' => 'mV',
  '㎸' => 'kV',
  '㎹' => 'MV',
  '㎺' => 'pW',
  '㎻' => 'nW',
  '㎼' => 'μW',
  '㎽' => 'mW',
  '㎾' => 'kW',
  '㎿' => 'MW',
  '㏀' => 'kΩ',
  '㏁' => 'MΩ',
  '㏂' => 'a.m.',
  '㏃' => 'Bq',
  '㏄' => 'cc',
  '㏅' => 'cd',
  '㏆' => 'C∕kg',
  '㏇' => 'Co.',
  '㏈' => 'dB',
  '㏉' => 'Gy',
  '㏊' => 'ha',
  '㏋' => 'HP',
  '㏌' => 'in',
  '㏍' => 'KK',
  '㏎' => 'KM',
  '㏏' => 'kt',
  '㏐' => 'lm',
  '㏑' => 'ln',
  '㏒' => 'log',
  '㏓' => 'lx',
  '㏔' => 'mb',
  '㏕' => 'mil',
  '㏖' => 'mol',
  '㏗' => 'PH',
  '㏘' => 'p.m.',
  '㏙' => 'PPM',
  '㏚' => 'PR',
  '㏛' => 'sr',
  '㏜' => 'Sv',
  '㏝' => 'Wb',
  '㏞' => 'V∕m',
  '㏟' => 'A∕m',
  '㏠' => '1日',
  '㏡' => '2日',
  '㏢' => '3日',
  '㏣' => '4日',
  '㏤' => '5日',
  '㏥' => '6日',
  '㏦' => '7日',
  '㏧' => '8日',
  '㏨' => '9日',
  '㏩' => '10日',
  '㏪' => '11日',
  '㏫' => '12日',
  '㏬' => '13日',
  '㏭' => '14日',
  '㏮' => '15日',
  '㏯' => '16日',
  '㏰' => '17日',
  '㏱' => '18日',
  '㏲' => '19日',
  '㏳' => '20日',
  '㏴' => '21日',
  '㏵' => '22日',
  '㏶' => '23日',
  '㏷' => '24日',
  '㏸' => '25日',
  '㏹' => '26日',
  '㏺' => '27日',
  '㏻' => '28日',
  '㏼' => '29日',
  '㏽' => '30日',
  '㏾' => '31日',
  '㏿' => 'gal',
  'ꚜ' => 'ъ',
  'ꚝ' => 'ь',
  'ꝰ' => 'ꝯ',
  'ꟸ' => 'Ħ',
  'ꟹ' => 'œ',
  'ꭜ' => 'ꜧ',
  'ꭝ' => 'ꬷ',
  'ꭞ' => 'ɫ',
  'ꭟ' => 'ꭒ',
  'ꭩ' => 'ʍ',
  'ff' => 'ff',
  'fi' => 'fi',
  'fl' => 'fl',
  'ffi' => 'ffi',
  'ffl' => 'ffl',
  'ſt' => 'st',
  'st' => 'st',
  'ﬓ' => 'մն',
  'ﬔ' => 'մե',
  'ﬕ' => 'մի',
  'ﬖ' => 'վն',
  'ﬗ' => 'մխ',
  'ﬠ' => 'ע',
  'ﬡ' => 'א',
  'ﬢ' => 'ד',
  'ﬣ' => 'ה',
  'ﬤ' => 'כ',
  'ﬥ' => 'ל',
  'ﬦ' => 'ם',
  'ﬧ' => 'ר',
  'ﬨ' => 'ת',
  '﬩' => '+',
  'ﭏ' => 'אל',
  'ﭐ' => 'ٱ',
  'ﭑ' => 'ٱ',
  'ﭒ' => 'ٻ',
  'ﭓ' => 'ٻ',
  'ﭔ' => 'ٻ',
  'ﭕ' => 'ٻ',
  'ﭖ' => 'پ',
  'ﭗ' => 'پ',
  'ﭘ' => 'پ',
  'ﭙ' => 'پ',
  'ﭚ' => 'ڀ',
  'ﭛ' => 'ڀ',
  'ﭜ' => 'ڀ',
  'ﭝ' => 'ڀ',
  'ﭞ' => 'ٺ',
  'ﭟ' => 'ٺ',
  'ﭠ' => 'ٺ',
  'ﭡ' => 'ٺ',
  'ﭢ' => 'ٿ',
  'ﭣ' => 'ٿ',
  'ﭤ' => 'ٿ',
  'ﭥ' => 'ٿ',
  'ﭦ' => 'ٹ',
  'ﭧ' => 'ٹ',
  'ﭨ' => 'ٹ',
  'ﭩ' => 'ٹ',
  'ﭪ' => 'ڤ',
  'ﭫ' => 'ڤ',
  'ﭬ' => 'ڤ',
  'ﭭ' => 'ڤ',
  'ﭮ' => 'ڦ',
  'ﭯ' => 'ڦ',
  'ﭰ' => 'ڦ',
  'ﭱ' => 'ڦ',
  'ﭲ' => 'ڄ',
  'ﭳ' => 'ڄ',
  'ﭴ' => 'ڄ',
  'ﭵ' => 'ڄ',
  'ﭶ' => 'ڃ',
  'ﭷ' => 'ڃ',
  'ﭸ' => 'ڃ',
  'ﭹ' => 'ڃ',
  'ﭺ' => 'چ',
  'ﭻ' => 'چ',
  'ﭼ' => 'چ',
  'ﭽ' => 'چ',
  'ﭾ' => 'ڇ',
  'ﭿ' => 'ڇ',
  'ﮀ' => 'ڇ',
  'ﮁ' => 'ڇ',
  'ﮂ' => 'ڍ',
  'ﮃ' => 'ڍ',
  'ﮄ' => 'ڌ',
  'ﮅ' => 'ڌ',
  'ﮆ' => 'ڎ',
  'ﮇ' => 'ڎ',
  'ﮈ' => 'ڈ',
  'ﮉ' => 'ڈ',
  'ﮊ' => 'ژ',
  'ﮋ' => 'ژ',
  'ﮌ' => 'ڑ',
  'ﮍ' => 'ڑ',
  'ﮎ' => 'ک',
  'ﮏ' => 'ک',
  'ﮐ' => 'ک',
  'ﮑ' => 'ک',
  'ﮒ' => 'گ',
  'ﮓ' => 'گ',
  'ﮔ' => 'گ',
  'ﮕ' => 'گ',
  'ﮖ' => 'ڳ',
  'ﮗ' => 'ڳ',
  'ﮘ' => 'ڳ',
  'ﮙ' => 'ڳ',
  'ﮚ' => 'ڱ',
  'ﮛ' => 'ڱ',
  'ﮜ' => 'ڱ',
  'ﮝ' => 'ڱ',
  'ﮞ' => 'ں',
  'ﮟ' => 'ں',
  'ﮠ' => 'ڻ',
  'ﮡ' => 'ڻ',
  'ﮢ' => 'ڻ',
  'ﮣ' => 'ڻ',
  'ﮤ' => 'ۀ',
  'ﮥ' => 'ۀ',
  'ﮦ' => 'ہ',
  'ﮧ' => 'ہ',
  'ﮨ' => 'ہ',
  'ﮩ' => 'ہ',
  'ﮪ' => 'ھ',
  'ﮫ' => 'ھ',
  'ﮬ' => 'ھ',
  'ﮭ' => 'ھ',
  'ﮮ' => 'ے',
  'ﮯ' => 'ے',
  'ﮰ' => 'ۓ',
  'ﮱ' => 'ۓ',
  'ﯓ' => 'ڭ',
  'ﯔ' => 'ڭ',
  'ﯕ' => 'ڭ',
  'ﯖ' => 'ڭ',
  'ﯗ' => 'ۇ',
  'ﯘ' => 'ۇ',
  'ﯙ' => 'ۆ',
  'ﯚ' => 'ۆ',
  'ﯛ' => 'ۈ',
  'ﯜ' => 'ۈ',
  'ﯝ' => 'ۇٴ',
  'ﯞ' => 'ۋ',
  'ﯟ' => 'ۋ',
  'ﯠ' => 'ۅ',
  'ﯡ' => 'ۅ',
  'ﯢ' => 'ۉ',
  'ﯣ' => 'ۉ',
  'ﯤ' => 'ې',
  'ﯥ' => 'ې',
  'ﯦ' => 'ې',
  'ﯧ' => 'ې',
  'ﯨ' => 'ى',
  'ﯩ' => 'ى',
  'ﯪ' => 'ئا',
  'ﯫ' => 'ئا',
  'ﯬ' => 'ئە',
  'ﯭ' => 'ئە',
  'ﯮ' => 'ئو',
  'ﯯ' => 'ئو',
  'ﯰ' => 'ئۇ',
  'ﯱ' => 'ئۇ',
  'ﯲ' => 'ئۆ',
  'ﯳ' => 'ئۆ',
  'ﯴ' => 'ئۈ',
  'ﯵ' => 'ئۈ',
  'ﯶ' => 'ئې',
  'ﯷ' => 'ئې',
  'ﯸ' => 'ئې',
  'ﯹ' => 'ئى',
  'ﯺ' => 'ئى',
  'ﯻ' => 'ئى',
  'ﯼ' => 'ی',
  'ﯽ' => 'ی',
  'ﯾ' => 'ی',
  'ﯿ' => 'ی',
  'ﰀ' => 'ئج',
  'ﰁ' => 'ئح',
  'ﰂ' => 'ئم',
  'ﰃ' => 'ئى',
  'ﰄ' => 'ئي',
  'ﰅ' => 'بج',
  'ﰆ' => 'بح',
  'ﰇ' => 'بخ',
  'ﰈ' => 'بم',
  'ﰉ' => 'بى',
  'ﰊ' => 'بي',
  'ﰋ' => 'تج',
  'ﰌ' => 'تح',
  'ﰍ' => 'تخ',
  'ﰎ' => 'تم',
  'ﰏ' => 'تى',
  'ﰐ' => 'تي',
  'ﰑ' => 'ثج',
  'ﰒ' => 'ثم',
  'ﰓ' => 'ثى',
  'ﰔ' => 'ثي',
  'ﰕ' => 'جح',
  'ﰖ' => 'جم',
  'ﰗ' => 'حج',
  'ﰘ' => 'حم',
  'ﰙ' => 'خج',
  'ﰚ' => 'خح',
  'ﰛ' => 'خم',
  'ﰜ' => 'سج',
  'ﰝ' => 'سح',
  'ﰞ' => 'سخ',
  'ﰟ' => 'سم',
  'ﰠ' => 'صح',
  'ﰡ' => 'صم',
  'ﰢ' => 'ضج',
  'ﰣ' => 'ضح',
  'ﰤ' => 'ضخ',
  'ﰥ' => 'ضم',
  'ﰦ' => 'طح',
  'ﰧ' => 'طم',
  'ﰨ' => 'ظم',
  'ﰩ' => 'عج',
  'ﰪ' => 'عم',
  'ﰫ' => 'غج',
  'ﰬ' => 'غم',
  'ﰭ' => 'فج',
  'ﰮ' => 'فح',
  'ﰯ' => 'فخ',
  'ﰰ' => 'فم',
  'ﰱ' => 'فى',
  'ﰲ' => 'في',
  'ﰳ' => 'قح',
  'ﰴ' => 'قم',
  'ﰵ' => 'قى',
  'ﰶ' => 'قي',
  'ﰷ' => 'كا',
  'ﰸ' => 'كج',
  'ﰹ' => 'كح',
  'ﰺ' => 'كخ',
  'ﰻ' => 'كل',
  'ﰼ' => 'كم',
  'ﰽ' => 'كى',
  'ﰾ' => 'كي',
  'ﰿ' => 'لج',
  'ﱀ' => 'لح',
  'ﱁ' => 'لخ',
  'ﱂ' => 'لم',
  'ﱃ' => 'لى',
  'ﱄ' => 'لي',
  'ﱅ' => 'مج',
  'ﱆ' => 'مح',
  'ﱇ' => 'مخ',
  'ﱈ' => 'مم',
  'ﱉ' => 'مى',
  'ﱊ' => 'مي',
  'ﱋ' => 'نج',
  'ﱌ' => 'نح',
  'ﱍ' => 'نخ',
  'ﱎ' => 'نم',
  'ﱏ' => 'نى',
  'ﱐ' => 'ني',
  'ﱑ' => 'هج',
  'ﱒ' => 'هم',
  'ﱓ' => 'هى',
  'ﱔ' => 'هي',
  'ﱕ' => 'يج',
  'ﱖ' => 'يح',
  'ﱗ' => 'يخ',
  'ﱘ' => 'يم',
  'ﱙ' => 'يى',
  'ﱚ' => 'يي',
  'ﱛ' => 'ذٰ',
  'ﱜ' => 'رٰ',
  'ﱝ' => 'ىٰ',
  'ﱞ' => ' ٌّ',
  'ﱟ' => ' ٍّ',
  'ﱠ' => ' َّ',
  'ﱡ' => ' ُّ',
  'ﱢ' => ' ِّ',
  'ﱣ' => ' ّٰ',
  'ﱤ' => 'ئر',
  'ﱥ' => 'ئز',
  'ﱦ' => 'ئم',
  'ﱧ' => 'ئن',
  'ﱨ' => 'ئى',
  'ﱩ' => 'ئي',
  'ﱪ' => 'بر',
  'ﱫ' => 'بز',
  'ﱬ' => 'بم',
  'ﱭ' => 'بن',
  'ﱮ' => 'بى',
  'ﱯ' => 'بي',
  'ﱰ' => 'تر',
  'ﱱ' => 'تز',
  'ﱲ' => 'تم',
  'ﱳ' => 'تن',
  'ﱴ' => 'تى',
  'ﱵ' => 'تي',
  'ﱶ' => 'ثر',
  'ﱷ' => 'ثز',
  'ﱸ' => 'ثم',
  'ﱹ' => 'ثن',
  'ﱺ' => 'ثى',
  'ﱻ' => 'ثي',
  'ﱼ' => 'فى',
  'ﱽ' => 'في',
  'ﱾ' => 'قى',
  'ﱿ' => 'قي',
  'ﲀ' => 'كا',
  'ﲁ' => 'كل',
  'ﲂ' => 'كم',
  'ﲃ' => 'كى',
  'ﲄ' => 'كي',
  'ﲅ' => 'لم',
  'ﲆ' => 'لى',
  'ﲇ' => 'لي',
  'ﲈ' => 'ما',
  'ﲉ' => 'مم',
  'ﲊ' => 'نر',
  'ﲋ' => 'نز',
  'ﲌ' => 'نم',
  'ﲍ' => 'نن',
  'ﲎ' => 'نى',
  'ﲏ' => 'ني',
  'ﲐ' => 'ىٰ',
  'ﲑ' => 'ير',
  'ﲒ' => 'يز',
  'ﲓ' => 'يم',
  'ﲔ' => 'ين',
  'ﲕ' => 'يى',
  'ﲖ' => 'يي',
  'ﲗ' => 'ئج',
  'ﲘ' => 'ئح',
  'ﲙ' => 'ئخ',
  'ﲚ' => 'ئم',
  'ﲛ' => 'ئه',
  'ﲜ' => 'بج',
  'ﲝ' => 'بح',
  'ﲞ' => 'بخ',
  'ﲟ' => 'بم',
  'ﲠ' => 'به',
  'ﲡ' => 'تج',
  'ﲢ' => 'تح',
  'ﲣ' => 'تخ',
  'ﲤ' => 'تم',
  'ﲥ' => 'ته',
  'ﲦ' => 'ثم',
  'ﲧ' => 'جح',
  'ﲨ' => 'جم',
  'ﲩ' => 'حج',
  'ﲪ' => 'حم',
  'ﲫ' => 'خج',
  'ﲬ' => 'خم',
  'ﲭ' => 'سج',
  'ﲮ' => 'سح',
  'ﲯ' => 'سخ',
  'ﲰ' => 'سم',
  'ﲱ' => 'صح',
  'ﲲ' => 'صخ',
  'ﲳ' => 'صم',
  'ﲴ' => 'ضج',
  'ﲵ' => 'ضح',
  'ﲶ' => 'ضخ',
  'ﲷ' => 'ضم',
  'ﲸ' => 'طح',
  'ﲹ' => 'ظم',
  'ﲺ' => 'عج',
  'ﲻ' => 'عم',
  'ﲼ' => 'غج',
  'ﲽ' => 'غم',
  'ﲾ' => 'فج',
  'ﲿ' => 'فح',
  'ﳀ' => 'فخ',
  'ﳁ' => 'فم',
  'ﳂ' => 'قح',
  'ﳃ' => 'قم',
  'ﳄ' => 'كج',
  'ﳅ' => 'كح',
  'ﳆ' => 'كخ',
  'ﳇ' => 'كل',
  'ﳈ' => 'كم',
  'ﳉ' => 'لج',
  'ﳊ' => 'لح',
  'ﳋ' => 'لخ',
  'ﳌ' => 'لم',
  'ﳍ' => 'له',
  'ﳎ' => 'مج',
  'ﳏ' => 'مح',
  'ﳐ' => 'مخ',
  'ﳑ' => 'مم',
  'ﳒ' => 'نج',
  'ﳓ' => 'نح',
  'ﳔ' => 'نخ',
  'ﳕ' => 'نم',
  'ﳖ' => 'نه',
  'ﳗ' => 'هج',
  'ﳘ' => 'هم',
  'ﳙ' => 'هٰ',
  'ﳚ' => 'يج',
  'ﳛ' => 'يح',
  'ﳜ' => 'يخ',
  'ﳝ' => 'يم',
  'ﳞ' => 'يه',
  'ﳟ' => 'ئم',
  'ﳠ' => 'ئه',
  'ﳡ' => 'بم',
  'ﳢ' => 'به',
  'ﳣ' => 'تم',
  'ﳤ' => 'ته',
  'ﳥ' => 'ثم',
  'ﳦ' => 'ثه',
  'ﳧ' => 'سم',
  'ﳨ' => 'سه',
  'ﳩ' => 'شم',
  'ﳪ' => 'شه',
  'ﳫ' => 'كل',
  'ﳬ' => 'كم',
  'ﳭ' => 'لم',
  'ﳮ' => 'نم',
  'ﳯ' => 'نه',
  'ﳰ' => 'يم',
  'ﳱ' => 'يه',
  'ﳲ' => 'ـَّ',
  'ﳳ' => 'ـُّ',
  'ﳴ' => 'ـِّ',
  'ﳵ' => 'طى',
  'ﳶ' => 'طي',
  'ﳷ' => 'عى',
  'ﳸ' => 'عي',
  'ﳹ' => 'غى',
  'ﳺ' => 'غي',
  'ﳻ' => 'سى',
  'ﳼ' => 'سي',
  'ﳽ' => 'شى',
  'ﳾ' => 'شي',
  'ﳿ' => 'حى',
  'ﴀ' => 'حي',
  'ﴁ' => 'جى',
  'ﴂ' => 'جي',
  'ﴃ' => 'خى',
  'ﴄ' => 'خي',
  'ﴅ' => 'صى',
  'ﴆ' => 'صي',
  'ﴇ' => 'ضى',
  'ﴈ' => 'ضي',
  'ﴉ' => 'شج',
  'ﴊ' => 'شح',
  'ﴋ' => 'شخ',
  'ﴌ' => 'شم',
  'ﴍ' => 'شر',
  'ﴎ' => 'سر',
  'ﴏ' => 'صر',
  'ﴐ' => 'ضر',
  'ﴑ' => 'طى',
  'ﴒ' => 'طي',
  'ﴓ' => 'عى',
  'ﴔ' => 'عي',
  'ﴕ' => 'غى',
  'ﴖ' => 'غي',
  'ﴗ' => 'سى',
  'ﴘ' => 'سي',
  'ﴙ' => 'شى',
  'ﴚ' => 'شي',
  'ﴛ' => 'حى',
  'ﴜ' => 'حي',
  'ﴝ' => 'جى',
  'ﴞ' => 'جي',
  'ﴟ' => 'خى',
  'ﴠ' => 'خي',
  'ﴡ' => 'صى',
  'ﴢ' => 'صي',
  'ﴣ' => 'ضى',
  'ﴤ' => 'ضي',
  'ﴥ' => 'شج',
  'ﴦ' => 'شح',
  'ﴧ' => 'شخ',
  'ﴨ' => 'شم',
  'ﴩ' => 'شر',
  'ﴪ' => 'سر',
  'ﴫ' => 'صر',
  'ﴬ' => 'ضر',
  'ﴭ' => 'شج',
  'ﴮ' => 'شح',
  'ﴯ' => 'شخ',
  'ﴰ' => 'شم',
  'ﴱ' => 'سه',
  'ﴲ' => 'شه',
  'ﴳ' => 'طم',
  'ﴴ' => 'سج',
  'ﴵ' => 'سح',
  'ﴶ' => 'سخ',
  'ﴷ' => 'شج',
  'ﴸ' => 'شح',
  'ﴹ' => 'شخ',
  'ﴺ' => 'طم',
  'ﴻ' => 'ظم',
  'ﴼ' => 'اً',
  'ﴽ' => 'اً',
  'ﵐ' => 'تجم',
  'ﵑ' => 'تحج',
  'ﵒ' => 'تحج',
  'ﵓ' => 'تحم',
  'ﵔ' => 'تخم',
  'ﵕ' => 'تمج',
  'ﵖ' => 'تمح',
  'ﵗ' => 'تمخ',
  'ﵘ' => 'جمح',
  'ﵙ' => 'جمح',
  'ﵚ' => 'حمي',
  'ﵛ' => 'حمى',
  'ﵜ' => 'سحج',
  'ﵝ' => 'سجح',
  'ﵞ' => 'سجى',
  'ﵟ' => 'سمح',
  'ﵠ' => 'سمح',
  'ﵡ' => 'سمج',
  'ﵢ' => 'سمم',
  'ﵣ' => 'سمم',
  'ﵤ' => 'صحح',
  'ﵥ' => 'صحح',
  'ﵦ' => 'صمم',
  'ﵧ' => 'شحم',
  'ﵨ' => 'شحم',
  'ﵩ' => 'شجي',
  'ﵪ' => 'شمخ',
  'ﵫ' => 'شمخ',
  'ﵬ' => 'شمم',
  'ﵭ' => 'شمم',
  'ﵮ' => 'ضحى',
  'ﵯ' => 'ضخم',
  'ﵰ' => 'ضخم',
  'ﵱ' => 'طمح',
  'ﵲ' => 'طمح',
  'ﵳ' => 'طمم',
  'ﵴ' => 'طمي',
  'ﵵ' => 'عجم',
  'ﵶ' => 'عمم',
  'ﵷ' => 'عمم',
  'ﵸ' => 'عمى',
  'ﵹ' => 'غمم',
  'ﵺ' => 'غمي',
  'ﵻ' => 'غمى',
  'ﵼ' => 'فخم',
  'ﵽ' => 'فخم',
  'ﵾ' => 'قمح',
  'ﵿ' => 'قمم',
  'ﶀ' => 'لحم',
  'ﶁ' => 'لحي',
  'ﶂ' => 'لحى',
  'ﶃ' => 'لجج',
  'ﶄ' => 'لجج',
  'ﶅ' => 'لخم',
  'ﶆ' => 'لخم',
  'ﶇ' => 'لمح',
  'ﶈ' => 'لمح',
  'ﶉ' => 'محج',
  'ﶊ' => 'محم',
  'ﶋ' => 'محي',
  'ﶌ' => 'مجح',
  'ﶍ' => 'مجم',
  'ﶎ' => 'مخج',
  'ﶏ' => 'مخم',
  'ﶒ' => 'مجخ',
  'ﶓ' => 'همج',
  'ﶔ' => 'همم',
  'ﶕ' => 'نحم',
  'ﶖ' => 'نحى',
  'ﶗ' => 'نجم',
  'ﶘ' => 'نجم',
  'ﶙ' => 'نجى',
  'ﶚ' => 'نمي',
  'ﶛ' => 'نمى',
  'ﶜ' => 'يمم',
  'ﶝ' => 'يمم',
  'ﶞ' => 'بخي',
  'ﶟ' => 'تجي',
  'ﶠ' => 'تجى',
  'ﶡ' => 'تخي',
  'ﶢ' => 'تخى',
  'ﶣ' => 'تمي',
  'ﶤ' => 'تمى',
  'ﶥ' => 'جمي',
  'ﶦ' => 'جحى',
  'ﶧ' => 'جمى',
  'ﶨ' => 'سخى',
  'ﶩ' => 'صحي',
  'ﶪ' => 'شحي',
  'ﶫ' => 'ضحي',
  'ﶬ' => 'لجي',
  'ﶭ' => 'لمي',
  'ﶮ' => 'يحي',
  'ﶯ' => 'يجي',
  'ﶰ' => 'يمي',
  'ﶱ' => 'ممي',
  'ﶲ' => 'قمي',
  'ﶳ' => 'نحي',
  'ﶴ' => 'قمح',
  'ﶵ' => 'لحم',
  'ﶶ' => 'عمي',
  'ﶷ' => 'كمي',
  'ﶸ' => 'نجح',
  'ﶹ' => 'مخي',
  'ﶺ' => 'لجم',
  'ﶻ' => 'كمم',
  'ﶼ' => 'لجم',
  'ﶽ' => 'نجح',
  'ﶾ' => 'جحي',
  'ﶿ' => 'حجي',
  'ﷀ' => 'مجي',
  'ﷁ' => 'فمي',
  'ﷂ' => 'بحي',
  'ﷃ' => 'كمم',
  'ﷄ' => 'عجم',
  'ﷅ' => 'صمم',
  'ﷆ' => 'سخي',
  'ﷇ' => 'نجي',
  'ﷰ' => 'صلے',
  'ﷱ' => 'قلے',
  'ﷲ' => 'الله',
  'ﷳ' => 'اكبر',
  'ﷴ' => 'محمد',
  'ﷵ' => 'صلعم',
  'ﷶ' => 'رسول',
  'ﷷ' => 'عليه',
  'ﷸ' => 'وسلم',
  'ﷹ' => 'صلى',
  'ﷺ' => 'صلى الله عليه وسلم',
  'ﷻ' => 'جل جلاله',
  '﷼' => 'ریال',
  '︐' => ',',
  '︑' => '、',
  '︒' => '。',
  '︓' => ':',
  '︔' => ';',
  '︕' => '!',
  '︖' => '?',
  '︗' => '〖',
  '︘' => '〗',
  '︙' => '...',
  '︰' => '..',
  '︱' => '—',
  '︲' => '–',
  '︳' => '_',
  '︴' => '_',
  '︵' => '(',
  '︶' => ')',
  '︷' => '{',
  '︸' => '}',
  '︹' => '〔',
  '︺' => '〕',
  '︻' => '【',
  '︼' => '】',
  '︽' => '《',
  '︾' => '》',
  '︿' => '〈',
  '﹀' => '〉',
  '﹁' => '「',
  '﹂' => '」',
  '﹃' => '『',
  '﹄' => '』',
  '﹇' => '[',
  '﹈' => ']',
  '﹉' => ' ̅',
  '﹊' => ' ̅',
  '﹋' => ' ̅',
  '﹌' => ' ̅',
  '﹍' => '_',
  '﹎' => '_',
  '﹏' => '_',
  '﹐' => ',',
  '﹑' => '、',
  '﹒' => '.',
  '﹔' => ';',
  '﹕' => ':',
  '﹖' => '?',
  '﹗' => '!',
  '﹘' => '—',
  '﹙' => '(',
  '﹚' => ')',
  '﹛' => '{',
  '﹜' => '}',
  '﹝' => '〔',
  '﹞' => '〕',
  '﹟' => '#',
  '﹠' => '&',
  '﹡' => '*',
  '﹢' => '+',
  '﹣' => '-',
  '﹤' => '<',
  '﹥' => '>',
  '﹦' => '=',
  '﹨' => '\\',
  '﹩' => '$',
  '﹪' => '%',
  '﹫' => '@',
  'ﹰ' => ' ً',
  'ﹱ' => 'ـً',
  'ﹲ' => ' ٌ',
  'ﹴ' => ' ٍ',
  'ﹶ' => ' َ',
  'ﹷ' => 'ـَ',
  'ﹸ' => ' ُ',
  'ﹹ' => 'ـُ',
  'ﹺ' => ' ِ',
  'ﹻ' => 'ـِ',
  'ﹼ' => ' ّ',
  'ﹽ' => 'ـّ',
  'ﹾ' => ' ْ',
  'ﹿ' => 'ـْ',
  'ﺀ' => 'ء',
  'ﺁ' => 'آ',
  'ﺂ' => 'آ',
  'ﺃ' => 'أ',
  'ﺄ' => 'أ',
  'ﺅ' => 'ؤ',
  'ﺆ' => 'ؤ',
  'ﺇ' => 'إ',
  'ﺈ' => 'إ',
  'ﺉ' => 'ئ',
  'ﺊ' => 'ئ',
  'ﺋ' => 'ئ',
  'ﺌ' => 'ئ',
  'ﺍ' => 'ا',
  'ﺎ' => 'ا',
  'ﺏ' => 'ب',
  'ﺐ' => 'ب',
  'ﺑ' => 'ب',
  'ﺒ' => 'ب',
  'ﺓ' => 'ة',
  'ﺔ' => 'ة',
  'ﺕ' => 'ت',
  'ﺖ' => 'ت',
  'ﺗ' => 'ت',
  'ﺘ' => 'ت',
  'ﺙ' => 'ث',
  'ﺚ' => 'ث',
  'ﺛ' => 'ث',
  'ﺜ' => 'ث',
  'ﺝ' => 'ج',
  'ﺞ' => 'ج',
  'ﺟ' => 'ج',
  'ﺠ' => 'ج',
  'ﺡ' => 'ح',
  'ﺢ' => 'ح',
  'ﺣ' => 'ح',
  'ﺤ' => 'ح',
  'ﺥ' => 'خ',
  'ﺦ' => 'خ',
  'ﺧ' => 'خ',
  'ﺨ' => 'خ',
  'ﺩ' => 'د',
  'ﺪ' => 'د',
  'ﺫ' => 'ذ',
  'ﺬ' => 'ذ',
  'ﺭ' => 'ر',
  'ﺮ' => 'ر',
  'ﺯ' => 'ز',
  'ﺰ' => 'ز',
  'ﺱ' => 'س',
  'ﺲ' => 'س',
  'ﺳ' => 'س',
  'ﺴ' => 'س',
  'ﺵ' => 'ش',
  'ﺶ' => 'ش',
  'ﺷ' => 'ش',
  'ﺸ' => 'ش',
  'ﺹ' => 'ص',
  'ﺺ' => 'ص',
  'ﺻ' => 'ص',
  'ﺼ' => 'ص',
  'ﺽ' => 'ض',
  'ﺾ' => 'ض',
  'ﺿ' => 'ض',
  'ﻀ' => 'ض',
  'ﻁ' => 'ط',
  'ﻂ' => 'ط',
  'ﻃ' => 'ط',
  'ﻄ' => 'ط',
  'ﻅ' => 'ظ',
  'ﻆ' => 'ظ',
  'ﻇ' => 'ظ',
  'ﻈ' => 'ظ',
  'ﻉ' => 'ع',
  'ﻊ' => 'ع',
  'ﻋ' => 'ع',
  'ﻌ' => 'ع',
  'ﻍ' => 'غ',
  'ﻎ' => 'غ',
  'ﻏ' => 'غ',
  'ﻐ' => 'غ',
  'ﻑ' => 'ف',
  'ﻒ' => 'ف',
  'ﻓ' => 'ف',
  'ﻔ' => 'ف',
  'ﻕ' => 'ق',
  'ﻖ' => 'ق',
  'ﻗ' => 'ق',
  'ﻘ' => 'ق',
  'ﻙ' => 'ك',
  'ﻚ' => 'ك',
  'ﻛ' => 'ك',
  'ﻜ' => 'ك',
  'ﻝ' => 'ل',
  'ﻞ' => 'ل',
  'ﻟ' => 'ل',
  'ﻠ' => 'ل',
  'ﻡ' => 'م',
  'ﻢ' => 'م',
  'ﻣ' => 'م',
  'ﻤ' => 'م',
  'ﻥ' => 'ن',
  'ﻦ' => 'ن',
  'ﻧ' => 'ن',
  'ﻨ' => 'ن',
  'ﻩ' => 'ه',
  'ﻪ' => 'ه',
  'ﻫ' => 'ه',
  'ﻬ' => 'ه',
  'ﻭ' => 'و',
  'ﻮ' => 'و',
  'ﻯ' => 'ى',
  'ﻰ' => 'ى',
  'ﻱ' => 'ي',
  'ﻲ' => 'ي',
  'ﻳ' => 'ي',
  'ﻴ' => 'ي',
  'ﻵ' => 'لآ',
  'ﻶ' => 'لآ',
  'ﻷ' => 'لأ',
  'ﻸ' => 'لأ',
  'ﻹ' => 'لإ',
  'ﻺ' => 'لإ',
  'ﻻ' => 'لا',
  'ﻼ' => 'لا',
  '!' => '!',
  '"' => '"',
  '#' => '#',
  '$' => '$',
  '%' => '%',
  '&' => '&',
  ''' => '\'',
  '(' => '(',
  ')' => ')',
  '*' => '*',
  '+' => '+',
  ',' => ',',
  '-' => '-',
  '.' => '.',
  '/' => '/',
  '0' => '0',
  '1' => '1',
  '2' => '2',
  '3' => '3',
  '4' => '4',
  '5' => '5',
  '6' => '6',
  '7' => '7',
  '8' => '8',
  '9' => '9',
  ':' => ':',
  ';' => ';',
  '<' => '<',
  '=' => '=',
  '>' => '>',
  '?' => '?',
  '@' => '@',
  'A' => 'A',
  'B' => 'B',
  'C' => 'C',
  'D' => 'D',
  'E' => 'E',
  'F' => 'F',
  'G' => 'G',
  'H' => 'H',
  'I' => 'I',
  'J' => 'J',
  'K' => 'K',
  'L' => 'L',
  'M' => 'M',
  'N' => 'N',
  'O' => 'O',
  'P' => 'P',
  'Q' => 'Q',
  'R' => 'R',
  'S' => 'S',
  'T' => 'T',
  'U' => 'U',
  'V' => 'V',
  'W' => 'W',
  'X' => 'X',
  'Y' => 'Y',
  'Z' => 'Z',
  '[' => '[',
  '\' => '\\',
  ']' => ']',
  '^' => '^',
  '_' => '_',
  '`' => '`',
  'a' => 'a',
  'b' => 'b',
  'c' => 'c',
  'd' => 'd',
  'e' => 'e',
  'f' => 'f',
  'g' => 'g',
  'h' => 'h',
  'i' => 'i',
  'j' => 'j',
  'k' => 'k',
  'l' => 'l',
  'm' => 'm',
  'n' => 'n',
  'o' => 'o',
  'p' => 'p',
  'q' => 'q',
  'r' => 'r',
  's' => 's',
  't' => 't',
  'u' => 'u',
  'v' => 'v',
  'w' => 'w',
  'x' => 'x',
  'y' => 'y',
  'z' => 'z',
  '{' => '{',
  '|' => '|',
  '}' => '}',
  '~' => '~',
  '⦅' => '⦅',
  '⦆' => '⦆',
  '。' => '。',
  '「' => '「',
  '」' => '」',
  '、' => '、',
  '・' => '・',
  'ヲ' => 'ヲ',
  'ァ' => 'ァ',
  'ィ' => 'ィ',
  'ゥ' => 'ゥ',
  'ェ' => 'ェ',
  'ォ' => 'ォ',
  'ャ' => 'ャ',
  'ュ' => 'ュ',
  'ョ' => 'ョ',
  'ッ' => 'ッ',
  'ー' => 'ー',
  'ア' => 'ア',
  'イ' => 'イ',
  'ウ' => 'ウ',
  'エ' => 'エ',
  'オ' => 'オ',
  'カ' => 'カ',
  'キ' => 'キ',
  'ク' => 'ク',
  'ケ' => 'ケ',
  'コ' => 'コ',
  'サ' => 'サ',
  'シ' => 'シ',
  'ス' => 'ス',
  'セ' => 'セ',
  'ソ' => 'ソ',
  'タ' => 'タ',
  'チ' => 'チ',
  'ツ' => 'ツ',
  'テ' => 'テ',
  'ト' => 'ト',
  'ナ' => 'ナ',
  'ニ' => 'ニ',
  'ヌ' => 'ヌ',
  'ネ' => 'ネ',
  'ノ' => 'ノ',
  'ハ' => 'ハ',
  'ヒ' => 'ヒ',
  'フ' => 'フ',
  'ヘ' => 'ヘ',
  'ホ' => 'ホ',
  'マ' => 'マ',
  'ミ' => 'ミ',
  'ム' => 'ム',
  'メ' => 'メ',
  'モ' => 'モ',
  'ヤ' => 'ヤ',
  'ユ' => 'ユ',
  'ヨ' => 'ヨ',
  'ラ' => 'ラ',
  'リ' => 'リ',
  'ル' => 'ル',
  'レ' => 'レ',
  'ロ' => 'ロ',
  'ワ' => 'ワ',
  'ン' => 'ン',
  '゙' => '゙',
  '゚' => '゚',
  'ᅠ' => 'ᅠ',
  'ᄀ' => 'ᄀ',
  'ᄁ' => 'ᄁ',
  'ᆪ' => 'ᆪ',
  'ᄂ' => 'ᄂ',
  'ᆬ' => 'ᆬ',
  'ᆭ' => 'ᆭ',
  'ᄃ' => 'ᄃ',
  'ᄄ' => 'ᄄ',
  'ᄅ' => 'ᄅ',
  'ᆰ' => 'ᆰ',
  'ᆱ' => 'ᆱ',
  'ᆲ' => 'ᆲ',
  'ᆳ' => 'ᆳ',
  'ᆴ' => 'ᆴ',
  'ᆵ' => 'ᆵ',
  'ᄚ' => 'ᄚ',
  'ᄆ' => 'ᄆ',
  'ᄇ' => 'ᄇ',
  'ᄈ' => 'ᄈ',
  'ᄡ' => 'ᄡ',
  'ᄉ' => 'ᄉ',
  'ᄊ' => 'ᄊ',
  'ᄋ' => 'ᄋ',
  'ᄌ' => 'ᄌ',
  'ᄍ' => 'ᄍ',
  'ᄎ' => 'ᄎ',
  'ᄏ' => 'ᄏ',
  'ᄐ' => 'ᄐ',
  'ᄑ' => 'ᄑ',
  'ᄒ' => 'ᄒ',
  'ᅡ' => 'ᅡ',
  'ᅢ' => 'ᅢ',
  'ᅣ' => 'ᅣ',
  'ᅤ' => 'ᅤ',
  'ᅥ' => 'ᅥ',
  'ᅦ' => 'ᅦ',
  'ᅧ' => 'ᅧ',
  'ᅨ' => 'ᅨ',
  'ᅩ' => 'ᅩ',
  'ᅪ' => 'ᅪ',
  'ᅫ' => 'ᅫ',
  'ᅬ' => 'ᅬ',
  'ᅭ' => 'ᅭ',
  'ᅮ' => 'ᅮ',
  'ᅯ' => 'ᅯ',
  'ᅰ' => 'ᅰ',
  'ᅱ' => 'ᅱ',
  'ᅲ' => 'ᅲ',
  'ᅳ' => 'ᅳ',
  'ᅴ' => 'ᅴ',
  'ᅵ' => 'ᅵ',
  '¢' => '¢',
  '£' => '£',
  '¬' => '¬',
  ' ̄' => ' ̄',
  '¦' => '¦',
  '¥' => '¥',
  '₩' => '₩',
  '│' => '│',
  '←' => '←',
  '↑' => '↑',
  '→' => '→',
  '↓' => '↓',
  '■' => '■',
  '○' => '○',
  '𝐀' => 'A',
  '𝐁' => 'B',
  '𝐂' => 'C',
  '𝐃' => 'D',
  '𝐄' => 'E',
  '𝐅' => 'F',
  '𝐆' => 'G',
  '𝐇' => 'H',
  '𝐈' => 'I',
  '𝐉' => 'J',
  '𝐊' => 'K',
  '𝐋' => 'L',
  '𝐌' => 'M',
  '𝐍' => 'N',
  '𝐎' => 'O',
  '𝐏' => 'P',
  '𝐐' => 'Q',
  '𝐑' => 'R',
  '𝐒' => 'S',
  '𝐓' => 'T',
  '𝐔' => 'U',
  '𝐕' => 'V',
  '𝐖' => 'W',
  '𝐗' => 'X',
  '𝐘' => 'Y',
  '𝐙' => 'Z',
  '𝐚' => 'a',
  '𝐛' => 'b',
  '𝐜' => 'c',
  '𝐝' => 'd',
  '𝐞' => 'e',
  '𝐟' => 'f',
  '𝐠' => 'g',
  '𝐡' => 'h',
  '𝐢' => 'i',
  '𝐣' => 'j',
  '𝐤' => 'k',
  '𝐥' => 'l',
  '𝐦' => 'm',
  '𝐧' => 'n',
  '𝐨' => 'o',
  '𝐩' => 'p',
  '𝐪' => 'q',
  '𝐫' => 'r',
  '𝐬' => 's',
  '𝐭' => 't',
  '𝐮' => 'u',
  '𝐯' => 'v',
  '𝐰' => 'w',
  '𝐱' => 'x',
  '𝐲' => 'y',
  '𝐳' => 'z',
  '𝐴' => 'A',
  '𝐵' => 'B',
  '𝐶' => 'C',
  '𝐷' => 'D',
  '𝐸' => 'E',
  '𝐹' => 'F',
  '𝐺' => 'G',
  '𝐻' => 'H',
  '𝐼' => 'I',
  '𝐽' => 'J',
  '𝐾' => 'K',
  '𝐿' => 'L',
  '𝑀' => 'M',
  '𝑁' => 'N',
  '𝑂' => 'O',
  '𝑃' => 'P',
  '𝑄' => 'Q',
  '𝑅' => 'R',
  '𝑆' => 'S',
  '𝑇' => 'T',
  '𝑈' => 'U',
  '𝑉' => 'V',
  '𝑊' => 'W',
  '𝑋' => 'X',
  '𝑌' => 'Y',
  '𝑍' => 'Z',
  '𝑎' => 'a',
  '𝑏' => 'b',
  '𝑐' => 'c',
  '𝑑' => 'd',
  '𝑒' => 'e',
  '𝑓' => 'f',
  '𝑔' => 'g',
  '𝑖' => 'i',
  '𝑗' => 'j',
  '𝑘' => 'k',
  '𝑙' => 'l',
  '𝑚' => 'm',
  '𝑛' => 'n',
  '𝑜' => 'o',
  '𝑝' => 'p',
  '𝑞' => 'q',
  '𝑟' => 'r',
  '𝑠' => 's',
  '𝑡' => 't',
  '𝑢' => 'u',
  '𝑣' => 'v',
  '𝑤' => 'w',
  '𝑥' => 'x',
  '𝑦' => 'y',
  '𝑧' => 'z',
  '𝑨' => 'A',
  '𝑩' => 'B',
  '𝑪' => 'C',
  '𝑫' => 'D',
  '𝑬' => 'E',
  '𝑭' => 'F',
  '𝑮' => 'G',
  '𝑯' => 'H',
  '𝑰' => 'I',
  '𝑱' => 'J',
  '𝑲' => 'K',
  '𝑳' => 'L',
  '𝑴' => 'M',
  '𝑵' => 'N',
  '𝑶' => 'O',
  '𝑷' => 'P',
  '𝑸' => 'Q',
  '𝑹' => 'R',
  '𝑺' => 'S',
  '𝑻' => 'T',
  '𝑼' => 'U',
  '𝑽' => 'V',
  '𝑾' => 'W',
  '𝑿' => 'X',
  '𝒀' => 'Y',
  '𝒁' => 'Z',
  '𝒂' => 'a',
  '𝒃' => 'b',
  '𝒄' => 'c',
  '𝒅' => 'd',
  '𝒆' => 'e',
  '𝒇' => 'f',
  '𝒈' => 'g',
  '𝒉' => 'h',
  '𝒊' => 'i',
  '𝒋' => 'j',
  '𝒌' => 'k',
  '𝒍' => 'l',
  '𝒎' => 'm',
  '𝒏' => 'n',
  '𝒐' => 'o',
  '𝒑' => 'p',
  '𝒒' => 'q',
  '𝒓' => 'r',
  '𝒔' => 's',
  '𝒕' => 't',
  '𝒖' => 'u',
  '𝒗' => 'v',
  '𝒘' => 'w',
  '𝒙' => 'x',
  '𝒚' => 'y',
  '𝒛' => 'z',
  '𝒜' => 'A',
  '𝒞' => 'C',
  '𝒟' => 'D',
  '𝒢' => 'G',
  '𝒥' => 'J',
  '𝒦' => 'K',
  '𝒩' => 'N',
  '𝒪' => 'O',
  '𝒫' => 'P',
  '𝒬' => 'Q',
  '𝒮' => 'S',
  '𝒯' => 'T',
  '𝒰' => 'U',
  '𝒱' => 'V',
  '𝒲' => 'W',
  '𝒳' => 'X',
  '𝒴' => 'Y',
  '𝒵' => 'Z',
  '𝒶' => 'a',
  '𝒷' => 'b',
  '𝒸' => 'c',
  '𝒹' => 'd',
  '𝒻' => 'f',
  '𝒽' => 'h',
  '𝒾' => 'i',
  '𝒿' => 'j',
  '𝓀' => 'k',
  '𝓁' => 'l',
  '𝓂' => 'm',
  '𝓃' => 'n',
  '𝓅' => 'p',
  '𝓆' => 'q',
  '𝓇' => 'r',
  '𝓈' => 's',
  '𝓉' => 't',
  '𝓊' => 'u',
  '𝓋' => 'v',
  '𝓌' => 'w',
  '𝓍' => 'x',
  '𝓎' => 'y',
  '𝓏' => 'z',
  '𝓐' => 'A',
  '𝓑' => 'B',
  '𝓒' => 'C',
  '𝓓' => 'D',
  '𝓔' => 'E',
  '𝓕' => 'F',
  '𝓖' => 'G',
  '𝓗' => 'H',
  '𝓘' => 'I',
  '𝓙' => 'J',
  '𝓚' => 'K',
  '𝓛' => 'L',
  '𝓜' => 'M',
  '𝓝' => 'N',
  '𝓞' => 'O',
  '𝓟' => 'P',
  '𝓠' => 'Q',
  '𝓡' => 'R',
  '𝓢' => 'S',
  '𝓣' => 'T',
  '𝓤' => 'U',
  '𝓥' => 'V',
  '𝓦' => 'W',
  '𝓧' => 'X',
  '𝓨' => 'Y',
  '𝓩' => 'Z',
  '𝓪' => 'a',
  '𝓫' => 'b',
  '𝓬' => 'c',
  '𝓭' => 'd',
  '𝓮' => 'e',
  '𝓯' => 'f',
  '𝓰' => 'g',
  '𝓱' => 'h',
  '𝓲' => 'i',
  '𝓳' => 'j',
  '𝓴' => 'k',
  '𝓵' => 'l',
  '𝓶' => 'm',
  '𝓷' => 'n',
  '𝓸' => 'o',
  '𝓹' => 'p',
  '𝓺' => 'q',
  '𝓻' => 'r',
  '𝓼' => 's',
  '𝓽' => 't',
  '𝓾' => 'u',
  '𝓿' => 'v',
  '𝔀' => 'w',
  '𝔁' => 'x',
  '𝔂' => 'y',
  '𝔃' => 'z',
  '𝔄' => 'A',
  '𝔅' => 'B',
  '𝔇' => 'D',
  '𝔈' => 'E',
  '𝔉' => 'F',
  '𝔊' => 'G',
  '𝔍' => 'J',
  '𝔎' => 'K',
  '𝔏' => 'L',
  '𝔐' => 'M',
  '𝔑' => 'N',
  '𝔒' => 'O',
  '𝔓' => 'P',
  '𝔔' => 'Q',
  '𝔖' => 'S',
  '𝔗' => 'T',
  '𝔘' => 'U',
  '𝔙' => 'V',
  '𝔚' => 'W',
  '𝔛' => 'X',
  '𝔜' => 'Y',
  '𝔞' => 'a',
  '𝔟' => 'b',
  '𝔠' => 'c',
  '𝔡' => 'd',
  '𝔢' => 'e',
  '𝔣' => 'f',
  '𝔤' => 'g',
  '𝔥' => 'h',
  '𝔦' => 'i',
  '𝔧' => 'j',
  '𝔨' => 'k',
  '𝔩' => 'l',
  '𝔪' => 'm',
  '𝔫' => 'n',
  '𝔬' => 'o',
  '𝔭' => 'p',
  '𝔮' => 'q',
  '𝔯' => 'r',
  '𝔰' => 's',
  '𝔱' => 't',
  '𝔲' => 'u',
  '𝔳' => 'v',
  '𝔴' => 'w',
  '𝔵' => 'x',
  '𝔶' => 'y',
  '𝔷' => 'z',
  '𝔸' => 'A',
  '𝔹' => 'B',
  '𝔻' => 'D',
  '𝔼' => 'E',
  '𝔽' => 'F',
  '𝔾' => 'G',
  '𝕀' => 'I',
  '𝕁' => 'J',
  '𝕂' => 'K',
  '𝕃' => 'L',
  '𝕄' => 'M',
  '𝕆' => 'O',
  '𝕊' => 'S',
  '𝕋' => 'T',
  '𝕌' => 'U',
  '𝕍' => 'V',
  '𝕎' => 'W',
  '𝕏' => 'X',
  '𝕐' => 'Y',
  '𝕒' => 'a',
  '𝕓' => 'b',
  '𝕔' => 'c',
  '𝕕' => 'd',
  '𝕖' => 'e',
  '𝕗' => 'f',
  '𝕘' => 'g',
  '𝕙' => 'h',
  '𝕚' => 'i',
  '𝕛' => 'j',
  '𝕜' => 'k',
  '𝕝' => 'l',
  '𝕞' => 'm',
  '𝕟' => 'n',
  '𝕠' => 'o',
  '𝕡' => 'p',
  '𝕢' => 'q',
  '𝕣' => 'r',
  '𝕤' => 's',
  '𝕥' => 't',
  '𝕦' => 'u',
  '𝕧' => 'v',
  '𝕨' => 'w',
  '𝕩' => 'x',
  '𝕪' => 'y',
  '𝕫' => 'z',
  '𝕬' => 'A',
  '𝕭' => 'B',
  '𝕮' => 'C',
  '𝕯' => 'D',
  '𝕰' => 'E',
  '𝕱' => 'F',
  '𝕲' => 'G',
  '𝕳' => 'H',
  '𝕴' => 'I',
  '𝕵' => 'J',
  '𝕶' => 'K',
  '𝕷' => 'L',
  '𝕸' => 'M',
  '𝕹' => 'N',
  '𝕺' => 'O',
  '𝕻' => 'P',
  '𝕼' => 'Q',
  '𝕽' => 'R',
  '𝕾' => 'S',
  '𝕿' => 'T',
  '𝖀' => 'U',
  '𝖁' => 'V',
  '𝖂' => 'W',
  '𝖃' => 'X',
  '𝖄' => 'Y',
  '𝖅' => 'Z',
  '𝖆' => 'a',
  '𝖇' => 'b',
  '𝖈' => 'c',
  '𝖉' => 'd',
  '𝖊' => 'e',
  '𝖋' => 'f',
  '𝖌' => 'g',
  '𝖍' => 'h',
  '𝖎' => 'i',
  '𝖏' => 'j',
  '𝖐' => 'k',
  '𝖑' => 'l',
  '𝖒' => 'm',
  '𝖓' => 'n',
  '𝖔' => 'o',
  '𝖕' => 'p',
  '𝖖' => 'q',
  '𝖗' => 'r',
  '𝖘' => 's',
  '𝖙' => 't',
  '𝖚' => 'u',
  '𝖛' => 'v',
  '𝖜' => 'w',
  '𝖝' => 'x',
  '𝖞' => 'y',
  '𝖟' => 'z',
  '𝖠' => 'A',
  '𝖡' => 'B',
  '𝖢' => 'C',
  '𝖣' => 'D',
  '𝖤' => 'E',
  '𝖥' => 'F',
  '𝖦' => 'G',
  '𝖧' => 'H',
  '𝖨' => 'I',
  '𝖩' => 'J',
  '𝖪' => 'K',
  '𝖫' => 'L',
  '𝖬' => 'M',
  '𝖭' => 'N',
  '𝖮' => 'O',
  '𝖯' => 'P',
  '𝖰' => 'Q',
  '𝖱' => 'R',
  '𝖲' => 'S',
  '𝖳' => 'T',
  '𝖴' => 'U',
  '𝖵' => 'V',
  '𝖶' => 'W',
  '𝖷' => 'X',
  '𝖸' => 'Y',
  '𝖹' => 'Z',
  '𝖺' => 'a',
  '𝖻' => 'b',
  '𝖼' => 'c',
  '𝖽' => 'd',
  '𝖾' => 'e',
  '𝖿' => 'f',
  '𝗀' => 'g',
  '𝗁' => 'h',
  '𝗂' => 'i',
  '𝗃' => 'j',
  '𝗄' => 'k',
  '𝗅' => 'l',
  '𝗆' => 'm',
  '𝗇' => 'n',
  '𝗈' => 'o',
  '𝗉' => 'p',
  '𝗊' => 'q',
  '𝗋' => 'r',
  '𝗌' => 's',
  '𝗍' => 't',
  '𝗎' => 'u',
  '𝗏' => 'v',
  '𝗐' => 'w',
  '𝗑' => 'x',
  '𝗒' => 'y',
  '𝗓' => 'z',
  '𝗔' => 'A',
  '𝗕' => 'B',
  '𝗖' => 'C',
  '𝗗' => 'D',
  '𝗘' => 'E',
  '𝗙' => 'F',
  '𝗚' => 'G',
  '𝗛' => 'H',
  '𝗜' => 'I',
  '𝗝' => 'J',
  '𝗞' => 'K',
  '𝗟' => 'L',
  '𝗠' => 'M',
  '𝗡' => 'N',
  '𝗢' => 'O',
  '𝗣' => 'P',
  '𝗤' => 'Q',
  '𝗥' => 'R',
  '𝗦' => 'S',
  '𝗧' => 'T',
  '𝗨' => 'U',
  '𝗩' => 'V',
  '𝗪' => 'W',
  '𝗫' => 'X',
  '𝗬' => 'Y',
  '𝗭' => 'Z',
  '𝗮' => 'a',
  '𝗯' => 'b',
  '𝗰' => 'c',
  '𝗱' => 'd',
  '𝗲' => 'e',
  '𝗳' => 'f',
  '𝗴' => 'g',
  '𝗵' => 'h',
  '𝗶' => 'i',
  '𝗷' => 'j',
  '𝗸' => 'k',
  '𝗹' => 'l',
  '𝗺' => 'm',
  '𝗻' => 'n',
  '𝗼' => 'o',
  '𝗽' => 'p',
  '𝗾' => 'q',
  '𝗿' => 'r',
  '𝘀' => 's',
  '𝘁' => 't',
  '𝘂' => 'u',
  '𝘃' => 'v',
  '𝘄' => 'w',
  '𝘅' => 'x',
  '𝘆' => 'y',
  '𝘇' => 'z',
  '𝘈' => 'A',
  '𝘉' => 'B',
  '𝘊' => 'C',
  '𝘋' => 'D',
  '𝘌' => 'E',
  '𝘍' => 'F',
  '𝘎' => 'G',
  '𝘏' => 'H',
  '𝘐' => 'I',
  '𝘑' => 'J',
  '𝘒' => 'K',
  '𝘓' => 'L',
  '𝘔' => 'M',
  '𝘕' => 'N',
  '𝘖' => 'O',
  '𝘗' => 'P',
  '𝘘' => 'Q',
  '𝘙' => 'R',
  '𝘚' => 'S',
  '𝘛' => 'T',
  '𝘜' => 'U',
  '𝘝' => 'V',
  '𝘞' => 'W',
  '𝘟' => 'X',
  '𝘠' => 'Y',
  '𝘡' => 'Z',
  '𝘢' => 'a',
  '𝘣' => 'b',
  '𝘤' => 'c',
  '𝘥' => 'd',
  '𝘦' => 'e',
  '𝘧' => 'f',
  '𝘨' => 'g',
  '𝘩' => 'h',
  '𝘪' => 'i',
  '𝘫' => 'j',
  '𝘬' => 'k',
  '𝘭' => 'l',
  '𝘮' => 'm',
  '𝘯' => 'n',
  '𝘰' => 'o',
  '𝘱' => 'p',
  '𝘲' => 'q',
  '𝘳' => 'r',
  '𝘴' => 's',
  '𝘵' => 't',
  '𝘶' => 'u',
  '𝘷' => 'v',
  '𝘸' => 'w',
  '𝘹' => 'x',
  '𝘺' => 'y',
  '𝘻' => 'z',
  '𝘼' => 'A',
  '𝘽' => 'B',
  '𝘾' => 'C',
  '𝘿' => 'D',
  '𝙀' => 'E',
  '𝙁' => 'F',
  '𝙂' => 'G',
  '𝙃' => 'H',
  '𝙄' => 'I',
  '𝙅' => 'J',
  '𝙆' => 'K',
  '𝙇' => 'L',
  '𝙈' => 'M',
  '𝙉' => 'N',
  '𝙊' => 'O',
  '𝙋' => 'P',
  '𝙌' => 'Q',
  '𝙍' => 'R',
  '𝙎' => 'S',
  '𝙏' => 'T',
  '𝙐' => 'U',
  '𝙑' => 'V',
  '𝙒' => 'W',
  '𝙓' => 'X',
  '𝙔' => 'Y',
  '𝙕' => 'Z',
  '𝙖' => 'a',
  '𝙗' => 'b',
  '𝙘' => 'c',
  '𝙙' => 'd',
  '𝙚' => 'e',
  '𝙛' => 'f',
  '𝙜' => 'g',
  '𝙝' => 'h',
  '𝙞' => 'i',
  '𝙟' => 'j',
  '𝙠' => 'k',
  '𝙡' => 'l',
  '𝙢' => 'm',
  '𝙣' => 'n',
  '𝙤' => 'o',
  '𝙥' => 'p',
  '𝙦' => 'q',
  '𝙧' => 'r',
  '𝙨' => 's',
  '𝙩' => 't',
  '𝙪' => 'u',
  '𝙫' => 'v',
  '𝙬' => 'w',
  '𝙭' => 'x',
  '𝙮' => 'y',
  '𝙯' => 'z',
  '𝙰' => 'A',
  '𝙱' => 'B',
  '𝙲' => 'C',
  '𝙳' => 'D',
  '𝙴' => 'E',
  '𝙵' => 'F',
  '𝙶' => 'G',
  '𝙷' => 'H',
  '𝙸' => 'I',
  '𝙹' => 'J',
  '𝙺' => 'K',
  '𝙻' => 'L',
  '𝙼' => 'M',
  '𝙽' => 'N',
  '𝙾' => 'O',
  '𝙿' => 'P',
  '𝚀' => 'Q',
  '𝚁' => 'R',
  '𝚂' => 'S',
  '𝚃' => 'T',
  '𝚄' => 'U',
  '𝚅' => 'V',
  '𝚆' => 'W',
  '𝚇' => 'X',
  '𝚈' => 'Y',
  '𝚉' => 'Z',
  '𝚊' => 'a',
  '𝚋' => 'b',
  '𝚌' => 'c',
  '𝚍' => 'd',
  '𝚎' => 'e',
  '𝚏' => 'f',
  '𝚐' => 'g',
  '𝚑' => 'h',
  '𝚒' => 'i',
  '𝚓' => 'j',
  '𝚔' => 'k',
  '𝚕' => 'l',
  '𝚖' => 'm',
  '𝚗' => 'n',
  '𝚘' => 'o',
  '𝚙' => 'p',
  '𝚚' => 'q',
  '𝚛' => 'r',
  '𝚜' => 's',
  '𝚝' => 't',
  '𝚞' => 'u',
  '𝚟' => 'v',
  '𝚠' => 'w',
  '𝚡' => 'x',
  '𝚢' => 'y',
  '𝚣' => 'z',
  '𝚤' => 'ı',
  '𝚥' => 'ȷ',
  '𝚨' => 'Α',
  '𝚩' => 'Β',
  '𝚪' => 'Γ',
  '𝚫' => 'Δ',
  '𝚬' => 'Ε',
  '𝚭' => 'Ζ',
  '𝚮' => 'Η',
  '𝚯' => 'Θ',
  '𝚰' => 'Ι',
  '𝚱' => 'Κ',
  '𝚲' => 'Λ',
  '𝚳' => 'Μ',
  '𝚴' => 'Ν',
  '𝚵' => 'Ξ',
  '𝚶' => 'Ο',
  '𝚷' => 'Π',
  '𝚸' => 'Ρ',
  '𝚹' => 'Θ',
  '𝚺' => 'Σ',
  '𝚻' => 'Τ',
  '𝚼' => 'Υ',
  '𝚽' => 'Φ',
  '𝚾' => 'Χ',
  '𝚿' => 'Ψ',
  '𝛀' => 'Ω',
  '𝛁' => '∇',
  '𝛂' => 'α',
  '𝛃' => 'β',
  '𝛄' => 'γ',
  '𝛅' => 'δ',
  '𝛆' => 'ε',
  '𝛇' => 'ζ',
  '𝛈' => 'η',
  '𝛉' => 'θ',
  '𝛊' => 'ι',
  '𝛋' => 'κ',
  '𝛌' => 'λ',
  '𝛍' => 'μ',
  '𝛎' => 'ν',
  '𝛏' => 'ξ',
  '𝛐' => 'ο',
  '𝛑' => 'π',
  '𝛒' => 'ρ',
  '𝛓' => 'ς',
  '𝛔' => 'σ',
  '𝛕' => 'τ',
  '𝛖' => 'υ',
  '𝛗' => 'φ',
  '𝛘' => 'χ',
  '𝛙' => 'ψ',
  '𝛚' => 'ω',
  '𝛛' => '∂',
  '𝛜' => 'ε',
  '𝛝' => 'θ',
  '𝛞' => 'κ',
  '𝛟' => 'φ',
  '𝛠' => 'ρ',
  '𝛡' => 'π',
  '𝛢' => 'Α',
  '𝛣' => 'Β',
  '𝛤' => 'Γ',
  '𝛥' => 'Δ',
  '𝛦' => 'Ε',
  '𝛧' => 'Ζ',
  '𝛨' => 'Η',
  '𝛩' => 'Θ',
  '𝛪' => 'Ι',
  '𝛫' => 'Κ',
  '𝛬' => 'Λ',
  '𝛭' => 'Μ',
  '𝛮' => 'Ν',
  '𝛯' => 'Ξ',
  '𝛰' => 'Ο',
  '𝛱' => 'Π',
  '𝛲' => 'Ρ',
  '𝛳' => 'Θ',
  '𝛴' => 'Σ',
  '𝛵' => 'Τ',
  '𝛶' => 'Υ',
  '𝛷' => 'Φ',
  '𝛸' => 'Χ',
  '𝛹' => 'Ψ',
  '𝛺' => 'Ω',
  '𝛻' => '∇',
  '𝛼' => 'α',
  '𝛽' => 'β',
  '𝛾' => 'γ',
  '𝛿' => 'δ',
  '𝜀' => 'ε',
  '𝜁' => 'ζ',
  '𝜂' => 'η',
  '𝜃' => 'θ',
  '𝜄' => 'ι',
  '𝜅' => 'κ',
  '𝜆' => 'λ',
  '𝜇' => 'μ',
  '𝜈' => 'ν',
  '𝜉' => 'ξ',
  '𝜊' => 'ο',
  '𝜋' => 'π',
  '𝜌' => 'ρ',
  '𝜍' => 'ς',
  '𝜎' => 'σ',
  '𝜏' => 'τ',
  '𝜐' => 'υ',
  '𝜑' => 'φ',
  '𝜒' => 'χ',
  '𝜓' => 'ψ',
  '𝜔' => 'ω',
  '𝜕' => '∂',
  '𝜖' => 'ε',
  '𝜗' => 'θ',
  '𝜘' => 'κ',
  '𝜙' => 'φ',
  '𝜚' => 'ρ',
  '𝜛' => 'π',
  '𝜜' => 'Α',
  '𝜝' => 'Β',
  '𝜞' => 'Γ',
  '𝜟' => 'Δ',
  '𝜠' => 'Ε',
  '𝜡' => 'Ζ',
  '𝜢' => 'Η',
  '𝜣' => 'Θ',
  '𝜤' => 'Ι',
  '𝜥' => 'Κ',
  '𝜦' => 'Λ',
  '𝜧' => 'Μ',
  '𝜨' => 'Ν',
  '𝜩' => 'Ξ',
  '𝜪' => 'Ο',
  '𝜫' => 'Π',
  '𝜬' => 'Ρ',
  '𝜭' => 'Θ',
  '𝜮' => 'Σ',
  '𝜯' => 'Τ',
  '𝜰' => 'Υ',
  '𝜱' => 'Φ',
  '𝜲' => 'Χ',
  '𝜳' => 'Ψ',
  '𝜴' => 'Ω',
  '𝜵' => '∇',
  '𝜶' => 'α',
  '𝜷' => 'β',
  '𝜸' => 'γ',
  '𝜹' => 'δ',
  '𝜺' => 'ε',
  '𝜻' => 'ζ',
  '𝜼' => 'η',
  '𝜽' => 'θ',
  '𝜾' => 'ι',
  '𝜿' => 'κ',
  '𝝀' => 'λ',
  '𝝁' => 'μ',
  '𝝂' => 'ν',
  '𝝃' => 'ξ',
  '𝝄' => 'ο',
  '𝝅' => 'π',
  '𝝆' => 'ρ',
  '𝝇' => 'ς',
  '𝝈' => 'σ',
  '𝝉' => 'τ',
  '𝝊' => 'υ',
  '𝝋' => 'φ',
  '𝝌' => 'χ',
  '𝝍' => 'ψ',
  '𝝎' => 'ω',
  '𝝏' => '∂',
  '𝝐' => 'ε',
  '𝝑' => 'θ',
  '𝝒' => 'κ',
  '𝝓' => 'φ',
  '𝝔' => 'ρ',
  '𝝕' => 'π',
  '𝝖' => 'Α',
  '𝝗' => 'Β',
  '𝝘' => 'Γ',
  '𝝙' => 'Δ',
  '𝝚' => 'Ε',
  '𝝛' => 'Ζ',
  '𝝜' => 'Η',
  '𝝝' => 'Θ',
  '𝝞' => 'Ι',
  '𝝟' => 'Κ',
  '𝝠' => 'Λ',
  '𝝡' => 'Μ',
  '𝝢' => 'Ν',
  '𝝣' => 'Ξ',
  '𝝤' => 'Ο',
  '𝝥' => 'Π',
  '𝝦' => 'Ρ',
  '𝝧' => 'Θ',
  '𝝨' => 'Σ',
  '𝝩' => 'Τ',
  '𝝪' => 'Υ',
  '𝝫' => 'Φ',
  '𝝬' => 'Χ',
  '𝝭' => 'Ψ',
  '𝝮' => 'Ω',
  '𝝯' => '∇',
  '𝝰' => 'α',
  '𝝱' => 'β',
  '𝝲' => 'γ',
  '𝝳' => 'δ',
  '𝝴' => 'ε',
  '𝝵' => 'ζ',
  '𝝶' => 'η',
  '𝝷' => 'θ',
  '𝝸' => 'ι',
  '𝝹' => 'κ',
  '𝝺' => 'λ',
  '𝝻' => 'μ',
  '𝝼' => 'ν',
  '𝝽' => 'ξ',
  '𝝾' => 'ο',
  '𝝿' => 'π',
  '𝞀' => 'ρ',
  '𝞁' => 'ς',
  '𝞂' => 'σ',
  '𝞃' => 'τ',
  '𝞄' => 'υ',
  '𝞅' => 'φ',
  '𝞆' => 'χ',
  '𝞇' => 'ψ',
  '𝞈' => 'ω',
  '𝞉' => '∂',
  '𝞊' => 'ε',
  '𝞋' => 'θ',
  '𝞌' => 'κ',
  '𝞍' => 'φ',
  '𝞎' => 'ρ',
  '𝞏' => 'π',
  '𝞐' => 'Α',
  '𝞑' => 'Β',
  '𝞒' => 'Γ',
  '𝞓' => 'Δ',
  '𝞔' => 'Ε',
  '𝞕' => 'Ζ',
  '𝞖' => 'Η',
  '𝞗' => 'Θ',
  '𝞘' => 'Ι',
  '𝞙' => 'Κ',
  '𝞚' => 'Λ',
  '𝞛' => 'Μ',
  '𝞜' => 'Ν',
  '𝞝' => 'Ξ',
  '𝞞' => 'Ο',
  '𝞟' => 'Π',
  '𝞠' => 'Ρ',
  '𝞡' => 'Θ',
  '𝞢' => 'Σ',
  '𝞣' => 'Τ',
  '𝞤' => 'Υ',
  '𝞥' => 'Φ',
  '𝞦' => 'Χ',
  '𝞧' => 'Ψ',
  '𝞨' => 'Ω',
  '𝞩' => '∇',
  '𝞪' => 'α',
  '𝞫' => 'β',
  '𝞬' => 'γ',
  '𝞭' => 'δ',
  '𝞮' => 'ε',
  '𝞯' => 'ζ',
  '𝞰' => 'η',
  '𝞱' => 'θ',
  '𝞲' => 'ι',
  '𝞳' => 'κ',
  '𝞴' => 'λ',
  '𝞵' => 'μ',
  '𝞶' => 'ν',
  '𝞷' => 'ξ',
  '𝞸' => 'ο',
  '𝞹' => 'π',
  '𝞺' => 'ρ',
  '𝞻' => 'ς',
  '𝞼' => 'σ',
  '𝞽' => 'τ',
  '𝞾' => 'υ',
  '𝞿' => 'φ',
  '𝟀' => 'χ',
  '𝟁' => 'ψ',
  '𝟂' => 'ω',
  '𝟃' => '∂',
  '𝟄' => 'ε',
  '𝟅' => 'θ',
  '𝟆' => 'κ',
  '𝟇' => 'φ',
  '𝟈' => 'ρ',
  '𝟉' => 'π',
  '𝟊' => 'Ϝ',
  '𝟋' => 'ϝ',
  '𝟎' => '0',
  '𝟏' => '1',
  '𝟐' => '2',
  '𝟑' => '3',
  '𝟒' => '4',
  '𝟓' => '5',
  '𝟔' => '6',
  '𝟕' => '7',
  '𝟖' => '8',
  '𝟗' => '9',
  '𝟘' => '0',
  '𝟙' => '1',
  '𝟚' => '2',
  '𝟛' => '3',
  '𝟜' => '4',
  '𝟝' => '5',
  '𝟞' => '6',
  '𝟟' => '7',
  '𝟠' => '8',
  '𝟡' => '9',
  '𝟢' => '0',
  '𝟣' => '1',
  '𝟤' => '2',
  '𝟥' => '3',
  '𝟦' => '4',
  '𝟧' => '5',
  '𝟨' => '6',
  '𝟩' => '7',
  '𝟪' => '8',
  '𝟫' => '9',
  '𝟬' => '0',
  '𝟭' => '1',
  '𝟮' => '2',
  '𝟯' => '3',
  '𝟰' => '4',
  '𝟱' => '5',
  '𝟲' => '6',
  '𝟳' => '7',
  '𝟴' => '8',
  '𝟵' => '9',
  '𝟶' => '0',
  '𝟷' => '1',
  '𝟸' => '2',
  '𝟹' => '3',
  '𝟺' => '4',
  '𝟻' => '5',
  '𝟼' => '6',
  '𝟽' => '7',
  '𝟾' => '8',
  '𝟿' => '9',
  '𞸀' => 'ا',
  '𞸁' => 'ب',
  '𞸂' => 'ج',
  '𞸃' => 'د',
  '𞸅' => 'و',
  '𞸆' => 'ز',
  '𞸇' => 'ح',
  '𞸈' => 'ط',
  '𞸉' => 'ي',
  '𞸊' => 'ك',
  '𞸋' => 'ل',
  '𞸌' => 'م',
  '𞸍' => 'ن',
  '𞸎' => 'س',
  '𞸏' => 'ع',
  '𞸐' => 'ف',
  '𞸑' => 'ص',
  '𞸒' => 'ق',
  '𞸓' => 'ر',
  '𞸔' => 'ش',
  '𞸕' => 'ت',
  '𞸖' => 'ث',
  '𞸗' => 'خ',
  '𞸘' => 'ذ',
  '𞸙' => 'ض',
  '𞸚' => 'ظ',
  '𞸛' => 'غ',
  '𞸜' => 'ٮ',
  '𞸝' => 'ں',
  '𞸞' => 'ڡ',
  '𞸟' => 'ٯ',
  '𞸡' => 'ب',
  '𞸢' => 'ج',
  '𞸤' => 'ه',
  '𞸧' => 'ح',
  '𞸩' => 'ي',
  '𞸪' => 'ك',
  '𞸫' => 'ل',
  '𞸬' => 'م',
  '𞸭' => 'ن',
  '𞸮' => 'س',
  '𞸯' => 'ع',
  '𞸰' => 'ف',
  '𞸱' => 'ص',
  '𞸲' => 'ق',
  '𞸴' => 'ش',
  '𞸵' => 'ت',
  '𞸶' => 'ث',
  '𞸷' => 'خ',
  '𞸹' => 'ض',
  '𞸻' => 'غ',
  '𞹂' => 'ج',
  '𞹇' => 'ح',
  '𞹉' => 'ي',
  '𞹋' => 'ل',
  '𞹍' => 'ن',
  '𞹎' => 'س',
  '𞹏' => 'ع',
  '𞹑' => 'ص',
  '𞹒' => 'ق',
  '𞹔' => 'ش',
  '𞹗' => 'خ',
  '𞹙' => 'ض',
  '𞹛' => 'غ',
  '𞹝' => 'ں',
  '𞹟' => 'ٯ',
  '𞹡' => 'ب',
  '𞹢' => 'ج',
  '𞹤' => 'ه',
  '𞹧' => 'ح',
  '𞹨' => 'ط',
  '𞹩' => 'ي',
  '𞹪' => 'ك',
  '𞹬' => 'م',
  '𞹭' => 'ن',
  '𞹮' => 'س',
  '𞹯' => 'ع',
  '𞹰' => 'ف',
  '𞹱' => 'ص',
  '𞹲' => 'ق',
  '𞹴' => 'ش',
  '𞹵' => 'ت',
  '𞹶' => 'ث',
  '𞹷' => 'خ',
  '𞹹' => 'ض',
  '𞹺' => 'ظ',
  '𞹻' => 'غ',
  '𞹼' => 'ٮ',
  '𞹾' => 'ڡ',
  '𞺀' => 'ا',
  '𞺁' => 'ب',
  '𞺂' => 'ج',
  '𞺃' => 'د',
  '𞺄' => 'ه',
  '𞺅' => 'و',
  '𞺆' => 'ز',
  '𞺇' => 'ح',
  '𞺈' => 'ط',
  '𞺉' => 'ي',
  '𞺋' => 'ل',
  '𞺌' => 'م',
  '𞺍' => 'ن',
  '𞺎' => 'س',
  '𞺏' => 'ع',
  '𞺐' => 'ف',
  '𞺑' => 'ص',
  '𞺒' => 'ق',
  '𞺓' => 'ر',
  '𞺔' => 'ش',
  '𞺕' => 'ت',
  '𞺖' => 'ث',
  '𞺗' => 'خ',
  '𞺘' => 'ذ',
  '𞺙' => 'ض',
  '𞺚' => 'ظ',
  '𞺛' => 'غ',
  '𞺡' => 'ب',
  '𞺢' => 'ج',
  '𞺣' => 'د',
  '𞺥' => 'و',
  '𞺦' => 'ز',
  '𞺧' => 'ح',
  '𞺨' => 'ط',
  '𞺩' => 'ي',
  '𞺫' => 'ل',
  '𞺬' => 'م',
  '𞺭' => 'ن',
  '𞺮' => 'س',
  '𞺯' => 'ع',
  '𞺰' => 'ف',
  '𞺱' => 'ص',
  '𞺲' => 'ق',
  '𞺳' => 'ر',
  '𞺴' => 'ش',
  '𞺵' => 'ت',
  '𞺶' => 'ث',
  '𞺷' => 'خ',
  '𞺸' => 'ذ',
  '𞺹' => 'ض',
  '𞺺' => 'ظ',
  '𞺻' => 'غ',
  '🄀' => '0.',
  '🄁' => '0,',
  '🄂' => '1,',
  '🄃' => '2,',
  '🄄' => '3,',
  '🄅' => '4,',
  '🄆' => '5,',
  '🄇' => '6,',
  '🄈' => '7,',
  '🄉' => '8,',
  '🄊' => '9,',
  '🄐' => '(A)',
  '🄑' => '(B)',
  '🄒' => '(C)',
  '🄓' => '(D)',
  '🄔' => '(E)',
  '🄕' => '(F)',
  '🄖' => '(G)',
  '🄗' => '(H)',
  '🄘' => '(I)',
  '🄙' => '(J)',
  '🄚' => '(K)',
  '🄛' => '(L)',
  '🄜' => '(M)',
  '🄝' => '(N)',
  '🄞' => '(O)',
  '🄟' => '(P)',
  '🄠' => '(Q)',
  '🄡' => '(R)',
  '🄢' => '(S)',
  '🄣' => '(T)',
  '🄤' => '(U)',
  '🄥' => '(V)',
  '🄦' => '(W)',
  '🄧' => '(X)',
  '🄨' => '(Y)',
  '🄩' => '(Z)',
  '🄪' => '〔S〕',
  '🄫' => 'C',
  '🄬' => 'R',
  '🄭' => 'CD',
  '🄮' => 'WZ',
  '🄰' => 'A',
  '🄱' => 'B',
  '🄲' => 'C',
  '🄳' => 'D',
  '🄴' => 'E',
  '🄵' => 'F',
  '🄶' => 'G',
  '🄷' => 'H',
  '🄸' => 'I',
  '🄹' => 'J',
  '🄺' => 'K',
  '🄻' => 'L',
  '🄼' => 'M',
  '🄽' => 'N',
  '🄾' => 'O',
  '🄿' => 'P',
  '🅀' => 'Q',
  '🅁' => 'R',
  '🅂' => 'S',
  '🅃' => 'T',
  '🅄' => 'U',
  '🅅' => 'V',
  '🅆' => 'W',
  '🅇' => 'X',
  '🅈' => 'Y',
  '🅉' => 'Z',
  '🅊' => 'HV',
  '🅋' => 'MV',
  '🅌' => 'SD',
  '🅍' => 'SS',
  '🅎' => 'PPV',
  '🅏' => 'WC',
  '🅪' => 'MC',
  '🅫' => 'MD',
  '🅬' => 'MR',
  '🆐' => 'DJ',
  '🈀' => 'ほか',
  '🈁' => 'ココ',
  '🈂' => 'サ',
  '🈐' => '手',
  '🈑' => '字',
  '🈒' => '双',
  '🈓' => 'デ',
  '🈔' => '二',
  '🈕' => '多',
  '🈖' => '解',
  '🈗' => '天',
  '🈘' => '交',
  '🈙' => '映',
  '🈚' => '無',
  '🈛' => '料',
  '🈜' => '前',
  '🈝' => '後',
  '🈞' => '再',
  '🈟' => '新',
  '🈠' => '初',
  '🈡' => '終',
  '🈢' => '生',
  '🈣' => '販',
  '🈤' => '声',
  '🈥' => '吹',
  '🈦' => '演',
  '🈧' => '投',
  '🈨' => '捕',
  '🈩' => '一',
  '🈪' => '三',
  '🈫' => '遊',
  '🈬' => '左',
  '🈭' => '中',
  '🈮' => '右',
  '🈯' => '指',
  '🈰' => '走',
  '🈱' => '打',
  '🈲' => '禁',
  '🈳' => '空',
  '🈴' => '合',
  '🈵' => '満',
  '🈶' => '有',
  '🈷' => '月',
  '🈸' => '申',
  '🈹' => '割',
  '🈺' => '営',
  '🈻' => '配',
  '🉀' => '〔本〕',
  '🉁' => '〔三〕',
  '🉂' => '〔二〕',
  '🉃' => '〔安〕',
  '🉄' => '〔点〕',
  '🉅' => '〔打〕',
  '🉆' => '〔盗〕',
  '🉇' => '〔勝〕',
  '🉈' => '〔敗〕',
  '🉐' => '得',
  '🉑' => '可',
  '🯰' => '0',
  '🯱' => '1',
  '🯲' => '2',
  '🯳' => '3',
  '🯴' => '4',
  '🯵' => '5',
  '🯶' => '6',
  '🯷' => '7',
  '🯸' => '8',
  '🯹' => '9',
);
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '΅' => '΅',
  'Ά' => 'Ά',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ὲ' => 'ὲ',
  'ὴ' => 'ὴ',
  'ὶ' => 'ὶ',
  'ὸ' => 'ὸ',
  'ὺ' => 'ὺ',
  'ὼ' => 'ὼ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'ᾼ' => 'ᾼ',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Ὴ' => 'Ὴ',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ὼ' => 'Ὼ',
  'ῼ' => 'ῼ',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
);
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Intl\Normalizer as p;

if (!function_exists('normalizer_is_normalized')) {
    function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); }
}
if (!function_exists('normalizer_normalize')) {
    function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Intl\Normalizer as p;

if (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('normalizer_is_normalized')) {
    function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); }
}
if (!function_exists('normalizer_normalize')) {
    function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Intl\Normalizer;

/**
 * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
 *
 * It has been validated with Unicode 6.3 Normalization Conformance Test.
 * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
class Normalizer
{
    public const FORM_D = \Normalizer::FORM_D;
    public const FORM_KD = \Normalizer::FORM_KD;
    public const FORM_C = \Normalizer::FORM_C;
    public const FORM_KC = \Normalizer::FORM_KC;
    public const NFD = \Normalizer::NFD;
    public const NFKD = \Normalizer::NFKD;
    public const NFC = \Normalizer::NFC;
    public const NFKC = \Normalizer::NFKC;

    private static $C;
    private static $D;
    private static $KD;
    private static $cC;
    private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
    private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";

    public static function isNormalized(string $s, int $form = self::FORM_C)
    {
        if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) {
            return false;
        }
        if (!isset($s[strspn($s, self::$ASCII)])) {
            return true;
        }
        if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
            return true;
        }

        return self::normalize($s, $form) === $s;
    }

    public static function normalize(string $s, int $form = self::FORM_C)
    {
        if (!preg_match('//u', $s)) {
            return false;
        }

        switch ($form) {
            case self::NFC: $C = true; $K = false; break;
            case self::NFD: $C = false; $K = false; break;
            case self::NFKC: $C = true; $K = true; break;
            case self::NFKD: $C = false; $K = true; break;
            default:
                if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
                    return $s;
                }

                if (80000 > \PHP_VERSION_ID) {
                    return false;
                }

                throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form');
        }

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

        if ($K && null === self::$KD) {
            self::$KD = self::getData('compatibilityDecomposition');
        }

        if (null === self::$D) {
            self::$D = self::getData('canonicalDecomposition');
            self::$cC = self::getData('combiningClass');
        }

        if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
            mb_internal_encoding('8bit');
        }

        $r = self::decompose($s, $K);

        if ($C) {
            if (null === self::$C) {
                self::$C = self::getData('canonicalComposition');
            }

            $r = self::recompose($r);
        }
        if (null !== $mbEncoding) {
            mb_internal_encoding($mbEncoding);
        }

        return $r;
    }

    private static function recompose($s)
    {
        $ASCII = self::$ASCII;
        $compMap = self::$C;
        $combClass = self::$cC;
        $ulenMask = self::$ulenMask;

        $result = $tail = '';

        $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
        $len = \strlen($s);

        $lastUchr = substr($s, 0, $i);
        $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;

        while ($i < $len) {
            if ($s[$i] < "\x80") {
                // ASCII chars

                if ($tail) {
                    $lastUchr .= $tail;
                    $tail = '';
                }

                if ($j = strspn($s, $ASCII, $i + 1)) {
                    $lastUchr .= substr($s, $i, $j);
                    $i += $j;
                }

                $result .= $lastUchr;
                $lastUchr = $s[$i];
                $lastUcls = 0;
                ++$i;
                continue;
            }

            $ulen = $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);

            if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
                || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
                || $lastUcls) {
                // Table lookup and combining chars composition

                $ucls = $combClass[$uchr] ?? 0;

                if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
                    $lastUchr = $compMap[$lastUchr.$uchr];
                } elseif ($lastUcls = $ucls) {
                    $tail .= $uchr;
                } else {
                    if ($tail) {
                        $lastUchr .= $tail;
                        $tail = '';
                    }

                    $result .= $lastUchr;
                    $lastUchr = $uchr;
                }
            } else {
                // Hangul chars

                $L = \ord($lastUchr[2]) - 0x80;
                $V = \ord($uchr[2]) - 0xA1;
                $T = 0;

                $uchr = substr($s, $i + $ulen, 3);

                if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
                    $T = \ord($uchr[2]) - 0xA7;
                    0 > $T && $T += 0x40;
                    $ulen += 3;
                }

                $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
                $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
            }

            $i += $ulen;
        }

        return $result.$lastUchr.$tail;
    }

    private static function decompose($s, $c)
    {
        $result = '';

        $ASCII = self::$ASCII;
        $decompMap = self::$D;
        $combClass = self::$cC;
        $ulenMask = self::$ulenMask;
        if ($c) {
            $compatMap = self::$KD;
        }

        $c = [];
        $i = 0;
        $len = \strlen($s);

        while ($i < $len) {
            if ($s[$i] < "\x80") {
                // ASCII chars

                if ($c) {
                    ksort($c);
                    $result .= implode('', $c);
                    $c = [];
                }

                $j = 1 + strspn($s, $ASCII, $i + 1);
                $result .= substr($s, $i, $j);
                $i += $j;
                continue;
            }

            $ulen = $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;

            if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
                // Table lookup

                if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) {
                    $uchr = $j;

                    $j = \strlen($uchr);
                    $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];

                    if ($ulen != $j) {
                        // Put trailing chars in $s

                        $j -= $ulen;
                        $i -= $j;

                        if (0 > $i) {
                            $s = str_repeat(' ', -$i).$s;
                            $len -= $i;
                            $i = 0;
                        }

                        while ($j--) {
                            $s[$i + $j] = $uchr[$ulen + $j];
                        }

                        $uchr = substr($uchr, 0, $ulen);
                    }
                }
                if (isset($combClass[$uchr])) {
                    // Combining chars, for sorting

                    if (!isset($c[$combClass[$uchr]])) {
                        $c[$combClass[$uchr]] = '';
                    }
                    $c[$combClass[$uchr]] .= $uchr;
                    continue;
                }
            } else {
                // Hangul chars

                $uchr = unpack('C*', $uchr);
                $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;

                $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
                       ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));

                if ($j %= 28) {
                    $uchr .= $j < 25
                        ? ("\xE1\x86".\chr(0xA7 + $j))
                        : ("\xE1\x87".\chr(0x67 + $j));
                }
            }
            if ($c) {
                ksort($c);
                $result .= implode('', $c);
                $c = [];
            }

            $result .= $uchr;
        }

        if ($c) {
            ksort($c);
            $result .= implode('', $c);
        }

        return $result;
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }
}
<?php

require __DIR__ . '/../vendor/autoload.php';

$data = json_decode(file_get_contents('data.json'));

// Validate
$validator = new JsonSchema\Validator();
$validator->check($data, (object) array('$ref' => 'file://' . realpath('schema.json')));

if ($validator->isValid()) {
    echo "The supplied JSON validates against the schema.\n";
} else {
    echo "JSON does not validate. Violations:\n";
    foreach ($validator->getErrors() as $error) {
        echo sprintf("[%s] %s\n", $error['property'], $error['message']);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\InvalidSchemaMediaTypeException;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\ResourceNotFoundException;
use JsonSchema\Uri\Retrievers\FileGetContents;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Retrieves JSON Schema URIs
 *
 * @author Tyler Akins <fidian@rumkin.com>
 */
class UriRetriever implements BaseUriRetrieverInterface
{
    /**
     * @var array Map of URL translations
     */
    protected $translationMap = array(
        // use local copies of the spec schemas
        '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
    );

    /**
     * @var array A list of endpoints for media type check exclusion
     */
    protected $allowedInvalidContentTypeEndpoints = array(
        'http://json-schema.org/',
        'https://json-schema.org/'
    );

    /**
     * @var null|UriRetrieverInterface
     */
    protected $uriRetriever = null;

    /**
     * @var array|object[]
     *
     * @see loadSchema
     */
    private $schemaCache = array();

    /**
     * Adds an endpoint to the media type validation exclusion list
     *
     * @param string $endpoint
     */
    public function addInvalidContentTypeEndpoint($endpoint)
    {
        $this->allowedInvalidContentTypeEndpoints[] = $endpoint;
    }

    /**
     * Guarantee the correct media type was encountered
     *
     * @param UriRetrieverInterface $uriRetriever
     * @param string                $uri
     *
     * @return bool|void
     */
    public function confirmMediaType($uriRetriever, $uri)
    {
        $contentType = $uriRetriever->getContentType();

        if (is_null($contentType)) {
            // Well, we didn't get an invalid one
            return;
        }

        if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) {
            return;
        }

        foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) {
            if (strpos($uri, $endpoint) === 0) {
                return true;
            }
        }

        throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
    }

    /**
     * Get a URI Retriever
     *
     * If none is specified, sets a default FileGetContents retriever and
     * returns that object.
     *
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        if (is_null($this->uriRetriever)) {
            $this->setUriRetriever(new FileGetContents());
        }

        return $this->uriRetriever;
    }

    /**
     * Resolve a schema based on pointer
     *
     * URIs can have a fragment at the end in the format of
     * #/path/to/object and we are to look up the 'path' property of
     * the first object then the 'to' and 'object' properties.
     *
     * @param object $jsonSchema JSON Schema contents
     * @param string $uri        JSON Schema URI
     *
     * @throws ResourceNotFoundException
     *
     * @return object JSON Schema after walking down the fragment pieces
     */
    public function resolvePointer($jsonSchema, $uri)
    {
        $resolver = new UriResolver();
        $parsed = $resolver->parse($uri);
        if (empty($parsed['fragment'])) {
            return $jsonSchema;
        }

        $path = explode('/', $parsed['fragment']);
        while ($path) {
            $pathElement = array_shift($path);
            if (!empty($pathElement)) {
                $pathElement = str_replace('~1', '/', $pathElement);
                $pathElement = str_replace('~0', '~', $pathElement);
                if (!empty($jsonSchema->$pathElement)) {
                    $jsonSchema = $jsonSchema->$pathElement;
                } else {
                    throw new ResourceNotFoundException(
                        'Fragment "' . $parsed['fragment'] . '" not found'
                        . ' in ' . $uri
                    );
                }

                if (!is_object($jsonSchema)) {
                    throw new ResourceNotFoundException(
                        'Fragment part "' . $pathElement . '" is no object '
                        . ' in ' . $uri
                    );
                }
            }
        }

        return $jsonSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function retrieve($uri, $baseUri = null, $translate = true)
    {
        $resolver = new UriResolver();
        $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);

        //fetch URL without #fragment
        $arParts = $resolver->parse($resolvedUri);
        if (isset($arParts['fragment'])) {
            unset($arParts['fragment']);
            $fetchUri = $resolver->generate($arParts);
        }

        // apply URI translations
        if ($translate) {
            $fetchUri = $this->translate($fetchUri);
        }

        $jsonSchema = $this->loadSchema($fetchUri);

        // Use the JSON pointer if specified
        $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);

        if ($jsonSchema instanceof \stdClass) {
            $jsonSchema->id = $resolvedUri;
        }

        return $jsonSchema;
    }

    /**
     * Fetch a schema from the given URI, json-decode it and return it.
     * Caches schema objects.
     *
     * @param string $fetchUri Absolute URI
     *
     * @return object JSON schema object
     */
    protected function loadSchema($fetchUri)
    {
        if (isset($this->schemaCache[$fetchUri])) {
            return $this->schemaCache[$fetchUri];
        }

        $uriRetriever = $this->getUriRetriever();
        $contents = $this->uriRetriever->retrieve($fetchUri);
        $this->confirmMediaType($uriRetriever, $fetchUri);
        $jsonSchema = json_decode($contents);

        if (JSON_ERROR_NONE < $error = json_last_error()) {
            throw new JsonDecodingException($error);
        }

        $this->schemaCache[$fetchUri] = $jsonSchema;

        return $jsonSchema;
    }

    /**
     * Set the URI Retriever
     *
     * @param UriRetrieverInterface $uriRetriever
     *
     * @return $this for chaining
     */
    public function setUriRetriever(UriRetrieverInterface $uriRetriever)
    {
        $this->uriRetriever = $uriRetriever;

        return $this;
    }

    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);

        $components = array();
        if (5 < count($match)) {
            $components =  array(
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            );
        }

        if (7 < count($match)) {
            $components['query'] = $match[7];
        }

        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components)) {
            $uri .= $components['query'];
        }

        if (array_key_exists('fragment', $components)) {
            $uri .= $components['fragment'];
        }

        return $uri;
    }

    /**
     * Resolves a URI
     *
     * @param string $uri     Absolute or relative
     * @param string $baseUri Optional base URI
     *
     * @return string
     */
    public function resolve($uri, $baseUri = null)
    {
        $components = $this->parse($uri);
        $path = $components['path'];

        if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
            return $uri;
        }

        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);

        return $this->generate($baseComponents);
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }

    /**
     * Set a URL translation rule
     */
    public function setTranslation($from, $to)
    {
        $this->translationMap[$from] = $to;
    }

    /**
     * Apply URI translation rules
     */
    public function translate($uri)
    {
        foreach ($this->translationMap as $from => $to) {
            $uri = preg_replace($from, $to, $uri);
        }

        // translate references to local files within the json-schema package
        $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);

        return $uri;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * Tries to retrieve JSON schemas from a URI using cURL library
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class Curl extends AbstractRetriever
{
    protected $messageBody;

    public function __construct()
    {
        if (!function_exists('curl_init')) {
            // Cannot test this, because curl_init is present on all test platforms plus mock
            throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
        }
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $uri);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE));

        $response = curl_exec($ch);
        if (false === $response) {
            throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
        }

        $this->fetchMessageBody($response);
        $this->fetchContentType($response);

        curl_close($ch);

        return $this->messageBody;
    }

    /**
     * @param string $response cURL HTTP response
     */
    private function fetchMessageBody($response)
    {
        preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
        $this->messageBody = $match[1];
    }

    /**
     * @param string $response cURL HTTP response
     *
     * @return bool Whether the Content-Type header was found or not
     */
    protected function fetchContentType($response)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
            $this->contentType = trim($match[1]);

            return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * Interface for URI retrievers
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a schema from the specified URI
     *
     * @param string $uri URI that resolves to a JSON schema
     *
     * @throws \JsonSchema\Exception\ResourceNotFoundException
     *
     * @return mixed string|null
     */
    public function retrieve($uri);

    /**
     * Get media content type
     *
     * @return string
     */
    public function getContentType();
}
<?php

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Validator;

/**
 * URI retrieved based on a predefined array of schemas
 *
 * @example
 *
 *      $retriever = new PredefinedArray(array(
 *          'http://acme.com/schemas/person#'  => '{ ... }',
 *          'http://acme.com/schemas/address#' => '{ ... }',
 *      ))
 *
 *      $schema = $retriever->retrieve('http://acme.com/schemas/person#');
 */
class PredefinedArray extends AbstractRetriever
{
    /**
     * Contains schemas as URI => JSON
     *
     * @var array
     */
    private $schemas;

    /**
     * Constructor
     *
     * @param array  $schemas
     * @param string $contentType
     */
    public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
    {
        $this->schemas     = $schemas;
        $this->contentType = $contentType;
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        if (!array_key_exists($uri, $this->schemas)) {
            throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
                'The JSON schema "%s" was not found.',
                $uri
            ));
        }

        return $this->schemas[$uri];
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\ResourceNotFoundException;

/**
 * Tries to retrieve JSON schemas from a URI using file_get_contents()
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class FileGetContents extends AbstractRetriever
{
    protected $messageBody;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $errorMessage = null;
        set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
            $errorMessage = $errstr;
        });
        $response = file_get_contents($uri);
        restore_error_handler();

        if ($errorMessage) {
            throw new ResourceNotFoundException($errorMessage);
        }

        if (false === $response) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        if ($response == ''
            && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
        ) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        $this->messageBody = $response;
        if (!empty($http_response_header)) {
            // $http_response_header cannot be tested, because it's defined in the method's local scope
            // See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info.
            $this->fetchContentType($http_response_header); // @codeCoverageIgnore
        } else {                                            // @codeCoverageIgnore
            // Could be a "file://" url or something else - fake up the response
            $this->contentType = null;
        }

        return $this->messageBody;
    }

    /**
     * @param array $headers HTTP Response Headers
     *
     * @return bool Whether the Content-Type header was found or not
     */
    private function fetchContentType(array $headers)
    {
        foreach ($headers as $header) {
            if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param string $header
     *
     * @return string|null
     */
    protected static function getContentTypeMatchInHeader($header)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
            return trim($match[1]);
        }

        return null;
    }
}
<?php
/**
 * JsonSchema
 *
 * @filesource
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * AbstractRetriever implements the default shared behavior
 * that all descendant Retrievers should inherit
 *
 * @author Steven Garcia <webwhammy@gmail.com>
 */
abstract class AbstractRetriever implements UriRetrieverInterface
{
    /**
     * Media content type
     *
     * @var string
     */
    protected $contentType;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
     */
    public function getContentType()
    {
        return $this->contentType;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\UriResolverException;
use JsonSchema\UriResolverInterface;

/**
 * Resolves JSON Schema URIs
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class UriResolver implements UriResolverInterface
{
    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);

        $components = array();
        if (5 < count($match)) {
            $components =  array(
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            );
        }
        if (7 < count($match)) {
            $components['query'] = $match[7];
        }
        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components) && strlen($components['query'])) {
            $uri .= '?' . $components['query'];
        }
        if (array_key_exists('fragment', $components)) {
            $uri .= '#' . $components['fragment'];
        }

        return $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($uri, $baseUri = null)
    {
        // treat non-uri base as local file path
        if (
            !is_null($baseUri) &&
            !filter_var($baseUri, \FILTER_VALIDATE_URL) &&
            !preg_match('|^[^/]+://|u', $baseUri)
        ) {
            if (is_file($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri);
            } elseif (is_dir($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri) . '/';
            } else {
                $baseUri = 'file://' . getcwd() . '/' . $baseUri;
            }
        }

        if ($uri == '') {
            return $baseUri;
        }

        $components = $this->parse($uri);
        $path = $components['path'];

        if (!empty($components['scheme'])) {
            return $uri;
        }
        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
        if (isset($components['fragment'])) {
            $baseComponents['fragment'] = $components['fragment'];
        }

        return $this->generate($baseComponents);
    }

    /**
     * Tries to glue a relative path onto an absolute one
     *
     * @param string $relativePath
     * @param string $basePath
     *
     * @throws UriResolverException
     *
     * @return string Merged path
     */
    public static function combineRelativePathWithBasePath($relativePath, $basePath)
    {
        $relativePath = self::normalizePath($relativePath);
        if ($relativePath == '') {
            return $basePath;
        }
        if ($relativePath[0] == '/') {
            return $relativePath;
        }

        $basePathSegments = explode('/', $basePath);

        preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match);
        $numLevelUp = strlen($match[0]) /3 + 1;
        if ($numLevelUp >= count($basePathSegments)) {
            throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
        }

        $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp);
        $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath);

        return implode('/', $basePathSegments) . '/' . $path;
    }

    /**
     * Normalizes a URI path component by removing dot-slash and double slashes
     *
     * @param string $path
     *
     * @return string
     */
    private static function normalizePath($path)
    {
        $path = preg_replace('|((?<!\.)\./)*|', '', $path);
        $path = preg_replace('|//|', '/', $path);

        return $path;
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Iterator;

/**
 * @package JsonSchema\Iterator
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class ObjectIterator implements \Iterator, \Countable
{
    /** @var object */
    private $object;

    /** @var int */
    private $position = 0;

    /** @var array */
    private $data = array();

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

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

    /**
     * {@inheritdoc}
     */
    public function current()
    {
        $this->initialize();

        return $this->data[$this->position];
    }

    /**
     * {@inheritdoc}
     */
    public function next()
    {
        $this->initialize();
        $this->position++;
    }

    /**
     * {@inheritdoc}
     */
    public function key()
    {
        $this->initialize();

        return $this->position;
    }

    /**
     * {@inheritdoc}
     */
    public function valid()
    {
        $this->initialize();

        return isset($this->data[$this->position]);
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        $this->initialize();
        $this->position = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function count()
    {
        $this->initialize();

        return count($this->data);
    }

    /**
     * Initializer
     */
    private function initialize()
    {
        if (!$this->initialized) {
            $this->data = $this->buildDataFromObject($this->object);
            $this->initialized = true;
        }
    }

    /**
     * @param object $object
     *
     * @return array
     */
    private function buildDataFromObject($object)
    {
        $result = array();

        $stack = new \SplStack();
        $stack->push($object);

        while (!$stack->isEmpty()) {
            $current = $stack->pop();
            if (is_object($current)) {
                array_push($result, $current);
            }

            foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) {
                if (is_object($propertyValue) || is_array($propertyValue)) {
                    $stack->push($propertyValue);
                }
            }
        }

        return $result;
    }

    /**
     * @param object|array $item
     *
     * @return array
     */
    private function getDataFromItem($item)
    {
        if (!is_object($item) && !is_array($item)) {
            return array();
        }

        return is_object($item) ? get_object_vars($item) : $item;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaException extends RuntimeException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSourceUriException
 */
class InvalidSourceUriException extends InvalidArgumentException
{
}
<?php

namespace JsonSchema\Exception;

interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the JsonDecodingException
 */
class JsonDecodingException extends RuntimeException
{
    public function __construct($code = JSON_ERROR_NONE, \Exception $previous = null)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $message = 'The maximum stack depth has been exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $message = 'Invalid or malformed JSON';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $message = 'Control character error, possibly incorrectly encoded';
                break;
            case JSON_ERROR_UTF8:
                $message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            case JSON_ERROR_SYNTAX:
                $message = 'JSON syntax is malformed';
                break;
            default:
                $message = 'Syntax error';
        }
        parent::__construct($message, $code, $previous);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * @package JsonSchema\Exception
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class UnresolvableJsonPointerException extends InvalidArgumentException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the RuntimeException
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

class ValidationException extends RuntimeException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the UriResolverException
 */
class UriResolverException extends RuntimeException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidArgumentException
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class InvalidConfigException extends RuntimeException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaMediaTypeException extends RuntimeException
{
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class ResourceNotFoundException extends RuntimeException
{
}
<?php

namespace JsonSchema;

interface SchemaStorageInterface
{
    /**
     * Adds schema with given identifier
     *
     * @param string $id
     * @param object $schema
     */
    public function addSchema($id, $schema = null);

    /**
     * Returns schema for given identifier, or null if it does not exist
     *
     * @param string $id
     *
     * @return object
     */
    public function getSchema($id);

    /**
     * Returns schema for given reference with all sub-references resolved
     *
     * @param string $ref
     *
     * @return object
     */
    public function resolveRef($ref);

    /**
     * Returns schema referenced by '$ref' property
     *
     * @param mixed $refSchema
     *
     * @return object
     */
    public function resolveRefSchema($refSchema);
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a URI
     *
     * @param string      $uri     JSON Schema URI
     * @param null|string $baseUri
     *
     * @return object JSON Schema contents
     */
    public function retrieve($uri, $baseUri = null);
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Uri\UriResolver;

/**
 * The UndefinedConstraint Constraints
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class UndefinedConstraint extends Constraint
{
    /**
     * @var array List of properties to which a default value has been applied
     */
    protected $appliedDefaults = array();

    /**
     * {@inheritdoc}
     */
    public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
    {
        if (is_null($schema) || !is_object($schema)) {
            return;
        }

        $path = $this->incrementPath($path ?: new JsonPointer(''), $i);
        if ($fromDefault) {
            $path->setFromDefault();
        }

        // check special properties
        $this->validateCommonProperties($value, $schema, $path, $i);

        // check allOf, anyOf, and oneOf properties
        $this->validateOfProperties($value, $schema, $path, '');

        // check known types
        $this->validateTypes($value, $schema, $path, $i);
    }

    /**
     * Validates the value against the types
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    public function validateTypes(&$value, $schema, JsonPointer $path, $i = null)
    {
        // check array
        if ($this->getTypeCheck()->isArray($value)) {
            $this->checkArray($value, $schema, $path, $i);
        }

        // check object
        if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays,
                                                // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
                                                // is not set (i.e. don't use $this->getTypeCheck() here).
            $this->checkObject(
                $value,
                $schema,
                $path,
                isset($schema->properties) ? $schema->properties : null,
                isset($schema->additionalProperties) ? $schema->additionalProperties : null,
                isset($schema->patternProperties) ? $schema->patternProperties : null,
                $this->appliedDefaults
            );
        }

        // check string
        if (is_string($value)) {
            $this->checkString($value, $schema, $path, $i);
        }

        // check numeric
        if (is_numeric($value)) {
            $this->checkNumber($value, $schema, $path, $i);
        }

        // check enum
        if (isset($schema->enum)) {
            $this->checkEnum($value, $schema, $path, $i);
        }
    }

    /**
     * Validates common properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // if it extends another schema, it must pass that schema as well
        if (isset($schema->extends)) {
            if (is_string($schema->extends)) {
                $schema->extends = $this->validateUri($schema, $schema->extends);
            }
            if (is_array($schema->extends)) {
                foreach ($schema->extends as $extends) {
                    $this->checkUndefined($value, $extends, $path, $i);
                }
            } else {
                $this->checkUndefined($value, $schema->extends, $path, $i);
            }
        }

        // Apply default values from schema
        if (!$path->fromDefault()) {
            $this->applyDefaultValues($value, $schema, $path);
        }

        // Verify required values
        if ($this->getTypeCheck()->isObject($value)) {
            if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
                // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
                foreach ($schema->required as $required) {
                    if (!$this->getTypeCheck()->propertyExists($value, $required)) {
                        $this->addError(
                            $this->incrementPath($path ?: new JsonPointer(''), $required),
                            'The property ' . $required . ' is required',
                            'required'
                        );
                    }
                }
            } elseif (isset($schema->required) && !is_array($schema->required)) {
                // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
                if ($schema->required && $value instanceof self) {
                    $propertyPaths = $path->getPropertyPaths();
                    $propertyName = end($propertyPaths);
                    $this->addError(
                        $path,
                        'The property ' . $propertyName . ' is required',
                        'required'
                    );
                }
            } else {
                // if the value is both undefined and not required, skip remaining checks
                // in this method which assume an actual, defined instance when validating.
                if ($value instanceof self) {
                    return;
                }
            }
        }

        // Verify type
        if (!($value instanceof self)) {
            $this->checkType($value, $schema, $path, $i);
        }

        // Verify disallowed items
        if (isset($schema->disallow)) {
            $initErrors = $this->getErrors();

            $typeSchema = new \stdClass();
            $typeSchema->type = $schema->disallow;
            $this->checkType($value, $typeSchema, $path);

            // if no new errors were raised it must be a disallowed value
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError($path, 'Disallowed value was matched', 'disallow');
            } else {
                $this->errors = $initErrors;
            }
        }

        if (isset($schema->not)) {
            $initErrors = $this->getErrors();
            $this->checkUndefined($value, $schema->not, $path, $i);

            // if no new errors were raised then the instance validated against the "not" schema
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError($path, 'Matched a schema which it should not', 'not');
            } else {
                $this->errors = $initErrors;
            }
        }

        // Verify that dependencies are met
        if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
            $this->validateDependencies($value, $schema->dependencies, $path);
        }
    }

    /**
     * Check whether a default should be applied for this value
     *
     * @param mixed $schema
     * @param mixed $parentSchema
     * @param bool  $requiredOnly
     *
     * @return bool
     */
    private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
    {
        // required-only mode is off
        if (!$requiredOnly) {
            return true;
        }
        // draft-04 required is set
        if (
            $name !== null
            && isset($parentSchema->required)
            && is_array($parentSchema->required)
            && in_array($name, $parentSchema->required)
        ) {
            return true;
        }
        // draft-03 required is set
        if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
            return true;
        }
        // default case
        return false;
    }

    /**
     * Apply default values
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     */
    protected function applyDefaultValues(&$value, $schema, $path)
    {
        // only apply defaults if feature is enabled
        if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
            return;
        }

        // apply defaults if appropriate
        $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
        if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
            // $value is an object or assoc array, and properties are defined - treat as an object
            foreach ($schema->properties as $currentProperty => $propertyDefinition) {
                $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
                if (
                    !LooseTypeCheck::propertyExists($value, $currentProperty)
                    && property_exists($propertyDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
                ) {
                    // assign default value
                    if (is_object($propertyDefinition->default)) {
                        LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
                    } else {
                        LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
                    }
                    $this->appliedDefaults[] = $currentProperty;
                }
            }
        } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
            $items = array();
            if (LooseTypeCheck::isArray($schema->items)) {
                $items = $schema->items;
            } elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
                $items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
            }
            // $value is an array, and items are defined - treat as plain array
            foreach ($items as $currentItem => $itemDefinition) {
                $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
                if (
                    !array_key_exists($currentItem, $value)
                    && property_exists($itemDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
                    if (is_object($itemDefinition->default)) {
                        $value[$currentItem] = clone $itemDefinition->default;
                    } else {
                        $value[$currentItem] = $itemDefinition->default;
                    }
                }
                $path->setFromDefault();
            }
        } elseif (
            $value instanceof self
            && property_exists($schema, 'default')
            && $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
            // $value is a leaf, not a container - apply the default directly
            $value = is_object($schema->default) ? clone $schema->default : $schema->default;
            $path->setFromDefault();
        }
    }

    /**
     * Validate allOf, anyOf, and oneOf properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // Verify type
        if ($value instanceof self) {
            return;
        }

        if (isset($schema->allOf)) {
            $isValid = true;
            foreach ($schema->allOf as $allOf) {
                $initErrors = $this->getErrors();
                $this->checkUndefined($value, $allOf, $path, $i);
                $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
            }
            if (!$isValid) {
                $this->addError($path, 'Failed to match all schemas', 'allOf');
            }
        }

        if (isset($schema->anyOf)) {
            $isValid = false;
            $startErrors = $this->getErrors();
            $caughtException = null;
            foreach ($schema->anyOf as $anyOf) {
                $initErrors = $this->getErrors();
                try {
                    $this->checkUndefined($value, $anyOf, $path, $i);
                    if ($isValid = (count($this->getErrors()) == count($initErrors))) {
                        break;
                    }
                } catch (ValidationException $e) {
                    $isValid = false;
                }
            }
            if (!$isValid) {
                $this->addError($path, 'Failed to match at least one schema', 'anyOf');
            } else {
                $this->errors = $startErrors;
            }
        }

        if (isset($schema->oneOf)) {
            $allErrors = array();
            $matchedSchemas = 0;
            $startErrors = $this->getErrors();
            foreach ($schema->oneOf as $oneOf) {
                try {
                    $this->errors = array();
                    $this->checkUndefined($value, $oneOf, $path, $i);
                    if (count($this->getErrors()) == 0) {
                        $matchedSchemas++;
                    }
                    $allErrors = array_merge($allErrors, array_values($this->getErrors()));
                } catch (ValidationException $e) {
                    // deliberately do nothing here - validation failed, but we want to check
                    // other schema options in the OneOf field.
                }
            }
            if ($matchedSchemas !== 1) {
                $this->addErrors(array_merge($allErrors, $startErrors));
                $this->addError($path, 'Failed to match exactly one schema', 'oneOf');
            } else {
                $this->errors = $startErrors;
            }
        }
    }

    /**
     * Validate dependencies
     *
     * @param mixed       $value
     * @param mixed       $dependencies
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
    {
        foreach ($dependencies as $key => $dependency) {
            if ($this->getTypeCheck()->propertyExists($value, $key)) {
                if (is_string($dependency)) {
                    // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
                    if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
                        $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
                    }
                } elseif (is_array($dependency)) {
                    // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
                    foreach ($dependency as $d) {
                        if (!$this->getTypeCheck()->propertyExists($value, $d)) {
                            $this->addError($path, "$key depends on $d and $d is missing", 'dependencies');
                        }
                    }
                } elseif (is_object($dependency)) {
                    // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
                    $this->checkUndefined($value, $dependency, $path, $i);
                }
            }
        }
    }

    protected function validateUri($schema, $schemaUri = null)
    {
        $resolver = new UriResolver();
        $retriever = $this->factory->getUriRetriever();

        $jsonSchema = null;
        if ($resolver->isValid($schemaUri)) {
            $schemaId = property_exists($schema, 'id') ? $schema->id : null;
            $jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
        }

        return $jsonSchema;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\SchemaStorage;
use JsonSchema\SchemaStorageInterface;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\UriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Factory for centralize constraint initialization.
 */
class Factory
{
    /**
     * @var SchemaStorage
     */
    protected $schemaStorage;

    /**
     * @var UriRetriever
     */
    protected $uriRetriever;

    /**
     * @var int
     */
    private $checkMode = Constraint::CHECK_MODE_NORMAL;

    /**
     * @var TypeCheck\TypeCheckInterface[]
     */
    private $typeCheck = array();

    /**
     * @var int Validation context
     */
    protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;

    /**
     * @var array
     */
    protected $constraintMap = array(
        'array' => 'JsonSchema\Constraints\CollectionConstraint',
        'collection' => 'JsonSchema\Constraints\CollectionConstraint',
        'object' => 'JsonSchema\Constraints\ObjectConstraint',
        'type' => 'JsonSchema\Constraints\TypeConstraint',
        'undefined' => 'JsonSchema\Constraints\UndefinedConstraint',
        'string' => 'JsonSchema\Constraints\StringConstraint',
        'number' => 'JsonSchema\Constraints\NumberConstraint',
        'enum' => 'JsonSchema\Constraints\EnumConstraint',
        'format' => 'JsonSchema\Constraints\FormatConstraint',
        'schema' => 'JsonSchema\Constraints\SchemaConstraint',
        'validator' => 'JsonSchema\Validator'
    );

    /**
     * @var array<ConstraintInterface>
     */
    private $instanceCache = array();

    /**
     * @param SchemaStorage         $schemaStorage
     * @param UriRetrieverInterface $uriRetriever
     * @param int                   $checkMode
     */
    public function __construct(
        SchemaStorageInterface $schemaStorage = null,
        UriRetrieverInterface $uriRetriever = null,
        $checkMode = Constraint::CHECK_MODE_NORMAL
    ) {
        // set provided config options
        $this->setConfig($checkMode);

        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever);
    }

    /**
     * Set config values
     *
     * @param int $checkMode Set checkMode options - does not preserve existing flags
     */
    public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL)
    {
        $this->checkMode = $checkMode;
    }

    /**
     * Enable checkMode flags
     *
     * @param int $options
     */
    public function addConfig($options)
    {
        $this->checkMode |= $options;
    }

    /**
     * Disable checkMode flags
     *
     * @param int $options
     */
    public function removeConfig($options)
    {
        $this->checkMode &= ~$options;
    }

    /**
     * Get checkMode option
     *
     * @param int $options Options to get, if null then return entire bitmask
     *
     * @return int
     */
    public function getConfig($options = null)
    {
        if ($options === null) {
            return $this->checkMode;
        }

        return $this->checkMode & $options;
    }

    /**
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        return $this->uriRetriever;
    }

    public function getSchemaStorage()
    {
        return $this->schemaStorage;
    }

    public function getTypeCheck()
    {
        if (!isset($this->typeCheck[$this->checkMode])) {
            $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST)
                ? new TypeCheck\LooseTypeCheck()
                : new TypeCheck\StrictTypeCheck();
        }

        return $this->typeCheck[$this->checkMode];
    }

    /**
     * @param string $name
     * @param string $class
     *
     * @return Factory
     */
    public function setConstraintClass($name, $class)
    {
        // Ensure class exists
        if (!class_exists($class)) {
            throw new InvalidArgumentException('Unknown constraint ' . $name);
        }
        // Ensure class is appropriate
        if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) {
            throw new InvalidArgumentException('Invalid class ' . $name);
        }
        $this->constraintMap[$name] = $class;

        return $this;
    }

    /**
     * Create a constraint instance for the given constraint name.
     *
     * @param string $constraintName
     *
     * @throws InvalidArgumentException if is not possible create the constraint instance
     *
     * @return ConstraintInterface|ObjectConstraint
     */
    public function createInstanceFor($constraintName)
    {
        if (!isset($this->constraintMap[$constraintName])) {
            throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
        }

        if (!isset($this->instanceCache[$constraintName])) {
            $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this);
        }

        return clone $this->instanceCache[$constraintName];
    }

    /**
     * Get the error context
     *
     * @return string
     */
    public function getErrorContext()
    {
        return $this->errorContext;
    }

    /**
     * Set the error context
     *
     * @param string $validationContext
     */
    public function setErrorContext($errorContext)
    {
        $this->errorContext = $errorContext;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The CollectionConstraint Constraints, validates an array against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class CollectionConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$value, $schema = null, JsonPointer $path = null, $i = null)
    {
        // Verify minItems
        if (isset($schema->minItems) && count($value) < $schema->minItems) {
            $this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems));
        }

        // Verify maxItems
        if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
            $this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems));
        }

        // Verify uniqueItems
        if (isset($schema->uniqueItems) && $schema->uniqueItems) {
            $unique = $value;
            if (is_array($value) && count($value)) {
                $unique = array_map(function ($e) {
                    return var_export($e, true);
                }, $value);
            }
            if (count(array_unique($unique)) != count($value)) {
                $this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems');
            }
        }

        // Verify items
        if (isset($schema->items)) {
            $this->validateItems($value, $schema, $path, $i);
        }
    }

    /**
     * Validates the items
     *
     * @param array            $value
     * @param \stdClass        $schema
     * @param JsonPointer|null $path
     * @param string           $i
     */
    protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
    {
        if (is_object($schema->items)) {
            // just one type definition for the whole array
            foreach ($value as $k => &$v) {
                $initErrors = $this->getErrors();

                // First check if its defined in "items"
                $this->checkUndefined($v, $schema->items, $path, $k);

                // Recheck with "additionalItems" if the first test fails
                if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
                    $secondErrors = $this->getErrors();
                    $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                }

                // Reset errors if needed
                if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
                    $this->errors = $secondErrors;
                } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
                    $this->errors = $initErrors;
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */
        } else {
            // Defined item type definitions
            foreach ($value as $k => &$v) {
                if (array_key_exists($k, $schema->items)) {
                    $this->checkUndefined($v, $schema->items[$k], $path, $k);
                } else {
                    // Additional items
                    if (property_exists($schema, 'additionalItems')) {
                        if ($schema->additionalItems !== false) {
                            $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                        } else {
                            $this->addError(
                                $path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems));
                        }
                    } else {
                        // Should be valid against an empty schema
                        $this->checkUndefined($v, new \stdClass(), $path, $k);
                    }
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */

            // Treat when we have more schema definitions than values, not for empty arrays
            if (count($value) > 0) {
                for ($k = count($value); $k < count($schema->items); $k++) {
                    $undefinedInstance = $this->factory->createInstanceFor('undefined');
                    $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
                }
            }
        }
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Validator;

/**
 * A more basic constraint definition - used for the public
 * interface to avoid exposing library internals.
 */
class BaseConstraint
{
    /**
     * @var array Errors
     */
    protected $errors = array();

    /**
     * @var int All error types which have occurred
     */
    protected $errorMask = Validator::ERROR_NONE;

    /**
     * @var Factory
     */
    protected $factory;

    /**
     * @param Factory $factory
     */
    public function __construct(Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
    }

    public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null)
    {
        $error = array(
            'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
            'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
            'message' => $message,
            'constraint' => $constraint,
            'context' => $this->factory->getErrorContext(),
        );

        if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
            throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
        }

        if (is_array($more) && count($more) > 0) {
            $error += $more;
        }

        $this->errors[] = $error;
        $this->errorMask |= $error['context'];
    }

    public function addErrors(array $errors)
    {
        if ($errors) {
            $this->errors = array_merge($this->errors, $errors);
            $errorMask = &$this->errorMask;
            array_walk($errors, function ($error) use (&$errorMask) {
                if (isset($error['context'])) {
                    $errorMask |= $error['context'];
                }
            });
        }
    }

    public function getErrors($errorContext = Validator::ERROR_ALL)
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return $this->errors;
        }

        return array_filter($this->errors, function ($error) use ($errorContext) {
            if ($errorContext & $error['context']) {
                return true;
            }
        });
    }

    public function numErrors($errorContext = Validator::ERROR_ALL)
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return count($this->errors);
        }

        return count($this->getErrors($errorContext));
    }

    public function isValid()
    {
        return !$this->getErrors();
    }

    /**
     * Clears any reported errors.  Should be used between
     * multiple validation checks.
     */
    public function reset()
    {
        $this->errors = array();
        $this->errorMask = Validator::ERROR_NONE;
    }

    /**
     * Get the error mask
     *
     * @return int
     */
    public function getErrorMask()
    {
        return $this->errorMask;
    }

    /**
     * Recursively cast an associative array to an object
     *
     * @param array $array
     *
     * @return object
     */
    public static function arrayToObjectRecursive($array)
    {
        $json = json_encode($array);
        if (json_last_error() !== \JSON_ERROR_NONE) {
            $message = 'Unable to encode schema array as JSON';
            if (function_exists('json_last_error_msg')) {
                $message .= ': ' . json_last_error_msg();
            }
            throw new InvalidArgumentException($message);
        }

        return (object) json_decode($json);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;

/**
 * Validates against the "format" property
 *
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 *
 * @see   http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
 */
class FormatConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
            return;
        }

        switch ($schema->format) {
            case 'date':
                if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
                    $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 'time':
                if (!$this->validateDateTime($element, 'H:i:s')) {
                    $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 'date-time':
                if (null === Rfc3339::createFromString($element)) {
                    $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 'utc-millisec':
                if (!$this->validateDateTime($element, 'U')) {
                    $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 'regex':
                if (!$this->validateRegex($element)) {
                    $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format));
                }
                break;

            case 'color':
                if (!$this->validateColor($element)) {
                    $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format));
                }
                break;

            case 'style':
                if (!$this->validateStyle($element)) {
                    $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format));
                }
                break;

            case 'phone':
                if (!$this->validatePhone($element)) {
                    $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format));
                }
                break;

            case 'uri':
                if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
                    $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
                    // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
                    // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.
                    // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information.
                    if (substr($element, 0, 2) === '//') { // network-path reference
                        $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
                    } elseif (substr($element, 0, 1) === '/') { // absolute-path reference
                        $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
                    } elseif (strlen($element)) { // relative-path reference
                        $pathParts = explode('/', $element, 2);
                        if (strpos($pathParts[0], ':') !== false) {
                            $validURL = null;
                        } else {
                            $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
                        }
                    } else {
                        $validURL = null;
                    }
                    if ($validURL === null) {
                        $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
                    }
                }
                break;

            case 'email':
                $filterFlags = FILTER_NULL_ON_FAILURE;
                if (defined('FILTER_FLAG_EMAIL_UNICODE')) {
                    // Only available from PHP >= 7.1.0, so ignore it for coverage checks
                    $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore
                }
                if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) {
                    $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format));
                }
                break;

            case 'ip-address':
            case 'ipv4':
                if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
                    $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
                }
                break;

            case 'ipv6':
                if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
                    $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
                }
                break;

            case 'host-name':
            case 'hostname':
                if (!$this->validateHostname($element)) {
                    $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format));
                }
                break;

            default:
                // Empty as it should be:
                // The value of this keyword is called a format attribute. It MUST be a string.
                // A format attribute can generally only validate a given set of instance types.
                // If the type of the instance to validate is not in this set, validation for
                // this format attribute and instance SHOULD succeed.
                // http://json-schema.org/latest/json-schema-validation.html#anchor105
                break;
        }
    }

    protected function validateDateTime($datetime, $format)
    {
        $dt = \DateTime::createFromFormat($format, $datetime);

        if (!$dt) {
            return false;
        }

        if ($datetime === $dt->format($format)) {
            return true;
        }

        // handles the case where a non-6 digit microsecond datetime is passed
        // which will fail the above string comparison because the passed
        // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return
        // '2000-05-01T12:12:12.123000Z'
        if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) {
            return true;
        }

        return false;
    }

    protected function validateRegex($regex)
    {
        return false !== @preg_match('/' . $regex . '/u', '');
    }

    protected function validateColor($color)
    {
        if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia',
            'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
            'red', 'silver', 'teal', 'white', 'yellow'))) {
            return true;
        }

        return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
    }

    protected function validateStyle($style)
    {
        $properties     = explode(';', rtrim($style, ';'));
        $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    protected function validatePhone($phone)
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
    }

    protected function validateHostname($host)
    {
        $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i';

        return preg_match($hostnameRegex, $host);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The Constraints Interface
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
interface ConstraintInterface
{
    /**
     * returns all collected errors
     *
     * @return array
     */
    public function getErrors();

    /**
     * adds errors to this validator
     *
     * @param array $errors
     */
    public function addErrors(array $errors);

    /**
     * adds an error
     *
     * @param JsonPointer|null $path
     * @param string           $message
     * @param string           $constraint the constraint/rule that is broken, e.g.: 'minLength'
     * @param array            $more       more array elements to add to the error
     */
    public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null);

    /**
     * checks if the validator has not raised errors
     *
     * @return bool
     */
    public function isValid();

    /**
     * invokes the validation of an element
     *
     * @abstract
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     *
     * @throws \JsonSchema\Exception\ExceptionInterface
     */
    public function check(&$value, $schema = null, JsonPointer $path = null, $i = null);
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The Base Constraints, all Validators should extend this class
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
    protected $inlineSchemaProperty = '$schema';

    const CHECK_MODE_NONE =             0x00000000;
    const CHECK_MODE_NORMAL =           0x00000001;
    const CHECK_MODE_TYPE_CAST =        0x00000002;
    const CHECK_MODE_COERCE_TYPES =     0x00000004;
    const CHECK_MODE_APPLY_DEFAULTS =   0x00000008;
    const CHECK_MODE_EXCEPTIONS =       0x00000010;
    const CHECK_MODE_DISABLE_FORMAT =   0x00000020;
    const CHECK_MODE_ONLY_REQUIRED_DEFAULTS   = 0x00000080;
    const CHECK_MODE_VALIDATE_SCHEMA =  0x00000100;

    /**
     * Bubble down the path
     *
     * @param JsonPointer|null $path Current path
     * @param mixed            $i    What to append to the path
     *
     * @return JsonPointer;
     */
    protected function incrementPath(JsonPointer $path = null, $i)
    {
        $path = $path ?: new JsonPointer('');

        if ($i === null || $i === '') {
            return $path;
        }

        $path = $path->withPropertyPaths(
            array_merge(
                $path->getPropertyPaths(),
                array($i)
            )
        );

        return $path;
    }

    /**
     * Validates an array
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('collection');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates an object
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $properties
     * @param mixed            $additionalProperties
     * @param mixed            $patternProperties
     */
    protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null,
        $additionalProperties = null, $patternProperties = null, $appliedDefaults = array())
    {
        $validator = $this->factory->createInstanceFor('object');
        $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates the type of a property
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('type');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a undefined element
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
    {
        $validator = $this->factory->createInstanceFor('undefined');

        $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a string element
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('string');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a number element
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param mixed       $i
     */
    protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('number');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a enum element
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('enum');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks format of an element
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null)
    {
        $validator = $this->factory->createInstanceFor('format');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Get the type check based on the set check mode.
     *
     * @return TypeCheck\TypeCheckInterface
     */
    protected function getTypeCheck()
    {
        return $this->factory->getTypeCheck();
    }

    /**
     * @param JsonPointer $pointer
     *
     * @return string property path
     */
    protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer)
    {
        $result = array_map(
            function ($path) {
                return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
            },
            $pointer->getPropertyPaths()
        );

        return trim(implode('', $result), '.');
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The ObjectConstraint Constraints, validates an object against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class ObjectConstraint extends Constraint
{
    /**
     * @var array List of properties to which a default value has been applied
     */
    protected $appliedDefaults = array();

    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null,
        $additionalProp = null, $patternProperties = null, $appliedDefaults = array())
    {
        if ($element instanceof UndefinedConstraint) {
            return;
        }

        $this->appliedDefaults = $appliedDefaults;

        $matches = array();
        if ($patternProperties) {
            // validate the element pattern properties
            $matches = $this->validatePatternProperties($element, $path, $patternProperties);
        }

        if ($properties) {
            // validate the element properties
            $this->validateProperties($element, $properties, $path);
        }

        // validate additional element properties & constraints
        $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
    }

    public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties)
    {
        $try = array('/', '#', '+', '~', '%');
        $matches = array();
        foreach ($patternProperties as $pregex => $schema) {
            $delimiter = '/';
            // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error
            foreach ($try as $delimiter) {
                if (strpos($pregex, $delimiter) === false) { // safe to use
                    break;
                }
            }

            // Validate the pattern before using it to test for matches
            if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) {
                $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex));
                continue;
            }
            foreach ($element as $i => $value) {
                if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) {
                    $matches[] = $i;
                    $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults));
                }
            }
        }

        return $matches;
    }

    /**
     * Validates the element properties
     *
     * @param \StdClass        $element        Element to validate
     * @param array            $matches        Matches from patternProperties (if any)
     * @param \StdClass        $schema         ObjectConstraint definition
     * @param JsonPointer|null $path           Current test path
     * @param \StdClass        $properties     Properties
     * @param mixed            $additionalProp Additional properties
     */
    public function validateElement($element, $matches, $schema = null, JsonPointer $path = null,
        $properties = null, $additionalProp = null)
    {
        $this->validateMinMaxConstraint($element, $schema, $path);

        foreach ($element as $i => $value) {
            $definition = $this->getProperty($properties, $i);

            // no additional properties allowed
            if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
                $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp');
            }

            // additional properties defined
            if (!in_array($i, $matches) && $additionalProp && !$definition) {
                if ($additionalProp === true) {
                    $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults));
                } else {
                    $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
                }
            }

            // property requires presence of another
            $require = $this->getProperty($definition, 'requires');
            if ($require && !$this->getProperty($element, $require)) {
                $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires');
            }

            $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined'));
            if (is_object($property)) {
                $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path);
            }
        }
    }

    /**
     * Validates the definition properties
     *
     * @param \stdClass        $element    Element to validate
     * @param \stdClass        $properties Property definitions
     * @param JsonPointer|null $path       Path?
     */
    public function validateProperties(&$element, $properties = null, JsonPointer $path = null)
    {
        $undefinedConstraint = $this->factory->createInstanceFor('undefined');

        foreach ($properties as $i => $value) {
            $property = &$this->getProperty($element, $i, $undefinedConstraint);
            $definition = $this->getProperty($properties, $i);

            if (is_object($definition)) {
                // Undefined constraint will check for is_object() and quit if is not - so why pass it?
                $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
            }
        }
    }

    /**
     * retrieves a property from an object or array
     *
     * @param mixed  $element  Element to validate
     * @param string $property Property to retrieve
     * @param mixed  $fallback Default value if property is not found
     *
     * @return mixed
     */
    protected function &getProperty(&$element, $property, $fallback = null)
    {
        if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
            return $element[$property];
        } elseif (is_object($element) && property_exists($element, $property)) {
            return $element->$property;
        }

        return $fallback;
    }

    /**
     * validating minimum and maximum property constraints (if present) against an element
     *
     * @param \stdClass        $element          Element to validate
     * @param \stdClass        $objectDefinition ObjectConstraint definition
     * @param JsonPointer|null $path             Path to test?
     */
    protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null)
    {
        // Verify minimum number of properties
        if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) {
                $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties));
            }
        }
        // Verify maximum number of properties
        if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) {
                $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties));
            }
        }
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\InvalidSchemaException;
use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * The SchemaConstraint Constraints, validates an element against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class SchemaConstraint extends Constraint
{
    const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';

    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        if ($schema !== null) {
            // passed schema
            $validationSchema = $schema;
        } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
            // inline schema
            $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
        } else {
            throw new InvalidArgumentException('no schema found to verify against');
        }

        // cast array schemas to object
        if (is_array($validationSchema)) {
            $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
        }

        // validate schema against whatever is defined in $validationSchema->$schema. If no
        // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
        if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {
            if (!$this->getTypeCheck()->isObject($validationSchema)) {
                throw new RuntimeException('Cannot validate the schema of a non-object');
            }
            if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) {
                $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema');
            } else {
                $schemaSpec = self::DEFAULT_SCHEMA_SPEC;
            }

            // get the spec schema
            $schemaStorage = $this->factory->getSchemaStorage();
            if (!$this->getTypeCheck()->isObject($schemaSpec)) {
                $schemaSpec = $schemaStorage->getSchema($schemaSpec);
            }

            // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA
            $initialErrorCount = $this->numErrors();
            $initialConfig = $this->factory->getConfig();
            $initialContext = $this->factory->getErrorContext();
            $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS);
            $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST);
            $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION);

            // validate schema
            try {
                $this->check($validationSchema, $schemaSpec);
            } catch (\Exception $e) {
                if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) {
                    throw new InvalidSchemaException('Schema did not pass validation', 0, $e);
                }
            }
            if ($this->numErrors() > $initialErrorCount) {
                $this->addError($path, 'Schema is not valid', 'schema');
            }

            // restore the initial config
            $this->factory->setConfig($initialConfig);
            $this->factory->setErrorContext($initialContext);
        }

        // validate element against $validationSchema
        $this->checkUndefined($element, $validationSchema, $path, $i);
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use UnexpectedValueException as StandardUnexpectedValueException;

/**
 * The TypeConstraint Constraints, validates an element against a given type
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class TypeConstraint extends Constraint
{
    /**
     * @var array|string[] type wordings for validation error messages
     */
    public static $wording = array(
        'integer' => 'an integer',
        'number'  => 'a number',
        'boolean' => 'a boolean',
        'object'  => 'an object',
        'array'   => 'an array',
        'string'  => 'a string',
        'null'    => 'a null',
        'any'     => null, // validation of 'any' is always true so is not needed in message wording
        0         => null, // validation of a false-y value is always true, so not needed as well
    );

    /**
     * {@inheritdoc}
     */
    public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null)
    {
        $type = isset($schema->type) ? $schema->type : null;
        $isValid = false;
        $wording = array();

        if (is_array($type)) {
            $this->validateTypesArray($value, $type, $wording, $isValid, $path);
        } elseif (is_object($type)) {
            $this->checkUndefined($value, $type, $path);

            return;
        } else {
            $isValid = $this->validateType($value, $type);
        }

        if ($isValid === false) {
            if (!is_array($type)) {
                $this->validateTypeNameWording($type);
                $wording[] = self::$wording[$type];
            }
            $this->addError($path, ucwords(gettype($value)) . ' value found, but ' .
                $this->implodeWith($wording, ', ', 'or') . ' is required', 'type');
        }
    }

    /**
     * Validates the given $value against the array of types in $type. Sets the value
     * of $isValid to true, if at least one $type mateches the type of $value or the value
     * passed as $isValid is already true.
     *
     * @param mixed $value             Value to validate
     * @param array $type              TypeConstraints to check agains
     * @param array $validTypesWording An array of wordings of the valid types of the array $type
     * @param bool  $isValid           The current validation value
     * @param $path
     */
    protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path)
    {
        foreach ($type as $tp) {
            // $tp can be an object, if it's a schema instead of a simple type, validate it
            // with a new type constraint
            if (is_object($tp)) {
                if (!$isValid) {
                    $validator = $this->factory->createInstanceFor('type');
                    $subSchema = new \stdClass();
                    $subSchema->type = $tp;
                    $validator->check($value, $subSchema, $path, null);
                    $error = $validator->getErrors();
                    $isValid = !(bool) $error;
                    $validTypesWording[] = self::$wording['object'];
                }
            } else {
                $this->validateTypeNameWording($tp);
                $validTypesWording[] = self::$wording[$tp];
                if (!$isValid) {
                    $isValid = $this->validateType($value, $tp);
                }
            }
        }
    }

    /**
     * Implodes the given array like implode() with turned around parameters and with the
     * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
     * $delimiter.
     *
     * @param array  $elements  The elements to implode
     * @param string $delimiter The delimiter to use
     * @param bool   $listEnd   The last delimiter to use (defaults to $delimiter)
     *
     * @return string
     */
    protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
    {
        if ($listEnd === false || !isset($elements[1])) {
            return implode($delimiter, $elements);
        }
        $lastElement  = array_slice($elements, -1);
        $firsElements = join($delimiter, array_slice($elements, 0, -1));
        $implodedElements = array_merge(array($firsElements), $lastElement);

        return join(" $listEnd ", $implodedElements);
    }

    /**
     * Validates the given $type, if there's an associated self::$wording. If not, throws an
     * exception.
     *
     * @param string $type The type to validate
     *
     * @throws StandardUnexpectedValueException
     */
    protected function validateTypeNameWording($type)
    {
        if (!isset(self::$wording[$type])) {
            throw new StandardUnexpectedValueException(
                sprintf(
                    'No wording for %s available, expected wordings are: [%s]',
                    var_export($type, true),
                    implode(', ', array_filter(self::$wording)))
            );
        }
    }

    /**
     * Verifies that a given value is of a certain type
     *
     * @param mixed  $value Value to validate
     * @param string $type  TypeConstraint to check against
     *
     * @throws InvalidArgumentException
     *
     * @return bool
     */
    protected function validateType(&$value, $type)
    {
        //mostly the case for inline schema
        if (!$type) {
            return true;
        }

        if ('any' === $type) {
            return true;
        }

        if ('object' === $type) {
            return $this->getTypeCheck()->isObject($value);
        }

        if ('array' === $type) {
            return $this->getTypeCheck()->isArray($value);
        }

        $coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES);

        if ('integer' === $type) {
            if ($coerce) {
                $value = $this->toInteger($value);
            }

            return is_int($value);
        }

        if ('number' === $type) {
            if ($coerce) {
                $value = $this->toNumber($value);
            }

            return is_numeric($value) && !is_string($value);
        }

        if ('boolean' === $type) {
            if ($coerce) {
                $value = $this->toBoolean($value);
            }

            return is_bool($value);
        }

        if ('string' === $type) {
            return is_string($value);
        }

        if ('email' === $type) {
            return is_string($value);
        }

        if ('null' === $type) {
            return is_null($value);
        }

        throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
    }

    /**
     * Converts a value to boolean. For example, "true" becomes true.
     *
     * @param $value The value to convert to boolean
     *
     * @return bool|mixed
     */
    protected function toBoolean($value)
    {
        if ($value === 'true') {
            return true;
        }

        if ($value === 'false') {
            return false;
        }

        return $value;
    }

    /**
     * Converts a numeric string to a number. For example, "4" becomes 4.
     *
     * @param mixed $value the value to convert to a number
     *
     * @return int|float|mixed
     */
    protected function toNumber($value)
    {
        if (is_numeric($value)) {
            return $value + 0; // cast to number
        }

        return $value;
    }

    protected function toInteger($value)
    {
        if (is_numeric($value) && (int) $value == $value) {
            return (int) $value; // cast to number
        }

        return $value;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The NumberConstraint Constraints, validates an number against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class NumberConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // Verify minimum
        if (isset($schema->exclusiveMinimum)) {
            if (isset($schema->minimum)) {
                if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
                    $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum));
                } elseif ($element < $schema->minimum) {
                    $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
                }
            } else {
                $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum');
            }
        } elseif (isset($schema->minimum) && $element < $schema->minimum) {
            $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
        }

        // Verify maximum
        if (isset($schema->exclusiveMaximum)) {
            if (isset($schema->maximum)) {
                if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
                    $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum));
                } elseif ($element > $schema->maximum) {
                    $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
                }
            } else {
                $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum');
            }
        } elseif (isset($schema->maximum) && $element > $schema->maximum) {
            $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
        }

        // Verify divisibleBy - Draft v3
        if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
            $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy));
        }

        // Verify multipleOf - Draft v4
        if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
            $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf));
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function fmod($number1, $number2)
    {
        $modulus = ($number1 - round($number1 / $number2) * $number2);
        $precision = 0.0000000001;

        if (-$precision < $modulus && $modulus < $precision) {
            return 0.0;
        }

        return $modulus;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The StringConstraint Constraints, validates an string against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class StringConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // Verify maxLength
        if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
            $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array(
                'maxLength' => $schema->maxLength,
            ));
        }

        //verify minLength
        if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
            $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array(
                'minLength' => $schema->minLength,
            ));
        }

        // Verify a regex pattern
        if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) {
            $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array(
                'pattern' => $schema->pattern,
            ));
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function strlen($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, mb_detect_encoding($string));
        }

        // mbstring is present on all test platforms, so strlen() can be ignored for coverage
        return strlen($string); // @codeCoverageIgnore
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

/**
 * The EnumConstraint Constraints, validates an element against a given set of possibilities
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class EnumConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // Only validate enum if the attribute exists
        if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
            return;
        }
        $type = gettype($element);

        foreach ($schema->enum as $enum) {
            $enumType = gettype($enum);
            if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') {
                if ((object) $element == $enum) {
                    return;
                }
            }

            if ($type === gettype($enum)) {
                if ($type == 'object') {
                    if ($element == $enum) {
                        return;
                    }
                } elseif ($element === $enum) {
                    return;
                }
            }
        }

        $this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum));
    }
}
<?php

namespace JsonSchema\Constraints\TypeCheck;

class StrictTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return is_object($value);
    }

    public static function isArray($value)
    {
        return is_array($value);
    }

    public static function propertyGet($value, $property)
    {
        return $value->{$property};
    }

    public static function propertySet(&$value, $property, $data)
    {
        $value->{$property} = $data;
    }

    public static function propertyExists($value, $property)
    {
        return property_exists($value, $property);
    }

    public static function propertyCount($value)
    {
        if (!is_object($value)) {
            return 0;
        }

        return count(get_object_vars($value));
    }
}
<?php

namespace JsonSchema\Constraints\TypeCheck;

class LooseTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return
            is_object($value) ||
            (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
    }

    public static function isArray($value)
    {
        return
            is_array($value) &&
            (count($value) == 0 || !self::isAssociativeArray($value));
    }

    public static function propertyGet($value, $property)
    {
        if (is_object($value)) {
            return $value->{$property};
        }

        return $value[$property];
    }

    public static function propertySet(&$value, $property, $data)
    {
        if (is_object($value)) {
            $value->{$property} = $data;
        } else {
            $value[$property] = $data;
        }
    }

    public static function propertyExists($value, $property)
    {
        if (is_object($value)) {
            return property_exists($value, $property);
        }

        return array_key_exists($property, $value);
    }

    public static function propertyCount($value)
    {
        if (is_object($value)) {
            return count(get_object_vars($value));
        }

        return count($value);
    }

    /**
     * Check if the provided array is associative or not
     *
     * @param array $arr
     *
     * @return bool
     */
    private static function isAssociativeArray($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}
<?php

namespace JsonSchema\Constraints\TypeCheck;

interface TypeCheckInterface
{
    public static function isObject($value);

    public static function isArray($value);

    public static function propertyGet($value, $property);

    public static function propertySet(&$value, $property, $data);

    public static function propertyExists($value, $property);

    public static function propertyCount($value);
}
<?php

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\UnresolvableJsonPointerException;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Uri\UriRetriever;

class SchemaStorage implements SchemaStorageInterface
{
    const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/';

    protected $uriRetriever;
    protected $uriResolver;
    protected $schemas = array();

    public function __construct(
        UriRetrieverInterface $uriRetriever = null,
        UriResolverInterface $uriResolver = null
    ) {
        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->uriResolver = $uriResolver ?: new UriResolver();
    }

    /**
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        return $this->uriRetriever;
    }

    /**
     * @return UriResolverInterface
     */
    public function getUriResolver()
    {
        return $this->uriResolver;
    }

    /**
     * {@inheritdoc}
     */
    public function addSchema($id, $schema = null)
    {
        if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
            // if the schema was user-provided to Validator and is still null, then assume this is
            // what the user intended, as there's no way for us to retrieve anything else. User-supplied
            // schemas do not have an associated URI when passed via Validator::validate().
            $schema = $this->uriRetriever->retrieve($id);
        }

        // cast array schemas to object
        if (is_array($schema)) {
            $schema = BaseConstraint::arrayToObjectRecursive($schema);
        }

        // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
        // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
        if (is_object($schema) && property_exists($schema, 'id')) {
            if ($schema->id == 'http://json-schema.org/draft-04/schema#') {
                $schema->properties->id->format = 'uri-reference';
            } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') {
                $schema->properties->id->format = 'uri-reference';
                $schema->properties->{'$ref'}->format = 'uri-reference';
            }
        }

        // resolve references
        $this->expandRefs($schema, $id);

        $this->schemas[$id] = $schema;
    }

    /**
     * Recursively resolve all references against the provided base
     *
     * @param mixed  $schema
     * @param string $base
     */
    private function expandRefs(&$schema, $base = null)
    {
        if (!is_object($schema)) {
            if (is_array($schema)) {
                foreach ($schema as &$member) {
                    $this->expandRefs($member, $base);
                }
            }

            return;
        }

        if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) {
            $base = $this->uriResolver->resolve($schema->id, $base);
        }

        if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
            $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base));
            $schema->{'$ref'} = (string) $refPointer;
        }

        foreach ($schema as &$member) {
            $this->expandRefs($member, $base);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSchema($id)
    {
        if (!array_key_exists($id, $this->schemas)) {
            $this->addSchema($id);
        }

        return $this->schemas[$id];
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRef($ref)
    {
        $jsonPointer = new JsonPointer($ref);

        // resolve filename for pointer
        $fileName = $jsonPointer->getFilename();
        if (!strlen($fileName)) {
            throw new UnresolvableJsonPointerException(sprintf(
                "Could not resolve fragment '%s': no file is defined",
                $jsonPointer->getPropertyPathAsString()
            ));
        }

        // get & process the schema
        $refSchema = $this->getSchema($fileName);
        foreach ($jsonPointer->getPropertyPaths() as $path) {
            if (is_object($refSchema) && property_exists($refSchema, $path)) {
                $refSchema = $this->resolveRefSchema($refSchema->{$path});
            } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
                $refSchema = $this->resolveRefSchema($refSchema[$path]);
            } else {
                throw new UnresolvableJsonPointerException(sprintf(
                    'File: %s is found, but could not resolve fragment: %s',
                    $jsonPointer->getFilename(),
                    $jsonPointer->getPropertyPathAsString()
                ));
            }
        }

        return $refSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRefSchema($refSchema)
    {
        if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
            $newSchema = $this->resolveRef($refSchema->{'$ref'});
            $refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema));
            unset($refSchema->{'$ref'});
        }

        return $refSchema;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriResolverInterface
{
    /**
     * Resolves a URI
     *
     * @param string      $uri     Absolute or relative
     * @param null|string $baseUri Optional base URI
     *
     * @return string Absolute URI
     */
    public function resolve($uri, $baseUri = null);
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Entity;

use JsonSchema\Exception\InvalidArgumentException;

/**
 * @package JsonSchema\Entity
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class JsonPointer
{
    /** @var string */
    private $filename;

    /** @var string[] */
    private $propertyPaths = array();

    /**
     * @var bool Whether the value at this path was set from a schema default
     */
    private $fromDefault = false;

    /**
     * @param string $value
     *
     * @throws InvalidArgumentException when $value is not a string
     */
    public function __construct($value)
    {
        if (!is_string($value)) {
            throw new InvalidArgumentException('Ref value must be a string');
        }

        $splitRef = explode('#', $value, 2);
        $this->filename = $splitRef[0];
        if (array_key_exists(1, $splitRef)) {
            $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
        }
    }

    /**
     * @param string $propertyPathString
     *
     * @return string[]
     */
    private function decodePropertyPaths($propertyPathString)
    {
        $paths = array();
        foreach (explode('/', trim($propertyPathString, '/')) as $path) {
            $path = $this->decodePath($path);
            if (is_string($path) && '' !== $path) {
                $paths[] = $path;
            }
        }

        return $paths;
    }

    /**
     * @return array
     */
    private function encodePropertyPaths()
    {
        return array_map(
            array($this, 'encodePath'),
            $this->getPropertyPaths()
        );
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function decodePath($path)
    {
        return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%'));
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function encodePath($path)
    {
        return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25'));
    }

    /**
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * @return string[]
     */
    public function getPropertyPaths()
    {
        return $this->propertyPaths;
    }

    /**
     * @param array $propertyPaths
     *
     * @return JsonPointer
     */
    public function withPropertyPaths(array $propertyPaths)
    {
        $new = clone $this;
        $new->propertyPaths = $propertyPaths;

        return $new;
    }

    /**
     * @return string
     */
    public function getPropertyPathAsString()
    {
        return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/');
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getFilename() . $this->getPropertyPathAsString();
    }

    /**
     * Mark the value at this path as being set from a schema default
     */
    public function setFromDefault()
    {
        $this->fromDefault = true;
    }

    /**
     * Check whether the value at this path was set from a schema default
     *
     * @return bool
     */
    public function fromDefault()
    {
        return $this->fromDefault;
    }
}
<?php

namespace JsonSchema;

class Rfc3339
{
    const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';

    /**
     * Try creating a DateTime instance
     *
     * @param string $string
     *
     * @return \DateTime|null
     */
    public static function createFromString($string)
    {
        if (!preg_match(self::REGEX, strtoupper($string), $matches)) {
            return null;
        }

        $dateAndTime = $matches[1];
        $microseconds = $matches[2] ?: '.000000';
        $timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
        $dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
        $dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));

        return $dateTime ?: null;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Constraints\Constraint;

/**
 * A JsonSchema Constraint
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 *
 * @see    README.md
 */
class Validator extends BaseConstraint
{
    const SCHEMA_MEDIA_TYPE = 'application/schema+json';

    const ERROR_NONE                    = 0x00000000;
    const ERROR_ALL                     = 0xFFFFFFFF;
    const ERROR_DOCUMENT_VALIDATION     = 0x00000001;
    const ERROR_SCHEMA_VALIDATION       = 0x00000002;

    /**
     * Validates the given data against the schema and returns an object containing the results
     * Both the php object and the schema are supposed to be a result of a json_decode call.
     * The validation works as defined by the schema proposal in http://json-schema.org.
     *
     * Note that the first argument is passed by reference, so you must pass in a variable.
     */
    public function validate(&$value, $schema = null, $checkMode = null)
    {
        // make sure $schema is an object
        if (is_array($schema)) {
            $schema = self::arrayToObjectRecursive($schema);
        }

        // set checkMode
        $initialCheckMode = $this->factory->getConfig();
        if ($checkMode !== null) {
            $this->factory->setConfig($checkMode);
        }

        // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
        if (is_object($schema) && property_exists($schema, 'id')) {
            $schemaURI = $schema->id;
        } else {
            $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
        }
        $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);

        $validator = $this->factory->createInstanceFor('schema');
        $validator->check(
            $value,
            $this->factory->getSchemaStorage()->getSchema($schemaURI)
        );

        $this->factory->setConfig($initialCheckMode);

        $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));

        return $validator->getErrorMask();
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     */
    public function check($value, $schema)
    {
        return $this->validate($value, $schema);
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     */
    public function coerce(&$value, $schema)
    {
        return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
    }
}
<?php

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

namespace Seld\PharUtils;

class Linter
{
    /**
     * Lints all php files inside a given phar with the current PHP version
     *
     * @param string $path Phar file path
     * @param list<string> $excludedPaths Paths which should be skipped by the linter
     */
    public static function lint($path, array $excludedPaths = array())
    {
        $php = defined('PHP_BINARY') ? PHP_BINARY : 'php';

        if ($isWindows = defined('PHP_WINDOWS_VERSION_BUILD')) {
            $tmpFile = @tempnam(sys_get_temp_dir(), '');

            if (!$tmpFile || !is_writable($tmpFile)) {
                throw new \RuntimeException('Unable to create temp file');
            }

            $php = self::escapeWindowsPath($php);
            $tmpFile = self::escapeWindowsPath($tmpFile);

            // PHP 8 encloses the command in double-quotes
            if (PHP_VERSION_ID >= 80000) {
                $format = '%s -l %s';
            } else {
                $format = '"%s -l %s"';
            }

            $command = sprintf($format, $php, $tmpFile);
        } else {
            $command = "'".$php."' -l";
        }

        $descriptorspec = array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w')
        );

        // path to phar + phar:// + trailing slash
        $baseLen = strlen(realpath($path)) + 7 + 1;
        foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) {
            if ($file->isDir()) {
                continue;
            }
            if (substr($file, -4) === '.php') {
                $filename = (string) $file;
                if (in_array(substr($filename, $baseLen), $excludedPaths, true)) {
                    continue;
                }
                if ($isWindows) {
                    file_put_contents($tmpFile, file_get_contents($filename));
                }

                $process = proc_open($command, $descriptorspec, $pipes);
                if (is_resource($process)) {
                    if (!$isWindows) {
                        fwrite($pipes[0], file_get_contents($filename));
                    }
                    fclose($pipes[0]);

                    $stdout = stream_get_contents($pipes[1]);
                    fclose($pipes[1]);
                    $stderr = stream_get_contents($pipes[2]);
                    fclose($pipes[2]);

                    $exitCode = proc_close($process);

                    if ($exitCode !== 0) {
                        if ($isWindows) {
                            $stderr = str_replace($tmpFile, $filename, $stderr);
                        }
                        throw new \UnexpectedValueException('Failed linting '.$file.': '.$stderr);
                    }
                } else {
                    throw new \RuntimeException('Could not start linter process');
                }
            }
        }

        if ($isWindows) {
            @unlink($tmpFile);
        }
    }

    /**
     * Escapes a Windows file path
     *
     * @param string $path
     * @return string The escaped path
     */
    private static function escapeWindowsPath($path)
    {
        // Quote if path contains spaces or brackets
        if (strpbrk($path, " ()") !== false) {
            $path = '"'.$path.'"';
        }

        return $path;
    }
}
<?php

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

namespace Seld\PharUtils;

class Timestamps
{
    private $contents;

    /**
     * @param string $file path to the phar file to use
     */
    public function __construct($file)
    {
        $this->contents = file_get_contents($file);
    }

    /**
     * Updates each file's unix timestamps in the PHAR
     *
     * The PHAR signature can then be produced in a reproducible manner.
     *
     * @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use
     */
    public function updateTimestamps($timestamp = null)
    {
        if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) {
            $timestamp = $timestamp->getTimestamp();
        } elseif (is_string($timestamp)) {
            $timestamp = strtotime($timestamp);
        } elseif (!is_int($timestamp)) {
            $timestamp = strtotime('1984-12-24T00:00:00Z');
        }

        // detect manifest offset / end of stub
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $stubEnd = $pos + $this->readUint($pos, 4);
        $pos += 4;

        $numFiles = $this->readUint($pos, 4);
        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        while ($pos < $stubEnd) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize
            $pos += 4;

            // update timestamp to a fixed value
            $this->contents = substr_replace($this->contents, pack('L', $timestamp), $pos, 4);

            // skip timestamp, compressed file size, crc32 checksum and file flags
            $pos += 4*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }
    }

    /**
     * Saves the updated phar file, optionally with an updated signature.
     *
     * @param  string $path
     * @param  int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
     * @return bool
     */
    public function save($path, $signatureAlgo)
    {
        $pos = $this->determineSignatureBegin();

        $algos = array(
            \Phar::MD5 => 'md5',
            \Phar::SHA1 => 'sha1',
            \Phar::SHA256 => 'sha256',
            \Phar::SHA512 => 'sha512',
        );

        if (!isset($algos[$signatureAlgo])) {
            throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512');
        }
        $algo = $algos[$signatureAlgo];

        // re-sign phar
        //           signature
        $signature = hash($algo, substr($this->contents, 0, $pos), true)
            // sig type
            . pack('L', $signatureAlgo)
            // ohai Greg & Marcus
            . 'GBMB';

        $this->contents = substr($this->contents, 0, $pos) . $signature;

        return file_put_contents($path, $this->contents);
    }

    private function readUint($pos, $bytes)
    {
        $res = unpack('V', substr($this->contents, $pos, $bytes));

        return $res[1];
    }

    /**
     * Determine the beginning of the signature.
     *
     * @return int
     */
    private function determineSignatureBegin()
    {
        // detect signature position
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $manifestEnd = $pos + 4 + $this->readUint($pos, 4);

        $pos += 4;
        $numFiles = $this->readUint($pos, 4);

        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        $compressedSizes = 0;
        while (($numFiles > 0) && ($pos < $manifestEnd - 24)) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize and timestamp
            $pos += 2*4;

            $compressedSizes += $this->readUint($pos, 4);
            // skip compressed file size, crc32 checksum and file flags
            $pos += 3*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }

        return $manifestEnd + $compressedSizes;
    }
}
<?php

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

namespace Seld\JsonLint;

/**
 * Lexer class
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class Lexer
{
    private $EOF = 1;
    /**
     * @phpstan-var array<int, string>
     */
    private $rules = array(
        0 => '/\G\s+/',
        1 => '/\G-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
        2 => '{\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}',
        3 => '/\G\{/',
        4 => '/\G\}/',
        5 => '/\G\[/',
        6 => '/\G\]/',
        7 => '/\G,/',
        8 => '/\G:/',
        9 => '/\Gtrue\b/',
        10 => '/\Gfalse\b/',
        11 => '/\Gnull\b/',
        12 => '/\G$/',
        13 => '/\G./',
    );

    private $conditions = array(
        "INITIAL" => array(
            "rules" => array(0,1,2,3,4,5,6,7,8,9,10,11,12,13),
            "inclusive" => true,
        ),
    );

    private $conditionStack;
    private $input;
    private $more;
    private $done;
    private $offset;

    public $match;
    public $yylineno;
    public $yyleng;
    public $yytext;
    public $yylloc;

    public function lex()
    {
        $r = $this->next();
        if (!$r instanceof Undefined) {
            return $r;
        }

        return $this->lex();
    }

    public function setInput($input)
    {
        $this->input = $input;
        $this->more = false;
        $this->done = false;
        $this->offset = 0;
        $this->yylineno = $this->yyleng = 0;
        $this->yytext = $this->match = '';
        $this->conditionStack = array('INITIAL');
        $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0);

        return $this;
    }

    public function showPosition()
    {
        $pre = str_replace("\n", '', $this->getPastInput());
        $c = str_repeat('-', max(0, \strlen($pre) - 1)); // new Array(pre.length + 1).join("-");

        return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^";
    }

    public function getPastInput()
    {
        $pastLength = $this->offset - \strlen($this->match);

        return ($pastLength > 20 ? '...' : '') . substr($this->input, max(0, $pastLength - 20), min(20, $pastLength));
    }

    public function getUpcomingInput()
    {
        $next = $this->match;
        if (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return substr($next, 0, 20) . (\strlen($next) > 20 ? '...' : '');
    }

    public function getFullUpcomingInput()
    {
        $next = $this->match;
        if (substr($next, 0, 1) === '"' && substr_count($next, '"') === 1) {
            $len = \strlen($this->input);
            $strEnd = min(strpos($this->input, '"', $this->offset + 1) ?: $len, strpos($this->input, "\n", $this->offset + 1) ?: $len);
            $next .= substr($this->input, $this->offset, $strEnd - $this->offset);
        } elseif (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return $next;
    }

    protected function parseError($str, $hash)
    {
        throw new \Exception($str);
    }

    private function next()
    {
        if ($this->done) {
            return $this->EOF;
        }
        if ($this->offset === \strlen($this->input)) {
            $this->done = true;
        }

        $token = null;
        $match = null;
        $col = null;
        $lines = null;

        if (!$this->more) {
            $this->yytext = '';
            $this->match = '';
        }

        $rules = $this->getCurrentRules();
        $rulesLen = \count($rules);

        for ($i=0; $i < $rulesLen; $i++) {
            if (preg_match($this->rules[$rules[$i]], $this->input, $match, 0, $this->offset)) {
                preg_match_all('/\n.*/', $match[0], $lines);
                $lines = $lines[0];
                if ($lines) {
                    $this->yylineno += \count($lines);
                }

                $this->yylloc = array(
                    'first_line' => $this->yylloc['last_line'],
                    'last_line' => $this->yylineno+1,
                    'first_column' => $this->yylloc['last_column'],
                    'last_column' => $lines ? \strlen($lines[\count($lines) - 1]) - 1 : $this->yylloc['last_column'] + \strlen($match[0]),
                );
                $this->yytext .= $match[0];
                $this->match .= $match[0];
                $this->yyleng = \strlen($this->yytext);
                $this->more = false;
                $this->offset += \strlen($match[0]);
                $token = $this->performAction($rules[$i], $this->conditionStack[\count($this->conditionStack)-1]);
                if ($token) {
                    return $token;
                }

                return new Undefined();
            }
        }

        if ($this->offset === \strlen($this->input)) {
            return $this->EOF;
        }

        $this->parseError(
            'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition(),
            array(
                'text' => "",
                'token' => null,
                'line' => $this->yylineno,
            )
        );
    }

    private function getCurrentRules()
    {
        return $this->conditions[$this->conditionStack[\count($this->conditionStack)-1]]['rules'];
    }

    private function performAction($avoiding_name_collisions, $YY_START)
    {
        switch ($avoiding_name_collisions) {
        case 0:/* skip whitespace */
            break;
        case 1:
            return 6;
        case 2:
            $this->yytext = substr($this->yytext, 1, $this->yyleng-2);

            return 4;
        case 3:
            return 17;
        case 4:
            return 18;
        case 5:
            return 23;
        case 6:
            return 24;
        case 7:
            return 22;
        case 8:
            return 21;
        case 9:
            return 10;
        case 10:
            return 11;
        case 11:
            return 8;
        case 12:
            return 14;
        case 13:
            return 'INVALID';
        }
    }
}
<?php

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

namespace Seld\JsonLint;

class Undefined
{
}
<?php

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

namespace Seld\JsonLint;

class ParsingException extends \Exception
{
    protected $details;

    /**
     * @param string $message
     * @phpstan-param array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details
     */
    public function __construct($message, $details = array())
    {
        $this->details = $details;
        parent::__construct($message);
    }

    /**
     * @phpstan-return array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
<?php

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

namespace Seld\JsonLint;
use stdClass;

/**
 * Parser class
 *
 * Example:
 *
 * $parser = new JsonParser();
 * // returns null if it's valid json, or an error object
 * $parser->lint($json);
 * // returns parsed json, like json_decode does, but slower, throws exceptions on failure.
 * $parser->parse($json);
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class JsonParser
{
    const DETECT_KEY_CONFLICTS = 1;
    const ALLOW_DUPLICATE_KEYS = 2;
    const PARSE_TO_ASSOC = 4;

    private $lexer;

    private $flags;
    private $stack;
    private $vstack; // semantic value stack
    private $lstack; // location stack

    /**
     * @phpstan-var array<string, int>
     */
    private $symbols = array(
        'error'                 => 2,
        'JSONString'            => 3,
        'STRING'                => 4,
        'JSONNumber'            => 5,
        'NUMBER'                => 6,
        'JSONNullLiteral'       => 7,
        'NULL'                  => 8,
        'JSONBooleanLiteral'    => 9,
        'TRUE'                  => 10,
        'FALSE'                 => 11,
        'JSONText'              => 12,
        'JSONValue'             => 13,
        'EOF'                   => 14,
        'JSONObject'            => 15,
        'JSONArray'             => 16,
        '{'                     => 17,
        '}'                     => 18,
        'JSONMemberList'        => 19,
        'JSONMember'            => 20,
        ':'                     => 21,
        ','                     => 22,
        '['                     => 23,
        ']'                     => 24,
        'JSONElementList'       => 25,
        '$accept'               => 0,
        '$end'                  => 1,
    );

    /**
     * @phpstan-var array<int, string>
     */
    private $terminals_ = array(
        2   => "error",
        4   => "STRING",
        6   => "NUMBER",
        8   => "NULL",
        10  => "TRUE",
        11  => "FALSE",
        14  => "EOF",
        17  => "{",
        18  => "}",
        21  => ":",
        22  => ",",
        23  => "[",
        24  => "]",
    );

    private $productions_ = array(
        0,
        array(3, 1),
        array(5, 1),
        array(7, 1),
        array(9, 1),
        array(9, 1),
        array(12, 2),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(15, 2),
        array(15, 3),
        array(20, 3),
        array(19, 1),
        array(19, 3),
        array(16, 2),
        array(16, 3),
        array(25, 1),
        array(25, 3)
    );

    private $table = array(array(3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 1 => array(3)), array( 14 => array(1,16)), array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)), array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)), array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)), array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)), array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)), array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)), array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)), array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)), array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)), array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)), array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)), array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), array( 1 => array(2,6)), array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)), array( 18 => array(1,24), 22 => array(1,25)), array( 18 => array(2,16), 22 => array(2,16)), array( 21 => array(1,26)), array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)), array( 22 => array(1,28), 24 => array(1,27)), array( 22 => array(2,20), 24 => array(2,20)), array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)), array( 3 => 20, 4 => array(1,12), 20 => 29 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 18 => array(2,17), 22 => array(2,17)), array( 18 => array(2,15), 22 => array(2,15)), array( 22 => array(2,21), 24 => array(2,21)),
    );

    private $defaultActions = array(
        16 => array(2, 6)
    );

    /**
     * @param  string                $input JSON string
     * @param  int                   $flags Bitmask of parse/lint options (see constants of this class)
     * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise
     */
    public function lint($input, $flags = 0)
    {
        try {
            $this->parse($input, $flags);
        } catch (ParsingException $e) {
            return $e;
        }
        return null;
    }

    /**
     * @param  string           $input JSON string
     * @param  int              $flags Bitmask of parse/lint options (see constants of this class)
     * @return mixed
     * @throws ParsingException
     */
    public function parse($input, $flags = 0)
    {
        $this->failOnBOM($input);

        $this->flags = $flags;

        $this->stack = array(0);
        $this->vstack = array(null);
        $this->lstack = array();

        $yytext = '';
        $yylineno = 0;
        $yyleng = 0;
        $recovering = 0;
        $TERROR = 2;
        $EOF = 1;

        $this->lexer = new Lexer();
        $this->lexer->setInput($input);

        $yyloc = $this->lexer->yylloc;
        $this->lstack[] = $yyloc;

        $symbol = null;
        $preErrorSymbol = null;
        $state = null;
        $action = null;
        $a = null;
        $r = null;
        $yyval = new stdClass;
        $p = null;
        $len = null;
        $newState = null;
        $expected = null;
        $errStr = null;

        while (true) {
            // retrieve state number from top of stack
            $state = $this->stack[\count($this->stack)-1];

            // use default actions if available
            if (isset($this->defaultActions[$state])) {
                $action = $this->defaultActions[$state];
            } else {
                if ($symbol == null) {
                    $symbol = $this->lex();
                }
                // read action for current state and first input
                $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
            }

            // handle parse error
            if (!$action || !$action[0]) {
                if (!$recovering) {
                    // Report error
                    $expected = array();
                    foreach ($this->table[$state] as $p => $ignore) {
                        if (isset($this->terminals_[$p]) && $p > 2) {
                            $expected[] = "'" . $this->terminals_[$p] . "'";
                        }
                    }

                    $message = null;
                    if (\in_array("'STRING'", $expected) && \in_array(substr($this->lexer->match, 0, 1), array('"', "'"))) {
                        $message = "Invalid string";
                        if ("'" === substr($this->lexer->match, 0, 1)) {
                            $message .= ", it appears you used single quotes instead of double quotes";
                        } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer->getFullUpcomingInput(), $match)) {
                            $message .= ", it appears you have an unescaped backslash at: ".$match[1];
                        } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getFullUpcomingInput())) {
                            $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid";
                        }
                    }

                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    if ($message) {
                        $errStr .= $message;
                    } else {
                        $errStr .= (\count($expected) > 1) ? "Expected one of: " : "Expected: ";
                        $errStr .= implode(', ', $expected);
                    }

                    if (',' === substr(trim($this->lexer->getPastInput()), -1)) {
                        $errStr .= " - It appears you have an extra trailing comma";
                    }

                    $this->parseError($errStr, array(
                        'text' => $this->lexer->match,
                        'token' => !empty($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol,
                        'line' => $this->lexer->yylineno,
                        'loc' => $yyloc,
                        'expected' => $expected,
                    ));
                }

                // just recovered from another error
                if ($recovering == 3) {
                    if ($symbol == $EOF) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }

                    // discard current lookahead and grab another
                    $yyleng = $this->lexer->yyleng;
                    $yytext = $this->lexer->yytext;
                    $yylineno = $this->lexer->yylineno;
                    $yyloc = $this->lexer->yylloc;
                    $symbol = $this->lex();
                }

                // try to recover from error
                while (true) {
                    // check for error recovery rule in this state
                    if (\array_key_exists($TERROR, $this->table[$state])) {
                        break;
                    }
                    if ($state == 0) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }
                    $this->popStack(1);
                    $state = $this->stack[\count($this->stack)-1];
                }

                $preErrorSymbol = $symbol; // save the lookahead token
                $symbol = $TERROR;         // insert generic error symbol as new lookahead
                $state = $this->stack[\count($this->stack)-1];
                $action = isset($this->table[$state][$TERROR]) ? $this->table[$state][$TERROR] : false;
                $recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
            }

            // this shouldn't happen, unless resolve defaults are off
            if (\is_array($action[0]) && \count($action) > 1) {
                throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol);
            }

            switch ($action[0]) {
                case 1: // shift
                    $this->stack[] = $symbol;
                    $this->vstack[] = $this->lexer->yytext;
                    $this->lstack[] = $this->lexer->yylloc;
                    $this->stack[] = $action[1]; // push state
                    $symbol = null;
                    if (!$preErrorSymbol) { // normal execution/no error
                        $yyleng = $this->lexer->yyleng;
                        $yytext = $this->lexer->yytext;
                        $yylineno = $this->lexer->yylineno;
                        $yyloc = $this->lexer->yylloc;
                        if ($recovering > 0) {
                            $recovering--;
                        }
                    } else { // error just occurred, resume old lookahead f/ before error
                        $symbol = $preErrorSymbol;
                        $preErrorSymbol = null;
                    }
                    break;

                case 2: // reduce
                    $len = $this->productions_[$action[1]][1];

                    // perform semantic action
                    $yyval->token = $this->vstack[\count($this->vstack) - $len]; // default to $$ = $1
                    // default location, uses first token for firsts, last for lasts
                    $yyval->store = array( // _$ = store
                        'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'],
                        'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'],
                        'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'],
                        'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'],
                    );
                    $r = $this->performAction($yyval, $yytext, $yyleng, $yylineno, $action[1], $this->vstack);

                    if (!$r instanceof Undefined) {
                        return $r;
                    }

                    if ($len) {
                        $this->popStack($len);
                    }

                    $this->stack[] = $this->productions_[$action[1]][0];    // push nonterminal (reduce)
                    $this->vstack[] = $yyval->token;
                    $this->lstack[] = $yyval->store;
                    $newState = $this->table[$this->stack[\count($this->stack)-2]][$this->stack[\count($this->stack)-1]];
                    $this->stack[] = $newState;
                    break;

                case 3: // accept

                    return true;
            }
        }
    }

    protected function parseError($str, $hash)
    {
        throw new ParsingException($str, $hash);
    }

    // $$ = $tokens // needs to be passed by ref?
    // $ = $token
    // _$ removed, useless?
    private function performAction(stdClass $yyval, $yytext, $yyleng, $yylineno, $yystate, &$tokens)
    {
        // $0 = $len
        $len = \count($tokens) - 1;
        switch ($yystate) {
        case 1:
            $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext);
            $yyval->token = $yytext;
            break;
        case 2:
            if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) {
                $yyval->token = \floatval($yytext);
            } else {
                $yyval->token = strpos($yytext, '.') === false ? \intval($yytext) : \floatval($yytext);
            }
            break;
        case 3:
            $yyval->token = null;
            break;
        case 4:
            $yyval->token = true;
            break;
        case 5:
            $yyval->token = false;
            break;
        case 6:
            return $yyval->token = $tokens[$len-1];
        case 13:
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $yyval->token = array();
            } else {
                $yyval->token = new stdClass;
            }
            break;
        case 14:
            $yyval->token = $tokens[$len-1];
            break;
        case 15:
            $yyval->token = array($tokens[$len-2], $tokens[$len]);
            break;
        case 16:
            if (PHP_VERSION_ID < 70100) {
                $property = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0];
            } else {
                $property = $tokens[$len][0];
            }
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $yyval->token = array();
                $yyval->token[$property] = $tokens[$len][1];
            } else {
                $yyval->token = new stdClass;
                $yyval->token->$property = $tokens[$len][1];
            }
            break;
        case 17:
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $yyval->token =& $tokens[$len-2];
                $key = $tokens[$len][0];
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2][$key])) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$tokens[$len][0];
                    throw new DuplicateKeyException($errStr, $tokens[$len][0], array('line' => $yylineno+1));
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2][$key])) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($tokens[$len-2][$duplicateKey]));
                    $key = $duplicateKey;
                }
                $tokens[$len-2][$key] = $tokens[$len][1];
            } else {
                $yyval->token = $tokens[$len-2];
                if (PHP_VERSION_ID < 70100) {
                    $key = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0];
                } else {
                    $key = $tokens[$len][0];
                }
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2]->{$key})) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$tokens[$len][0];
                    throw new DuplicateKeyException($errStr, $tokens[$len][0], array('line' => $yylineno+1));
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2]->{$key})) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($tokens[$len-2]->$duplicateKey));
                    $key = $duplicateKey;
                }
                $tokens[$len-2]->$key = $tokens[$len][1];
            }
            break;
        case 18:
            $yyval->token = array();
            break;
        case 19:
            $yyval->token = $tokens[$len-1];
            break;
        case 20:
            $yyval->token = array($tokens[$len]);
            break;
        case 21:
            $tokens[$len-2][] = $tokens[$len];
            $yyval->token = $tokens[$len-2];
            break;
        }

        return new Undefined();
    }

    private function stringInterpolation($match)
    {
        switch ($match[0]) {
        case '\\\\':
            return '\\';
        case '\"':
            return '"';
        case '\b':
            return \chr(8);
        case '\f':
            return \chr(12);
        case '\n':
            return "\n";
        case '\r':
            return "\r";
        case '\t':
            return "\t";
        case '\/':
            return "/";
        default:
            return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', ENT_QUOTES, 'UTF-8');
        }
    }

    private function popStack($n)
    {
        $this->stack = \array_slice($this->stack, 0, - (2 * $n));
        $this->vstack = \array_slice($this->vstack, 0, - $n);
        $this->lstack = \array_slice($this->lstack, 0, - $n);
    }

    private function lex()
    {
        $token = $this->lexer->lex() ?: 1; // $end = 1
        // if token isn't its numeric value, convert
        if (!is_numeric($token)) {
            $token = isset($this->symbols[$token]) ? $this->symbols[$token] : $token;
        }

        return $token;
    }

    private function failOnBOM($input)
    {
        // UTF-8 ByteOrderMark sequence
        $bom = "\xEF\xBB\xBF";

        if (substr($input, 0, 3) === $bom) {
            $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark", array());
        }
    }
}
<?php

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

namespace Seld\JsonLint;

class DuplicateKeyException extends ParsingException
{
    /**
     * @param string $message
     * @param string $key
     * @phpstan-param array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details
     */
    public function __construct($message, $key, array $details = array())
    {
        $details['key'] = $key;
        parent::__construct($message, $details);
    }

    public function getKey()
    {
        return $this->details['key'];
    }

    /**
     * @phpstan-return array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[], key: string}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
<?php

declare(strict_types=1);

namespace Psr\Container;

/**
 * Describes the interface of a container that exposes methods to read its entries.
 */
interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */
    public function get(string $id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has(string $id);
}
<?php

namespace Psr\Container;

/**
 * No entry was found in the container.
 */
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
<?php

namespace Psr\Container;

/**
 * Base interface representing a generic exception in a container.
 */
interface ContainerExceptionInterface
{
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface|null
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger);
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

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

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

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

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    abstract public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log\Test;

/**
 * This class is internal and does not follow the BC promise.
 *
 * Do NOT use this class in any way.
 *
 * @internal
 */
class DummyTest
{
    public function __toString()
    {
        return 'DummyTest';
    }
}
<?php

namespace Psr\Log\Test;

use Psr\Log\AbstractLogger;

/**
 * Used for testing purposes.
 *
 * It records all records and gives you access to them for verification.
 *
 * @method bool hasEmergency($record)
 * @method bool hasAlert($record)
 * @method bool hasCritical($record)
 * @method bool hasError($record)
 * @method bool hasWarning($record)
 * @method bool hasNotice($record)
 * @method bool hasInfo($record)
 * @method bool hasDebug($record)
 *
 * @method bool hasEmergencyRecords()
 * @method bool hasAlertRecords()
 * @method bool hasCriticalRecords()
 * @method bool hasErrorRecords()
 * @method bool hasWarningRecords()
 * @method bool hasNoticeRecords()
 * @method bool hasInfoRecords()
 * @method bool hasDebugRecords()
 *
 * @method bool hasEmergencyThatContains($message)
 * @method bool hasAlertThatContains($message)
 * @method bool hasCriticalThatContains($message)
 * @method bool hasErrorThatContains($message)
 * @method bool hasWarningThatContains($message)
 * @method bool hasNoticeThatContains($message)
 * @method bool hasInfoThatContains($message)
 * @method bool hasDebugThatContains($message)
 *
 * @method bool hasEmergencyThatMatches($message)
 * @method bool hasAlertThatMatches($message)
 * @method bool hasCriticalThatMatches($message)
 * @method bool hasErrorThatMatches($message)
 * @method bool hasWarningThatMatches($message)
 * @method bool hasNoticeThatMatches($message)
 * @method bool hasInfoThatMatches($message)
 * @method bool hasDebugThatMatches($message)
 *
 * @method bool hasEmergencyThatPasses($message)
 * @method bool hasAlertThatPasses($message)
 * @method bool hasCriticalThatPasses($message)
 * @method bool hasErrorThatPasses($message)
 * @method bool hasWarningThatPasses($message)
 * @method bool hasNoticeThatPasses($message)
 * @method bool hasInfoThatPasses($message)
 * @method bool hasDebugThatPasses($message)
 */
class TestLogger extends AbstractLogger
{
    /**
     * @var array
     */
    public $records = [];

    public $recordsByLevel = [];

    /**
     * @inheritdoc
     */
    public function log($level, $message, array $context = [])
    {
        $record = [
            'level' => $level,
            'message' => $message,
            'context' => $context,
        ];

        $this->recordsByLevel[$record['level']][] = $record;
        $this->records[] = $record;
    }

    public function hasRecords($level)
    {
        return isset($this->recordsByLevel[$level]);
    }

    public function hasRecord($record, $level)
    {
        if (is_string($record)) {
            $record = ['message' => $record];
        }
        return $this->hasRecordThatPasses(function ($rec) use ($record) {
            if ($rec['message'] !== $record['message']) {
                return false;
            }
            if (isset($record['context']) && $rec['context'] !== $record['context']) {
                return false;
            }
            return true;
        }, $level);
    }

    public function hasRecordThatContains($message, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($message) {
            return strpos($rec['message'], $message) !== false;
        }, $level);
    }

    public function hasRecordThatMatches($regex, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($regex) {
            return preg_match($regex, $rec['message']) > 0;
        }, $level);
    }

    public function hasRecordThatPasses(callable $predicate, $level)
    {
        if (!isset($this->recordsByLevel[$level])) {
            return false;
        }
        foreach ($this->recordsByLevel[$level] as $i => $rec) {
            if (call_user_func($predicate, $rec, $i)) {
                return true;
            }
        }
        return false;
    }

    public function __call($method, $args)
    {
        if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
            $level = strtolower($matches[2]);
            if (method_exists($this, $genericMethod)) {
                $args[] = $level;
                return call_user_func_array([$this, $genericMethod], $args);
            }
        }
        throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
    }

    public function reset()
    {
        $this->records = [];
        $this->recordsByLevel = [];
    }
}
<?php

namespace Psr\Log\Test;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use PHPUnit\Framework\TestCase;

/**
 * Provides a base test class for ensuring compliance with the LoggerInterface.
 *
 * Implementors can extend the class and implement abstract methods to run this
 * as part of their test suite.
 */
abstract class LoggerInterfaceTest extends TestCase
{
    /**
     * @return LoggerInterface
     */
    abstract public function getLogger();

    /**
     * This must return the log messages in order.
     *
     * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
     *
     * Example ->error('Foo') would yield "error Foo".
     *
     * @return string[]
     */
    abstract public function getLogs();

    public function testImplements()
    {
        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    }

    /**
     * @dataProvider provideLevelsAndMessages
     */
    public function testLogsAtAllLevels($level, $message)
    {
        $logger = $this->getLogger();
        $logger->{$level}($message, array('user' => 'Bob'));
        $logger->log($level, $message, array('user' => 'Bob'));

        $expected = array(
            $level.' message of level '.$level.' with context: Bob',
            $level.' message of level '.$level.' with context: Bob',
        );
        $this->assertEquals($expected, $this->getLogs());
    }

    public function provideLevelsAndMessages()
    {
        return array(
            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
        );
    }

    /**
     * @expectedException \Psr\Log\InvalidArgumentException
     */
    public function testThrowsOnInvalidLevel()
    {
        $logger = $this->getLogger();
        $logger->log('invalid level', 'Foo');
    }

    public function testContextReplacement()
    {
        $logger = $this->getLogger();
        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));

        $expected = array('info {Message {nothing} Bob Bar a}');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testObjectCastToString()
    {
        if (method_exists($this, 'createPartialMock')) {
            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
        } else {
            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
        }
        $dummy->expects($this->once())
            ->method('__toString')
            ->will($this->returnValue('DUMMY'));

        $this->getLogger()->warning($dummy);

        $expected = array('warning DUMMY');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextCanContainAnything()
    {
        $closed = fopen('php://memory', 'r');
        fclose($closed);

        $context = array(
            'bool' => true,
            'null' => null,
            'string' => 'Foo',
            'int' => 0,
            'float' => 0.5,
            'nested' => array('with object' => new DummyTest),
            'object' => new \DateTime,
            'resource' => fopen('php://memory', 'r'),
            'closed' => $closed,
        );

        $this->getLogger()->warning('Crazy context data', $context);

        $expected = array('warning Crazy context data');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    {
        $logger = $this->getLogger();
        $logger->warning('Random message', array('exception' => 'oops'));
        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));

        $expected = array(
            'warning Random message',
            'critical Uncaught Exception!'
        );
        $this->assertEquals($expected, $this->getLogs());
    }
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

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

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

    /**
     * Detailed debug information.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed   $level
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, $message, array $context = array());
}
<?php

namespace ComposerPatcher;

use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\VersionParser;

/**
 * Represent a single patch provided by a package.
 */
class Patch
{
    /**
     * The package that provides the patch.
     *
     * @var \Composer\Package\PackageInterface
     */
    private $fromPackage;

    /**
     * The name of the package the patch is for.
     *
     * @var string
     */
    private $forPackage;

    /**
     * The package version constraint.
     *
     * @var \Composer\Semver\Constraint\ConstraintInterface|null
     */
    private $forPackageVersion;

    /**
     * The path the patch as specified by the package.
     *
     * @var string
     */
    private $originalPath;

    /**
     * The absolute path of the local patch file.
     *
     * @var string
     */
    private $localPath;

    /**
     * The description of the patch.
     *
     * @var string
     */
    private $description;

    /**
     * The patch levels.
     *
     * @var string
     */
    private $levels;

    /**
     * The patch hash.
     *
     * @var string|null
     */
    private $hash;

    /**
     * @param \Composer\Package\PackageInterface $fromPackage the package that provides the patch
     * @param string $forPackage the name of the package the patch is for (may include versions after a colon)
     * @param string $originalPath the path the patch as specified by the package
     * @param string $localPath the absolute path of the local patch file
     * @param string $description the description of the patch
     * @param string[] $levels the patch levels
     */
    public function __construct(PackageInterface $fromPackage, $forPackage, $originalPath, $localPath, $description, array $levels)
    {
        $this->fromPackage = $fromPackage;
        list($this->forPackage, $forPackageVersionString) = $this->parsePackageVersion($forPackage);
        if ($forPackageVersionString === '*') {
            $this->forPackageVersion = null;
        } else {
            $versionParser = new VersionParser();
            $this->forPackageVersion = $versionParser->parseConstraints($forPackageVersionString);
        }

        $this->originalPath = $originalPath;
        $this->localPath = $localPath;
        $this->description = $description;
        $this->levels = $levels;
    }

    /**
     * Ge the package that provides the patch.
     *
     * @return \Composer\Package\PackageInterface
     */
    public function getFromPackage()
    {
        return $this->fromPackage;
    }

    /**
     * Get the name of the package the patch is for.
     *
     * @return string
     */
    public function getForPackage()
    {
        return $this->forPackage;
    }

    /**
     * Get the version constraint of the package the patch is for (if any).
     *
     * @return \Composer\Semver\Constraint\ConstraintInterface|null
     */
    public function getForPackageVersion()
    {
        return $this->forPackageVersion;
    }

    /**
     * Get the path the patch as specified by the package.
     *
     * @return string
     */
    public function getOriginalPath()
    {
        return $this->originalPath;
    }

    /**
     * Get the absolute path of the local patch file.
     *
     * @return string
     */
    public function getLocalPath()
    {
        return $this->localPath;
    }

    /**
     * Get the description of the patch.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Get the patch levels.
     *
     * @return string[]
     */
    public function getLevels()
    {
        return $this->levels;
    }

    /**
     * Check if this patch is for a package.
     *
     * @return bool
     */
    public function isForPackage(PackageInterface $package)
    {
        if ($this->getForPackage() !== $package->getName()) {
            return false;
        }
        $fpv = $this->getForPackageVersion();
        if ($fpv !== null) {
            $pv = new Constraint('=', $package->getVersion());
            if (!$fpv->matches($pv)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get the hash of this patch.
     *
     * @return string
     */
    public function getHash()
    {
        if ($this->hash === null) {
            $this->hash = sha1_file($this->getLocalPath());
        }

        return $this->hash;
    }

    /**
     * @param string $package
     *
     * @return string[]
     */
    protected function parsePackageVersion($package)
    {
        $parts = explode(':', $package, 2);
        $parts[1] = isset($parts[1]) ? trim($parts[1]) : '*';
        if ($parts[1] !== '') {
            $name = $parts[0];
            $version = $parts[1];
        } else {
            $name = $package;
            $version = '*';
        }

        return array($name, $version);
    }
}
<?php

namespace ComposerPatcher;

use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use ComposerPatcher\Util\PathResolver;
use Exception as BaseException;

/**
 * The class that collects patches from packages.
 */
class PatchCollector
{
    /**
     * The PathResoler instance to be used to resolve/download patch files.
     *
     * @var \ComposerPatcher\Util\PathResolver
     */
    private $pathResolver;

    /**
     * The IOInterface instance to be used for user interaction.
     *
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * The InstallationManager instance to be used to resolve package installation paths.
     *
     * @var \Composer\Installer\InstallationManager
     */
    private $installationManager;

    /**
     * Should errors be considered as warnings (printed to the IO) instead of throwing exceptions?
     *
     * @var bool
     */
    private $errorsAsWarnings;

    /**
     * Initialize the instance.
     *
     * @param \ComposerPatcher\Util\PathResolver $pathResolver the PathResoler instance to be used to resolve/download patch files
     * @param \Composer\IO\IOInterface $io the IOInterface instance to be used for user interaction
     * @param \Composer\Installer\InstallationManager $installationManager the InstallationManager instance to be used to resolve package installation paths
     * @param bool $errorsAsWarnings should errors be considered as warnings (printed to the IO) instead of throwing exceptions?
     */
    public function __construct(PathResolver $pathResolver, IOInterface $io, InstallationManager $installationManager, $errorsAsWarnings)
    {
        $this->pathResolver = $pathResolver;
        $this->io = $io;
        $this->installationManager = $installationManager;
        $this->errorsAsWarnings = $errorsAsWarnings;
    }

    /**
     * Collect the patches.
     *
     * @param \Composer\Package\RootPackageInterface $rootPackage the root composer package
     * @param \Composer\Package\PackageInterface[] $subPackages the dependency packages
     *
     * @return \ComposerPatcher\PatchCollection
     */
    public function collectPatches(RootPackageInterface $rootPackage, array $subPackages)
    {
        $collectedPatches = $this->collectPatchesFromPackage($rootPackage);
        $extra = $rootPackage->getExtra();
        if (isset($extra['allow-subpatches'])) {
            $allowedSubpatches = $extra['allow-subpatches'];
            if (!\is_bool($allowedSubpatches) && !\is_array($allowedSubpatches)) {
                $this->handleException(new Exception\InvalidPackageConfigurationValue($rootPackage, 'extra.allow-subpatches', $allowedSubpatches, 'The extra.allow-subpatches must be a boolean or an array of strings.'));
            }
            $collectedPatches->merge($this->collectSubpackagePatches($subPackages, $allowedSubpatches));
        }

        return $collectedPatches;
    }

    /**
     * Collect the patches from the sub-packages.
     *
     * @param \Composer\Package\PackageInterface[] $subPackages the dependency packages
     * @param string[]|bool $allowedSubpatches TRUE to allow any sub-package; FALSE to allow no sub-packages; an array with allowed package names
     *
     * @return \ComposerPatcher\PatchCollection
     */
    protected function collectSubpackagePatches(array $subPackages, $allowedSubpatches)
    {
        $collectedPatches = new PatchCollection();
        if ($allowedSubpatches !== false && $allowedSubpatches !== array()) {
            foreach ($subPackages as $subPackage) {
                if ($allowedSubpatches === true || \in_array($subPackage->getName(), $allowedSubpatches, true)) {
                    $collectedPatches->merge($this->collectPatchesFromPackage($subPackage));
                }
            }
        }

        return $collectedPatches;
    }

    /**
     * Collect the patches from a package.
     *
     * @param \Composer\Package\PackageInterface $package the package to be inspected
     *
     * @return \ComposerPatcher\PatchCollection
     */
    protected function collectPatchesFromPackage(PackageInterface $package)
    {
        $collectedPatches = new PatchCollection();
        $extra = $package->getExtra();
        if (isset($extra['patches'])) {
            $this->io->write('<info>Gathering patches from '.$package->getName().' (extra.patches).</info>');
            $collectedPatches->merge($this->resolveExplicitList($package, $extra['patches']));
        }
        if (isset($extra['patches-file'])) {
            $this->io->write('<info>Gathering patches from '.$package->getName().' (extra.patches-file).</info>');
            $collectedPatches->merge($this->resolveJsonFile($package, $extra['patches-file']));
        }

        return $collectedPatches;
    }

    /**
     * Collect the patches from the "patches" configuration key.
     *
     * @param \Composer\Package\PackageInterface $package the package being inspected
     * @param array|mixed $patches the extra.patches configuration provided by the package
     *
     * @throws \ComposerPatcher\Exception\InvalidPackageConfigurationValue when $patches is not valid
     *
     * @return \ComposerPatcher\PatchCollection
     */
    protected function resolveExplicitList(PackageInterface $package, $patches)
    {
        $collectedPatches = new PatchCollection();
        if (!\is_array($patches)) {
            $this->handleException(Exception\InvalidPackageConfigurationValue($package, 'extra.patches', $patches, 'The extra.patches configuration must be an array.'));
        } else {
            // If the package is the RootPackage, we won't be able to get its install path from the installationmanager
            if ($package instanceof RootPackageInterface) {
                $packageDirectory = getcwd();
            } else {
                $packageDirectory = $this->installationManager->getInstallPath($package);
            }
            foreach ($patches as $forPackageHandle => $patchList) {
                if (!\is_array($patchList)) {
                    $this->handleException(Exception\InvalidPackageConfigurationValue($package, 'extra.patches', $package, "The \"{$forPackageHandle}\" value must be an array."));
                    continue;
                }
                foreach ($patchList as $patchDescription => $patchData) {
                    try {
                        list($path, $levels) = $this->extractPatchData($package, $patchData);
                        $localFile = $this->pathResolver->resolve($path, $packageDirectory);
                        if ('' === $localFile) {
                            $this->handleException(Exception\InvalidPackageConfigurationValue($package, 'extra.patches', $package, "The path of the \"{$patchDescription}\" patch is empty or is not a string."));
                        }
                        $collectedPatches->addPatch(
                            new Patch($package, $forPackageHandle, $path, $localFile, $patchDescription, $levels)
                        );
                    } catch (BaseException $x) {
                        $this->handleException($x);
                    }
                }
            }
        }

        return $collectedPatches;
    }

    /**
     * Extract the data of a single patch.
     *
     * @param \Composer\Package\PackageInterface $package the package being inspected
     * @param array|string|mixed $patchData The single patch data
     *
     * @throws \Exception in case of errors
     *
     * @return array
     */
    protected function extractPatchData(PackageInterface $package, $patchData)
    {
        $levels = null;
        if (\is_string($patchData)) {
            $path = $patchData;
        } else {
            if (!\is_array($patchData) || !isset($patchData['path']) || \is_string($patchData['path'])) {
                throw new Exception\InvalidPackageConfigurationValue($package, 'extra.patches.[...]', $package, "The value of a patch must be a string or an array with a 'path' node value.");
            }
            $path = $patchData['path'];
            if (isset($patchData['levels'])) {
                $levels = $patchData['levels'];
                if (!\is_array($levels) || empty($levels)) {
                    throw new Exception\InvalidPackageConfigurationValue($package, 'extra.patches.[...].levels', $package, 'The patch levels must be an array of strings.');
                }
            }
        }
        if (null === $levels) {
            $levels = $this->getDefaultPatchLevels();
        }

        return array($path, $levels);
    }

    /**
     * Collect the patches from the "patches-file" configuration key.
     *
     * @param \Composer\Package\PackageInterface $package the package being inspected
     * @param string|mixed $jsonPath the extra.patches-file configuration provided by the package
     *
     * @throws \ComposerPatcher\Exception\InvalidPackageConfigurationValue when $jsonPath is not valid
     *
     * @return \ComposerPatcher\PatchCollection
     */
    protected function resolveJsonFile(PackageInterface $package, $jsonPath)
    {
        $collectedPatches = new PatchCollection();
        if (!\is_string($jsonPath) || '' === $jsonPath) {
            $this->handleException(Exception\InvalidPackageConfigurationValue($package(), 'extra.patches-file', $jsonPath, 'The extra.patches-file configuration must be a non empty string.'));
        } else {
            $packageDirectory = $this->installationManager->getInstallPath($package);
            $fullJsonPath = $this->pathResolver->resolve($jsonPath, $packageDirectory);
            $jsonReader = new JsonFile($fullJsonPath, null, $this->io);
            $data = $jsonReader->read();
            if (!\is_array($data)) {
                $this->handleException(Exception\InvalidPackageConfigurationValue($package, 'extra.patches-file', $jsonPath, "The JSON file at \"{$jsonPath}\" must contain an array."));
            } elseif (!isset($data['patches'])) {
                $this->handleException(Exception\InvalidPackageConfigurationValue($package, 'extra.patches-file', $jsonPath, "The JSON file at \"{$jsonPath}\" must contain an array with a \"patches\" key."));
            } else {
                $collectedPatches->merge($this->resolveExplicitList($package, $data['patches']));
            }
        }

        return $collectedPatches;
    }

    /**
     * Throws or prints out an error accordingly to $errorsAsWarnings.
     *
     * @param \Exception $exception The Exception to be reported
     *
     * @throws \Exception throws $exception if $errorsAsWarnings is false, prints it out if $errorsAsWarnings is true
     */
    protected function handleException(BaseException $exception)
    {
        if (!$this->errorsAsWarnings) {
            throw $exception;
        }
        $this->io->write('<error>'.$exception->getMessage().'</error>');
    }

    /**
     * Get the default patch levels.
     *
     * @return string[]
     */
    protected function getDefaultPatchLevels()
    {
        return array('-p1', '-p0', '-p2', '-p4');
    }
}
<?php

namespace ComposerPatcher;

use Exception as PhpException;

/**
 * A base class for the ComposerPatcher exceptions.
 */
abstract class Exception extends PhpException
{
}
<?php

namespace ComposerPatcher;

use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackage;
use Composer\Util\ProcessExecutor;
use ComposerPatcher\Event\Patch as PatchEvent;
use ComposerPatcher\PatchExecutor\GitPatcher;
use ComposerPatcher\PatchExecutor\PatchPatcher;
use ComposerPatcher\Util\VolatileDirectory;

class Patcher
{
    /**
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * The InstallationManager instance to be used to resolve package installation paths.
     *
     * @var \Composer\Installer\InstallationManager
     */
    private $installationManager;

    /**
     * @var \Composer\EventDispatcher\EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var \Composer\Util\ProcessExecutor
     */
    private $processExecutor;

    /**
     * @var \ComposerPatcher\PatchExecutor\GitPatcher|null
     */
    private $gitPatcher;

    /**
     * @var \ComposerPatcher\PatchExecutor\PatchPatcher|null
     */
    private $patchPatcher;

    /**
     * @var \ComposerPatcher\Util\VolatileDirectory
     */
    private $volatileDirectory;

    public function __construct(IOInterface $io, InstallationManager $installationManager, EventDispatcher $eventDispatcher, ProcessExecutor $processExecutor, VolatileDirectory $volatileDirectory)
    {
        $this->io = $io;
        $this->installationManager = $installationManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->processExecutor = $processExecutor;
        $this->volatileDirectory = $volatileDirectory;
    }

    /**
     * Apply a patch to a package.
     *
     * @param \ComposerPatcher\Patch $patch
     *
     * @throws \Exception in case of errors
     */
    public function applyPatch(Patch $patch, PackageInterface $package)
    {
        $this->io->write('<info>Applying patch '.$patch->getFromPackage()->getName().'/'.$patch->getDescription().' to '.$package->getName().'... </info>', false);
        if ($package instanceof RootPackage) {
            $baseDirectory = getcwd();
        } else {
            $baseDirectory = $this->installationManager->getInstallPath($package);
        }
        $this->eventDispatcher->dispatch(null, new PatchEvent(PatchEvent::EVENTNAME_PRE_APPLY_PATCH, $patch));
        try {
            if (GitPatcher::shouldBeUsetToApplyPatchesTo($baseDirectory)) {
                $this->getGitPatcher()->applyPatch($patch, $baseDirectory);
            } else {
                $this->getPatchPatcher()->applyPatch($patch, $baseDirectory);
            }
            $this->io->write('<info>done.</info>');
        } catch (Exception\PatchAlreadyApplied $x) {
            $this->io->write('<info>patch was already applied.</info>');
        }
        $this->eventDispatcher->dispatch(null, new PatchEvent(PatchEvent::EVENTNAME_POST_APPLY_PATCH, $patch));
    }

    /**
     * @throws \ComposerPatcher\Exception\CommandNotFound when the git command is not available
     *
     * @return \ComposerPatcher\PatchExecutor\GitPatcher
     */
    protected function getGitPatcher()
    {
        if (null === $this->gitPatcher) {
            $this->gitPatcher = new GitPatcher($this->processExecutor, $this->io, $this->volatileDirectory);
        }

        return $this->gitPatcher;
    }

    /**
     * @throws \ComposerPatcher\Exception\CommandNotFound when the git command is not available
     *
     * @return \ComposerPatcher\PatchExecutor\PatchPatcher
     */
    protected function getPatchPatcher()
    {
        if (null === $this->patchPatcher) {
            $this->patchPatcher = new PatchPatcher($this->processExecutor, $this->io, $this->volatileDirectory);
        }

        return $this->patchPatcher;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;
use ComposerPatcher\Patch;

/**
 * An exception thrown when a patch can't be applied.
 */
class PatchNotApplied extends Exception
{
    /**
     * The path that could not be applied.
     *
     * @var \ComposerPatcher\Patch
     */
    private $patch;

    /**
     * @param Patch $patch the path that could not be applied
     * @param string $reason the description why the patch could not be applied
     */
    public function __construct(Patch $patch, $reason = '')
    {
        $this->patch = $patch;
        $message = 'Unable to apply the patch "'.$patch->getDescription().'" provided by "'.$patch->getFromPackage()->getName().'"';
        $reason = (string) $reason;
        if ($reason === '') {
            $message .= '.';
        } else {
            $message .= ': '.$reason;
        }
        parent::__construct($message);
    }

    /**
     * Get the path that could not be applied.
     *
     * @return \ComposerPatcher\Patch
     */
    public function getPatch()
    {
        return $this->patch;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;
use ComposerPatcher\Patch;

/**
 * An exception thrown while trying to apply an already applied patch.
 */
class PatchAlreadyApplied extends Exception
{
    /**
     * The already applied patch.
     *
     * @var \ComposerPatcher\Patch
     */
    protected $patch;

    /**
     * @param \ComposerPatcher\Patch $patch the already applied patch
     * @param string $message an optional message
     */
    public function __construct(Patch $patch, $message = '')
    {
        $this->patch = $patch;
        $message = (string) $message;
        if ($message === '') {
            $packageVersion = $patch->getForPackageVersion();
            $message = 'The patch "'.$patch->getDescription().'"';
            if ($packageVersion !== null) {
                $message .= ' for '.$packageVersion->getPrettyString();
            }
            $message .= ' provided by '.$patch->getFromPackage()->__toString().' is already applied.';
        }
        parent::__construct($message);
    }

    /**
     * Get the already applied patch.
     *
     * @return \ComposerPatcher\Patch
     */
    public function getPatch()
    {
        return $this->patch;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;

/**
 * An exception thrown when a path could not be found.
 */
class PathNotFound extends Exception
{
    /**
     * The path that could not be found.
     *
     * @var string
     */
    protected $path;

    /**
     * @param string $path the path that could not be found
     * @param string $message an optional message
     */
    public function __construct($path, $message = '')
    {
        $this->path = $path;
        $message = (string) $message;
        if ($message === '') {
            $message = "Unable to find the path \"{$path}\".";
        }
        parent::__construct($message);
    }

    /**
     * Get the path that could not be found.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;

/**
 * An exception thrown when a path is not writable.
 */
class PathNotWritable extends Exception
{
    /**
     * The path that is not writable.
     *
     * @var string
     */
    protected $path;

    /**
     * @param string $path the path that is not writable
     * @param string $message an optional message
     */
    public function __construct($path, $message = '')
    {
        $this->path = $path;
        $message = (string) $message;
        if ($message === '') {
            $message = "The path \"{$path}\" is not writable.";
        }
        parent::__construct($message);
    }

    /**
     * Get the path that is not writable.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;

/**
 * An exception thrown when a path could not be created.
 */
class PathNotCreated extends Exception
{
    /**
     * The path that was not created.
     *
     * @var string
     */
    protected $path;

    /**
     * @param string $path the path that was not created
     * @param string $message an optional message
     */
    public function __construct($path, $message = '')
    {
        $this->path = $path;
        $message = (string) $message;
        if ($message === '') {
            $message = "Failed to create the path \"{$path}\".";
        }
        parent::__construct($message);
    }

    /**
     * Get the path that was not created.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;

/**
 * An exception thrown when a command is not found.
 */
class CommandNotFound extends Exception
{
    /**
     * The comamnd that could not be found.
     *
     * @var string
     */
    protected $command;

    /**
     * @param string $command the comamnd that could not be found
     * @param string $message an optional message
     */
    public function __construct($command, $message = '')
    {
        $this->command = $command;
        $message = (string) $message;
        if ($message === '') {
            $message = "Unable to find the command \"{$command}\".";
        }
        parent::__construct($message);
    }

    /**
     * Get the command that could not be found.
     *
     * @return string
     */
    public function getCommand()
    {
        return $this->command;
    }
}
<?php

namespace ComposerPatcher\Exception;

use Composer\Package\PackageInterface;
use ComposerPatcher\Exception;

/**
 * An exception thrown when a package contains an invalid configuration value.
 */
class InvalidPackageConfigurationValue extends Exception
{
    /**
     * The package that contains the invalid configuration value.
     *
     * @var \Composer\Package\PackageInterface
     */
    protected $package;

    /**
     * The name of the invalid configuration value.
     *
     * @var string
     */
    protected $configurationName;

    /**
     * The value of the invalid configuration.
     *
     * @var mixed
     */
    protected $configurationValue;

    /**
     * The reason why the message value is not valid.
     *
     * @var string
     */
    protected $invalidValueDescription;

    /**
     * @param string $configurationName the name of the invalid configuration value
     * @param mixed $configurationValue the value of the invalid configuration
     * @param string $invalidValueDescription the reason why the message value is not valid
     */
    public function __construct(PackageInterface $package, $configurationName, $configurationValue, $invalidValueDescription)
    {
        $this->package = $package;
        $this->configurationName = $configurationName;
        $this->configurationValue = $configurationValue;
        $this->invalidValueDescription = $invalidValueDescription;
        $message = 'The package "'.$package->getName().'" contains an invalid value of the "'.$configurationValue.'" configuration key: '.$invalidValueDescription;
        parent::__construct($message);
    }

    /**
     * Get the package that contains the invalid configuration value.
     *
     * @return \Composer\Package\PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Get the name of the invalid configuration value.
     *
     * @return string
     */
    public function getConfigurationName()
    {
        return $this->configurationName;
    }

    /**
     * Get the value of the invalid configuration.
     *
     * @return mixed
     */
    public function getConfigurationValue()
    {
        return $this->configurationValue;
    }

    /**
     * Get the reason why the message value is not valid.
     *
     * @return string
     */
    public function getInvalidValueDescription()
    {
        return $this->invalidValueDescription;
    }
}
<?php

namespace ComposerPatcher\Exception;

use ComposerPatcher\Exception;

/**
 * An exception thrown when a path is not readable.
 */
class PathNotReadable extends Exception
{
    /**
     * The path that is not readable.
     *
     * @var string
     */
    protected $path;

    /**
     * @param string $path the path that is not readable
     * @param string $message an optional message
     */
    public function __construct($path, $message = '')
    {
        $this->path = $path;
        $message = (string) $message;
        if ($message === '') {
            $message = "The path \"{$path}\" is not readable.";
        }
        parent::__construct($message);
    }

    /**
     * Get the path that is not readable.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace ComposerPatcher;

use Composer\EventDispatcher\Event as ComposerEvent;

/**
 * A base class for the ComposerPatcher events.
 */
abstract class Event extends ComposerEvent
{
    /**
     * The name of the event fired right before applying a patch.
     *
     * @var string
     */
    const EVENTNAME_PRE_APPLY_PATCH = 'pre-apply-patch';

    /**
     * The name of the event fired right after a patch has been applied.
     *
     * @var string
     */
    const EVENTNAME_POST_APPLY_PATCH = 'post-apply-patch';
}
<?php

namespace ComposerPatcher\Event;

use ComposerPatcher\Event;
use ComposerPatcher\Patch as PatchObject;

/**
 * Represent a patch-related event.
 */
class Patch extends Event
{
    /**
     * The patch.
     *
     * @var \ComposerPatcher\Patch
     */
    protected $patch;

    /**
     * Initialize the instance.
     *
     * @param string $name the event name
     */
    public function __construct($name, PatchObject $patch)
    {
        parent::__construct($name);
        $this->patch = $patch;
    }

    /**
     * Get the patch.
     *
     * @return \ComposerPatcher\Patch
     */
    public function getPatch()
    {
        return $this->patch;
    }
}
<?php

namespace ComposerPatcher\Util;

use Composer\Util\Filesystem;
use ComposerPatcher\Exception;
use Exception as GenericException;

/**
 * A helper class that creates a temporary directory, and deletes it when the class is no more in use.
 */
class VolatileDirectory
{
    /**
     * @var \Composer\Util\Filesystem
     */
    private $filesystem;

    /**
     * @var string
     */
    private $parentDirectory;

    /**
     * A progressive counter used to build unique paths.
     *
     * @var int
     */
    private $uniqueCounter = 0;

    /**
     * The path to the volatile directory.
     *
     * @var string|null
     */
    private $path;

    /**
     * Initialize the instance.
     *
     * @param string $parentDirectory the parent directory where the volatile directory will be created
     * @param \Composer\Util\Filesystem $filesystem|null The composer file system instance to be used
     */
    public function __construct($parentDirectory = '', Filesystem $filesystem = null)
    {
        $parentDirectory = str_replace(\DIRECTORY_SEPARATOR, '/', (string) $parentDirectory);
        if ($parentDirectory !== '/') {
            $parentDirectory = rtrim($parentDirectory, '/');
        }
        $this->parentDirectory = $parentDirectory;
        $this->filesystem = $filesystem === null ? new Filesystem() : $filesystem;
    }

    /**
     * Destroys the volatile directory.
     */
    public function __destruct()
    {
        if ($this->path !== null) {
            set_error_handler(function ($code, $msg) {
            });
            try {
                @$this->filesystem->removeDirectory($this->path);
            } catch (GenericException $x) {
            }
            restore_error_handler();
            $this->path = null;
        }
    }

    /**
     * Get the path of the system temporary directory.
     *
     * @throws \ComposerPatcher\Exception\PathNotFound when the temporary directory can't be retrieved or when it does not exist
     *
     * @return string
     */
    public static function getSystemTemporaryDirectory()
    {
        $tempDir = @sys_get_temp_dir();
        if (\is_string($tempDir)) {
            $tempDir = str_replace(\DIRECTORY_SEPARATOR, '/', (string) $tempDir);
            if ($tempDir !== '/') {
                $tempDir = rtrim($tempDir, '/');
            }
        } else {
            $tempDir = '';
        }
        if ($tempDir === '' || !is_dir($tempDir)) {
            throw new Exception\PathNotFound($tempDir, 'Unable to detect the system temporary directory.');
        }

        return $tempDir;
    }

    /**
     * Get the path of a new item inside this directory.
     *
     * @param string $suffix
     *
     * @return string
     */
    public function getNewPath($suffix = '')
    {
        $result = $this->getPath().'/'.$this->uniqueCounter.(string) $suffix;
        $this->uniqueCounter++;

        return $result;
    }

    /**
     * @return \Composer\Util\Filesystem
     */
    public function getFilesystem()
    {
        return $this->filesystem;
    }

    /**
     * Get the path to the volatile directory.
     *
     * @throws \ComposerPatcher\Exception\PathNotFound when the parent directory is not specified and the system temporary directory can't be retrieved or when it does not exist
     * @throws \ComposerPatcher\Exception\PathNotFound when $parentDirectory is supplied but it does not exist
     * @throws \ComposerPatcher\Exception\PathNotWritable when the directory where the volatile directory will be created is not writable
     * @throws \ComposerPatcher\Exception\PathNotCreated when the temporary directory could not be created
     *
     * @return string
     */
    public function getPath()
    {
        if ($this->path === null) {
            $parentDirectory = $this->parentDirectory;
            if ($parentDirectory === '') {
                $parentDirectory = static::getSystemTemporaryDirectory();
            } else {
                if (!is_dir($parentDirectory)) {
                    throw new Exception\PathNotFound($parentDirectory);
                }
            }
            if (!is_writable($parentDirectory)) {
                throw new Exception\PathNotWritable($parentDirectory);
            }
            for (;;) {
                $tempNam = @tempnam($parentDirectory, 'PCH');
                if ($tempNam === false) {
                    throw new Exception\PathNotCreated("{$parentDirectory}/<TEMPORARY>");
                }
                @unlink($tempNam);
                if (@mkdir($tempNam)) {
                    break;
                }
            }
            $this->path = rtrim(str_replace(\DIRECTORY_SEPARATOR, '/', $tempNam), '/');
        }

        return $this->path;
    }
}
<?php

namespace ComposerPatcher\Util;

use Composer\Util\RemoteFilesystem;
use ComposerPatcher\Exception;

/**
 * A helper class that resolves a single path.
 */
class PathResolver
{
    /**
     * The temporary directory that will hold downloaded patches.
     *
     * @var \ComposerPatcher\Util\VolatileDirectory
     */
    private $volatileDirectory;

    /**
     * The Filesystem instance to be used to work with local files.
     *
     * @var \Composer\Util\Filesystem
     */
    private $filesystem;

    /**
     * The RemoteFilesystem instance to be used to work with remote files.
     *
     * @var \Composer\Util\RemoteFilesystem
     */
    private $remoteFilesystem;

    /**
     * A list of already resolved remote paths.
     *
     * @var array
     */
    private $resolvedRemotePath = array();

    /**
     * Initialize the instance.
     *
     * @param \ComposerPatcher\Util\VolatileDirectory $volatileDirectory the temporary directory that will hold downloaded patches
     * @param \Composer\Util\RemoteFilesystem $remoteFilesystem the RemoteFilesystem instance to be used to work with remote files
     */
    public function __construct(VolatileDirectory $volatileFolder, RemoteFilesystem $remoteFilesystem)
    {
        $this->volatileFolder = $volatileFolder;
        $this->remoteFilesystem = $remoteFilesystem;
        $this->filesystem = $volatileFolder->getFilesystem();
    }

    /**
     * Resolve a local or remote path.
     *
     * @param string $path The path to be resolved
     * @param string $baseFolder the absolute path to be used when $path is a local relative path
     *
     * @throws \ComposerPatcher\Exception\PathNotFound when $path is not found
     *
     * @return string Empty string if $path is not a string or if it's an empty string
     */
    public function resolve($path, $baseFolder)
    {
        if (!\is_string($path)) {
            return '';
        }
        if (stripos($path, 'file://') === 0) {
            $path = substr($path, 7);
        }
        if ($path === '') {
            return '';
        }
        if (preg_match('_^\w\w+://_', $path)) {
            return $this->resolveRemotePath($path);
        }

        return $this->resolveLocalPath($path, $baseFolder);
    }

    /**
     * Resolve a local path.
     *
     * @param string $path The path to be resolved
     * @param string $baseFolder the absolute path to be used when $path is a local relative path
     *
     * @throws \ComposerPatcher\Exception\PathNotFound when $path is not found
     *
     * @return string
     */
    private function resolveLocalPath($path, $baseFolder)
    {
        $path = $this->filesystem->normalizePath($path);
        if ($this->filesystem->isAbsolutePath($path)) {
            return $path;
        }
        if (stripos($baseFolder, 'file://') === 0) {
            $baseFolder = substr($baseFolder, 7);
        }
        $baseFolder = $this->filesystem->normalizePath($baseFolder);

        return $baseFolder.'/'.$path;
    }

    /**
     * Resolve a local path.
     *
     * @param string $path The path to be resolved
     *
     * @throws \ComposerPatcher\Exception\PathNotFound when $path is not found
     *
     * @return string
     */
    private function resolveRemotePath($path)
    {
        if (!isset($this->resolvedRemotePath[$path])) {
            $resolvedPath = $this->volatileFolder->getNewPath();
            if ($this->remoteFilesystem->copy($path, $path, $resolvedPath) !== true) {
                throw new Exception\PathNotFound($path);
            }
            $this->resolvedRemotePath[$path] = $resolvedPath;
        }

        return $this->resolvedRemotePath[$path];
    }
}
<?php

namespace ComposerPatcher\PatchExecutor;

use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
use ComposerPatcher\Exception;
use ComposerPatcher\Patch;
use ComposerPatcher\PatchExecutor;
use ComposerPatcher\Util\VolatileDirectory;

class PatchPatcher extends PatchExecutor
{
    /**
     * The actual patch command to be executed.
     *
     * @var string
     */
    private $command;

    /**
     * The VolatileDirectory instance to use to store temporary files.
     *
     * @var \ComposerPatcher\Util\VolatileDirectory
     */
    private $volatileDirectory;

    /**
     * Initialize the instance.
     *
     * @param \Composer\Util\ProcessExecutor $processExecutor the ProcessExecutor instance to be used to actually run commands
     * @param \Composer\IO\IOInterface $io the IOInterface instance to be used for user feedback
     * @param \ComposerPatcher\Util\VolatileDirectory $volatileDirectory the VolatileDirectory instance to use to store temporary files
     *
     * @throws \ComposerPatcher\Exception\CommandNotFound when the patch command is not available
     */
    public function __construct(ProcessExecutor $processExecutor, IOInterface $io, VolatileDirectory $volatileDirectory)
    {
        parent::__construct($processExecutor, $io);
        $this->volatileDirectory = $volatileDirectory;
        if (\DIRECTORY_SEPARATOR === '\\') {
            $this->initializeWindows();
        } else {
            $this->initializePosix();
        }
    }

    /**
     * Check if a patch is already applied.
     *
     * @param \ComposerPatcher\Patch $patch the patch to be analyzed
     * @param string $baseDirectory the directory where the patch should be applied
     * @param string $patchLevel the patch level
     *
     * @return bool
     */
    public function patchIsAlreadyApplied(Patch $patch, $baseDirectory, $patchLevel)
    {
        $command = $this->buildCommand($patch->getLocalPath(), $baseDirectory, $patchLevel, true);
        list($rc, $stdOut, $stdErr) = $this->run($command);
        if ($stdErr === '') {
            $stdErr = $stdOut;
        }
        if ($rc === 1 && strpos($stdErr, 'Skipping patch.') !== false && preg_match('/^(\\d+) out of \\1 hunks? ignored/m', $stdErr)) {
            return true;
        }

        return false;
    }

    /**
     * {@inheritdoc}
     *
     * @see \ComposerPatcher\PatchExecutor::applyPatchLevel()
     */
    protected function applyPatchLevel(Patch $patch, $baseDirectory, $patchLevel)
    {
        if ($this->patchIsAlreadyApplied($patch, $baseDirectory, $patchLevel)) {
            throw new Exception\PatchAlreadyApplied($patch);
        }
        $command = $this->buildCommand($patch->getLocalPath(), $baseDirectory, $patchLevel, false);
        list($rc, $stdOut, $stdErr) = $this->run($command);
        if ($rc === 0) {
            return;
        }
        if ($stdErr === '') {
            $stdErr = $stdOut;
        }
        throw new Exception\PatchNotApplied($patch, "failed to apply the patch with the patch command: {$stdErr}");
    }

    /**
     * Configure the instance for POSIX.
     *
     * @throws \ComposerPatcher\Exception\CommandNotFound when the patch command is not available
     */
    private function initializePosix()
    {
        $this->checkCommandExists('patch');
        $this->command = 'patch';
    }

    /**
     * Configure the instance for Windows.
     *
     * @throws \ComposerPatcher\Exception\CommandNotFound when the patch command is not available
     */
    private function initializeWindows()
    {
        $gitFolder = $this->findWindowsGitFolder();
        if ($gitFolder !== '') {
            $patchPath = "{$gitFolder}\\usr\\bin\\patch.exe";
            if (is_file($patchPath)) {
                try {
                    $patchPath = escapeshellarg($patchPath);
                    $this->checkCommandExists($patchPath);
                    $this->command = $patchPath;

                    return;
                } catch (Exception\CommandNotFound $x) {
                }
            }
        }
        try {
            $this->checkCommandExists('patch');
            $this->command = 'patch';

            return;
        } catch (Exception\CommandNotFound $x) {
        }
        throw new Exception\CommandNotFound('patch', <<<'EOT'
The patch command is currently not available.
You have these options:
1. Install Git for Windows, and add it to the PATH environment variable
2. Find a Windows port of the "patch" GNU utility and it to a directory in the current PATH
EOT
        );
    }

    /**
     * Search the path where git is installed on Windows.
     *
     * @return string Empty string if not found
     */
    private function findWindowsGitFolder()
    {
        list($rc, $stdOut) = $this->run('where git.exe');
        if ($rc !== 0) {
            return '';
        }
        $stdOut = str_replace("\r\n", "\n", $stdOut);
        foreach (explode("\n", trim($stdOut)) as $path) {
            $easierPath = str_replace('\\', '/', $path);
            $match = null;
            if (preg_match('_^(.+)/(?:cmd|bin)/git\.exe$_i', $easierPath, $match)) {
                return str_replace('/', '\\', $match[1]);
            }
        }

        return '';
    }

    /**
     * @param string $localPatchFile
     * @param string $baseDirectory
     * @param string $patchLevel
     * @param bool $dryRun
     *
     * @return string
     */
    private function buildCommand($localPatchFile, $baseDirectory, $patchLevel, $dryRun)
    {
        $chunks = array(
            $this->command,
            $this->escape($patchLevel),
            // Back up mismatches only if otherwise requested
            '--no-backup-if-mismatch',
            // Ignore patches where the differences have already been applied to the file (aka --forward)
            '-N',
            // Change the working directory (aka --directory)
            '-d', $this->escape(str_replace('/', \DIRECTORY_SEPARATOR, $baseDirectory)),
            // Read patch from PATCHFILE instead of stdin (aka --input)
            '-i', $this->escape(str_replace('/', \DIRECTORY_SEPARATOR, $localPatchFile)),
            // Output rejects to FILE (aka --reject-file)
            '-r', $this->escape(str_replace('/', \DIRECTORY_SEPARATOR, $this->volatileDirectory->getNewPath('.rej'))),
        );
        if ($dryRun) {
            // Do not actually change any files; just print what would happen.
            $chunks[] = '--dry-run';
        }

        return implode(' ', $chunks);
    }
}
<?php

namespace ComposerPatcher\PatchExecutor;

use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
use ComposerPatcher\Exception;
use ComposerPatcher\Patch;
use ComposerPatcher\PatchExecutor;
use ComposerPatcher\Util\VolatileDirectory;

class GitPatcher extends PatchExecutor
{
    /**
     * @var \ComposerPatcher\PatchExecutor\PatchPatcher
     */
    private $patchPatcher;

    /**
     * Initialize the instance.
     *
     * @param \Composer\Util\ProcessExecutor $processExecutor the ProcessExecutor instance to be used to actually run commands
     * @param \Composer\IO\IOInterface $io the IOInterface instance to be used for user feedback
     * @param \ComposerPatcher\Util\VolatileDirectory $volatileDirectory the VolatileDirectory instance to use to store temporary files
     *
     * @throws \ComposerPatcher\Exception\CommandNotFound when the git command is not available
     */
    public function __construct(ProcessExecutor $processExecutor, IOInterface $io, VolatileDirectory $volatileDirectory)
    {
        parent::__construct($processExecutor, $io);
        $this->checkCommandExists('git');
        $this->patchPatcher = new PatchPatcher($processExecutor, $io, $volatileDirectory);
    }

    /**
     * Check if this PatchExecutor class should be used to apply patches to a directory.
     *
     * @param string $baseDirectory
     *
     * @return bool
     */
    public static function shouldBeUsetToApplyPatchesTo($baseDirectory)
    {
        return is_dir("${baseDirectory}/.git");
    }

    /**
     * {@inheritdoc}
     *
     * @see \ComposerPatcher\PatchExecutor::applyPatchLevel()
     */
    protected function applyPatchLevel(Patch $patch, $baseDirectory, $patchLevel)
    {
        if ($this->patchPatcher->patchIsAlreadyApplied($patch, $baseDirectory, $patchLevel)) {
            throw new Exception\PatchAlreadyApplied($patch);
        }
        foreach (array(true, false) as $justTesting) {
            $command = $this->buildCommand($patch->getLocalPath(), $baseDirectory, $patchLevel, $justTesting);
            if ($justTesting && $this->io->isVerbose()) {
                $this->io->write("<comment>Testing ability to patch with \"git apply\" using patch level {$patchLevel} with the following command:\n{$command}</comment>");
            }
            list($rc, , $stdErr) = $this->run($command);
            if (0 !== $rc) {
                throw new Exception\PatchNotApplied($patch, "failed to apply the patch with GIT: {$stdErr}");
            }
            if (0 === strpos($stdErr, 'Skipped')) {
                return;
            }
        }
    }

    /**
     * @param string $localPatchFile
     * @param string $baseDirectory
     * @param string $patchLevel
     * @param bool $justTesting
     *
     * @return string
     */
    private function buildCommand($localPatchFile, $baseDirectory, $patchLevel, $justTesting)
    {
        $chunks = array(
            'git',
            // Run git as if it was started in $baseDirectory
            '-C ', $this->escape(str_replace('/', \DIRECTORY_SEPARATOR, $baseDirectory)),
            'apply',
            $this->escape($patchLevel),
        );
        if ($justTesting) {
            $chunks[] = '--check';
            $chunks[] = '-v';
        }
        $chunks[] = $this->escape(str_replace('/', \DIRECTORY_SEPARATOR, $localPatchFile));

        return implode(' ', $chunks);
    }
}
<?php

namespace ComposerPatcher;

use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;

abstract class PatchExecutor
{
    /**
     * The IOInterface instance to be used for user feedback.
     *
     * @var \Composer\IO\IOInterface
     */
    protected $io;
    /**
     * The ProcessExecutor instance to be used to actually run commands.
     *
     * @var \Composer\Util\ProcessExecutor
     */
    protected $processExecutor;

    /**
     * Initialize the instance.
     *
     * @param \Composer\Util\ProcessExecutor $processExecutor the ProcessExecutor instance to be used to actually run commands
     * @param \Composer\IO\IOInterface $io the IOInterface instance to be used for user feedback
     */
    protected function __construct(ProcessExecutor $processExecutor, IOInterface $io)
    {
        $this->processExecutor = $processExecutor;
        $this->io = $io;
    }

    /**
     * Apply a patch to a base directory.
     *
     * @param \ComposerPatcher\Patch $patch the patch to be applied
     * @param string $baseDirectory the directory where the patch should be applied
     *
     * @throws \ComposerPatcher\Exception\PatchAlreadyApplied if the patch is already applied
     * @throws \ComposerPatcher\Exception\PatchNotApplied if the patch could not be applied
     *
     * @return bool
     */
    public function applyPatch(Patch $patch, $baseDirectory)
    {
        $error = null;
        foreach ($patch->getLevels() as $patchLevel) {
            try {
                $this->applyPatchLevel($patch, $baseDirectory, $patchLevel);

                return;
            } catch (Exception\PatchAlreadyApplied $x) {
                throw $x;
            } catch (Exception\PatchNotApplied $x) {
                if (null === $error) {
                    $error = $x;
                }
            }
        }
        throw $error;
    }

    /**
     * Apply a patch using a specific patch level to a base directory.
     *
     * @param \ComposerPatcher\Patch $patch the patch to be applied
     * @param string $baseDirectory the directory where the patch should be applied
     * @param string $patchLevel the patch level
     *
     * @throws \ComposerPatcher\Exception\PatchAlreadyApplied if the patch is already applied
     * @throws \ComposerPatcher\Exception\PatchNotApplied when the command failed
     */
    abstract protected function applyPatchLevel(Patch $patch, $baseDirectory, $patchLevel);

    /**
     * Run a command and return the exit code.
     *
     * @param string $command The command to be executed
     * @param mixed|null $standardInput
     *
     * @return array First element is the command return code, second element is the standard output, thirg element is the standard error
     */
    protected function run($command, $standardInput = null)
    {
        $stdOut = '';
        $stdErr = '';
        $stdOut = array();
        $rc = $this->processExecutor->execute($command, $stdOut);
        $stdOut = (string) $stdOut;
        $stdErr = (string) $this->processExecutor->getErrorOutput();

        return array($rc, $stdOut, $stdErr);
    }

    /**
     * Check if a command exists.
     *
     * @param string $commandName the command name
     * @param string $argument the argument to be used to run the command
     *
     * @throws \ComposerPatcher\Exception\CommandNotFound
     */
    protected function checkCommandExists($commandName, $argument = '--version')
    {
        $command = $commandName;
        $argument = (string) $argument;
        if ('' !== $argument) {
            $command .= ' '.$argument;
        }
        list($rc, , $stdErr) = $this->run($command);
        if (0 !== $rc) {
            throw new Exception\CommandNotFound($commandName, $stdErr ? $stdErr : null);
        }
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * Copy of ProcessUtils::escapeArgument() that is deprecated in Symfony 3.3 and removed in Symfony 4.
     *
     * @param string $argument
     *
     * @return string
     *
     * @copyright MIT License - Copyright (c) 2004-2018 Fabien Potencier
     */
    protected function escape($argument)
    {
        //Fix for PHP bug #43784 escapeshellarg removes % from given string
        //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
        //@see https://bugs.php.net/bug.php?id=43784
        //@see https://bugs.php.net/bug.php?id=49446
        if ('\\' === \DIRECTORY_SEPARATOR) {
            if ('' === $argument) {
                return escapeshellarg($argument);
            }
            $escapedArgument = '';
            $quote = false;
            foreach (preg_split('/(")/', $argument, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE) as $part) {
                if ('"' === $part) {
                    $escapedArgument .= '\\"';
                } elseif (self::isSurroundedBy($part, '%')) {
                    // Avoid environment variable expansion
                    $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
                } else {
                    // escape trailing backslash
                    if ('\\' === substr($part, -1)) {
                        $part .= '\\';
                    }
                    $quote = true;
                    $escapedArgument .= $part;
                }
            }
            if ($quote) {
                $escapedArgument = '"'.$escapedArgument.'"';
            }

            return $escapedArgument;
        }

        return "'".str_replace("'", "'\\''", $argument)."'";
    }

    private static function isSurroundedBy($arg, $char)
    {
        return 2 < \strlen($arg) && $char === $arg[0] && $char === $arg[\strlen($arg) - 1];
    }
}
<?php

namespace ComposerPatcher;

use Composer\Composer;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event as ComposerEvent;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use ComposerPatcher\Util\PathResolver;
use Exception as GenericException;

class Plugin implements PluginInterface, EventSubscriberInterface
{
    /**
     * @var \Composer\Composer
     */
    private $composer;

    /**
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * @var \ComposerPatcher\PatchCollection|null
     */
    private $patchCollection;

    /**
     * @var \ComposerPatcher\Util\VolatileDirectory|null
     */
    private $volatileDirectory;

    /**
     * @var \Composer\Util\RemoteFilesystem|null
     */
    private $remoteFilesystem;

    /**
     * @var \ComposerPatcher\Util\PathResolver|null
     */
    private $pathResolver;

    /**
     * @var bool|null
     */
    private $considerPatchErrorsAsWarnings;

    /**
     * @var \ComposerPatcher\PatchCollector|null
     */
    private $patchCollector;

    /**
     * @var \ComposerPatcher\Patcher|null
     */
    private $patcher;

    /**
     * {@inheritdoc}
     *
     * @see \Composer\Plugin\PluginInterface::activate()
     */
    public function activate(Composer $composer, IOInterface $io)
    {
        $this->composer = $composer;
        $this->io = $io;
    }

    /**
     * {@inheritdoc}
     *
     * @see \Composer\Plugin\PluginInterface::deactivate()
     */
    public function deactivate(Composer $composer, IOInterface $io)
    {
    }

    /**
     * {@inheritdoc}
     *
     * @see \Composer\Plugin\PluginInterface::uninstall()
     */
    public function uninstall(Composer $composer, IOInterface $io)
    {
    }

    /**
     * {@inheritdoc}
     *
     * @see \Composer\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
     */
    public static function getSubscribedEvents()
    {
        return array(
            ScriptEvents::PRE_INSTALL_CMD => array('preOperation'),
            ScriptEvents::PRE_UPDATE_CMD => array('preOperation'),
            ScriptEvents::POST_INSTALL_CMD => array('postOperation'),
            ScriptEvents::POST_UPDATE_CMD => array('postOperation'),
        );
    }

    /**
     * Before running composer install/update, let's uninstall some packages.
     */
    public function preOperation(ComposerEvent $event)
    {
        $patchCollection = $this->getPatchCollection();
        if ($patchCollection->isEmpty()) {
            return;
        }
        $packages = $this->getLocalRepositoryPackages();
        foreach ($packages as $package) {
            if ($this->mustUninstallPackage($package)) {
                $this->uninstallPackage($package);
            }
        }
    }

    /**
     * After running composer install/update, let's patch some packages.
     *
     * @throws \Exception
     */
    public function postOperation(ComposerEvent $event)
    {
        $this->refreshPatchCollection();
        $patchCollection = $this->getPatchCollection();
        if ($patchCollection->isEmpty()) {
            $this->io->write('<info>No patches supplied.</info>');

            return;
        }
        $packages = $this->getLocalRepositoryPackages();
        foreach ($packages as $package) {
            if ($this->mustInstallPatchesForPackage($package)) {
                $this->installPatchesForPackage($package, $event->isDevMode());
            }
        }
    }

    /**
     * @return string
     */
    protected function getPatchTemporaryFolder()
    {
        $result = '';
        $extra = $this->composer->getPackage()->getExtra();
        if (isset($extra['patch-temporary-folder'])) {
            $ptf = $extra['patch-temporary-folder'];
            if ($ptf !== null && $ptf !== '') {
                if (!\is_string($ptf)) {
                    $this->io->write("<error>The value of extra.patch-temporary-folder must be a string: we'll use the system temporary folder.</error>");
                } else {
                    $ptf = str_replace(\DIRECTORY_SEPARATOR, '/', $ptf);
                    if ($ptf !== '/') {
                        $ptf = rtrim($ptf, '/');
                    }
                    if (!is_dir($ptf)) {
                        $ptf = str_replace('/', \DIRECTORY_SEPARATOR, $ptf);
                        $this->io->write("<error>The value of extra.patch-temporary-folder '{$ptf}' does not exist: we\'ll use the system temporary folder.</error>");
                    } elseif (!is_writable($ptf)) {
                        $ptf = str_replace('/', \DIRECTORY_SEPARATOR, $ptf);
                        $this->io->write("<error>The value of extra.patch-temporary-folder '{$ptf}' is not writable: we\'ll use the system temporary folder.</error>");
                    } else {
                        $result = $ptf;
                    }
                }
            }
        }

        return $result;
    }

    /**
     * @return \ComposerPatcher\Util\VolatileDirectory
     */
    protected function getVolatileDirectory()
    {
        if ($this->volatileDirectory === null) {
            $this->volatileDirectory = new Util\VolatileDirectory($this->getPatchTemporaryFolder());
        }

        return $this->volatileDirectory;
    }

    /**
     * @return \Composer\Util\RemoteFilesystem
     */
    protected function getRemoteFilesystem()
    {
        if ($this->remoteFilesystem === null) {
            $this->remoteFilesystem = new RemoteFilesystem($this->io, $this->composer->getConfig());
        }

        return $this->remoteFilesystem;
    }

    /**
     * @return \ComposerPatcher\Util\PathResolver
     */
    protected function getPathResolver()
    {
        if ($this->pathResolver === null) {
            $this->pathResolver = new PathResolver($this->getVolatileDirectory(), $this->getRemoteFilesystem());
        }

        return $this->pathResolver;
    }

    /**
     * @return bool
     */
    protected function considerPatchErrorsAsWarnings()
    {
        if ($this->considerPatchErrorsAsWarnings === null) {
            $extra = $this->composer->getPackage()->getExtra();
            if (isset($extra['patch-errors-as-warnings'])) {
                $this->considerPatchErrorsAsWarnings = (bool) $extra['patch-errors-as-warnings'];
            } else {
                $this->considerPatchErrorsAsWarnings = true;
            }
        }

        return $this->considerPatchErrorsAsWarnings;
    }

    /**
     * @return \ComposerPatcher\PatchCollector
     */
    protected function getPatchCollector()
    {
        if ($this->patchCollector === null) {
            $this->patchCollector = new PatchCollector($this->getPathResolver(), $this->io, $this->composer->getInstallationManager(), $this->considerPatchErrorsAsWarnings());
        }

        return $this->patchCollector;
    }

    /**
     * The list of patches to be applied.
     *
     * @return \ComposerPatcher\PatchCollection
     */
    protected function getPatchCollection()
    {
        if ($this->patchCollection === null) {
            $patchCollector = $this->getPatchCollector();
            $packages = $this->getLocalRepositoryPackages(true);
            $this->patchCollection = $patchCollector->collectPatches($this->composer->getPackage(), $packages);
        }

        return $this->patchCollection;
    }

    /**
     * Reset the patch collection, so that the next call to getPatchCollection() will return fresh data.
     */
    protected function refreshPatchCollection()
    {
        $this->patchCollection = null;
    }

    /**
     * Get the Patcher instance.
     *
     * @return \ComposerPatcher\Patcher
     */
    protected function getPatcher()
    {
        if ($this->patcher === null) {
            $this->patcher = new Patcher($this->io, $this->composer->getInstallationManager(), $this->composer->getEventDispatcher(), new ProcessExecutor($this->io), $this->getVolatileDirectory());
        }

        return $this->patcher;
    }

    /**
     * Check if a package must be uninstalled before the install/update commands.
     *
     * @param \Composer\Package\PackageInterface $package the package to be checked
     *
     * @return bool
     */
    protected function mustUninstallPackage(PackageInterface $package)
    {
        if ($package instanceof AliasPackage) {
            return false;
        }
        $packageExtra = $package->getExtra();
        $packageHasAppliedPatches = !empty($packageExtra['patches_applied']);
        $packagePatches = $this->getPatchCollection()->getPatchesFor($package);
        $packageHasPatches = !$packagePatches->isEmpty();
        if ($packageHasAppliedPatches === false && $packageHasPatches === false) {
            return false;
        }
        if ($packageHasAppliedPatches === true && $packageHasPatches === true) {
            $installedHash = empty($packageExtra['patches_applied']['hash']) ? '' : $packageExtra['patches_applied']['hash'];
            if ($installedHash === '') {
                return true;
            }
            $patchesHash = $packagePatches->getHash();
            if ($installedHash !== $patchesHash) {
                return true;
            }
        }

        return false;
    }

    /**
     * Should we apply patches to the package?
     *
     * @param \Composer\Package\PackageInterface $package the package to be patched
     *
     * @return bool
     */
    protected function mustInstallPatchesForPackage(PackageInterface $package)
    {
        $result = false;
        $patches = $this->getPatchCollection()->getPatchesFor($package);
        if ($patches->isEmpty()) {
            if ($this->io->isVerbose()) {
                $this->io->write('<info>No patches found for '.$package->getName().'.</info>');
            }
        } else {
            $result = true;
        }

        return $result;
    }

    /**
     * Install the patches for a specific package.
     *
     * @param \Composer\Package\PackageInterface $package the package to be patched
     * @param bool $isDevMode
     */
    protected function installPatchesForPackage(PackageInterface $package, $isDevMode)
    {
        $considerPatchErrorsAsWarnings = $this->considerPatchErrorsAsWarnings();
        $patches = $this->getPatchCollection()->getPatchesFor($package);
        $patcher = $this->getPatcher();
        $appliedPatchesToSave = method_exists($package, 'setExtra') ? array() : null;
        foreach ($patches->getPatches() as $patch) {
            try {
                $patcher->applyPatch($patch, $package);
                if ($appliedPatchesToSave !== null) {
                    $appliedPatchesToSave[] = array(
                        'from-package' => $patch->getFromPackage()->getPrettyString(),
                        'path' => $patch->getOriginalPath(),
                        'description' => $patch->getDescription(),
                    );
                }
            } catch (GenericException $x) {
                $appliedPatchesToSave = null;
                if (!$considerPatchErrorsAsWarnings) {
                    throw $x;
                }
                $this->io->write('<error>'.$x->getMessage().'</error>');
            }
        }
        if ($appliedPatchesToSave !== null) {
            $this->setPatchedPackageData($package, array(
                'hash' => $patches->getHash(),
                'list' => $appliedPatchesToSave,
            ), $isDevMode);
        }
    }

    /**
     * Persist the patches_applied to the package extra section.
     *
     * @param bool $isDevMode
     */
    protected function setPatchedPackageData(PackageInterface $package, array $patchesAppliedData, $isDevMode)
    {
        $extra = $package->getExtra();
        $extra['patches_applied'] = $patchesAppliedData;
        $package->setExtra($extra);
        if (\defined('Composer\Composer::RUNTIME_API_VERSION') && version_compare(Composer::RUNTIME_API_VERSION, '2') >= 0) {
            $this->composer->getRepositoryManager()->getLocalRepository()->write($isDevMode, $this->composer->getInstallationManager());
        } else {
            $this->composer->getRepositoryManager()->getLocalRepository()->write();
        }
    }

    /**
     * Uninstall a package.
     *
     * @param \Composer\Package\PackageInterface $package the package to be uninstalled
     */
    protected function uninstallPackage(PackageInterface $package)
    {
        $uninstallOperation = new UninstallOperation($package, 'Removing package so it can be re-installed and re-patched.');
        $this->io->write('<info>Removing package '.$package->getName().' so that it can be re-installed and re-patched.</info>');
        $installationManager = $this->composer->getInstallationManager();
        $repositoryManager = $this->composer->getRepositoryManager();
        $localRepository = $repositoryManager->getLocalRepository();
        $installationManager->uninstall($localRepository, $uninstallOperation);
    }

    /**
     * Get the packages from the local Composer repository.
     *
     * @param mixed $excludeRootPackage
     *
     * @return \Composer\Package\PackageInterface[]
     */
    protected function getLocalRepositoryPackages($excludeRootPackage = false)
    {
        $repositoryManager = $this->composer->getRepositoryManager();
        $localRepository = $repositoryManager->getLocalRepository();
        $packages = $localRepository->getPackages();
        $rootRepository = null;
        $result = array();
        foreach ($packages as $package) {
            if ($package instanceof RootPackageInterface) {
                $rootRepository = $package;
                if (!$excludeRootPackage) {
                    $result[] = $package;
                }
            } else {
                $result[] = $package;
            }
        }
        if (!$excludeRootPackage && $rootRepository === null) {
            $result[] = $this->composer->getPackage();
        }

        return $result;
    }
}
<?php

namespace ComposerPatcher;

use Composer\Package\PackageInterface;

/**
 * The class that contains the collected patches.
 */
class PatchCollection
{
    /**
     * The list of collected patches.
     *
     * @var \ComposerPatcher\Patch[]
     */
    private $patches = array();

    /**
     * Add a collected patch.
     *
     * @param \ComposerPatcher\Patch $patch
     *
     * @return $this
     */
    public function addPatch(Patch $patch)
    {
        $this->patches[] = $patch;

        return $this;
    }

    /**
     * Get the list of collected patches.
     *
     * @return \ComposerPatcher\Patch[]
     */
    public function getPatches()
    {
        return $this->patches;
    }

    /**
     * Is this collection empty?
     *
     * @return bool
     */
    public function isEmpty()
    {
        return !isset($this->patches[0]);
    }

    /**
     * Merge another patch collection into this collection.
     *
     * @param \ComposerPatcher\PatchCollection $otherCollection
     *
     * @return $this
     */
    public function merge(self $otherCollection)
    {
        $this->patches = array_merge($this->patches, $otherCollection->getPatches());

        return $this;
    }

    /**
     * Check if this collection contains patches for a package.
     *
     * @param \Composer\Package\PackageInterface $package a PackageInterface instance
     *
     * @return bool
     */
    public function containsPatchesFor(PackageInterface $package)
    {
        foreach ($this->getPatches() as $patch) {
            if ($patch->isForPackage($package)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get the patches to be applied to a package.
     *
     * @param \Composer\Package\PackageInterface $package a PackageInterface instance
     *
     * @return static
     */
    public function getPatchesFor(PackageInterface $package)
    {
        $patches = array();
        foreach ($this->getPatches() as $patch) {
            if ($patch->isForPackage($package)) {
                $patches[] = $patch;
            }
        }
        $result = new static();
        $result->patches = $patches;

        return $result;
    }

    /**
     * Get the hash of all the patches.
     *
     * @return string
     */
    public function getHash()
    {
        $hashes = array();

        foreach ($this->getPatches() as $patch) {
            $hashes[] = $patch->getHash();
        }
        sort($hashes);

        return sha1(implode(' ', $hashes));
    }
}
<?php

// platform_check.php @generated by Composer

$issues = array();

if (!(PHP_VERSION_ID >= 70205)) {
    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.';
}

if ($issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
        }
    }
    trigger_error(
        'Composer detected issues in your platform: ' . implode(' ', $issues),
        E_USER_ERROR
    );
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit8e4de4eb796111b7004a7747b952dd58
{
    private static $loader;

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

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

        require __DIR__ . '/platform_check.php';

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

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

        $loader->register(true);

        $includeFiles = \Composer\Autoload\ComposerStaticInit8e4de4eb796111b7004a7747b952dd58::$files;
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire8e4de4eb796111b7004a7747b952dd58($fileIdentifier, $file);
        }

        return $loader;
    }
}

/**
 * @param string $fileIdentifier
 * @param string $file
 * @return void
 */
function composerRequire8e4de4eb796111b7004a7747b952dd58($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

        require $file;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * Provides utility functions to prepare a child process command-line and set
 * environment variables in that process.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Process
{
    /**
     * Returns an array of parameters, including a color option if required
     *
     * A color option is needed because child process output is piped.
     *
     * @param array $args The script parameters
     * @param string $colorOption The long option to force color output
     *
     * @return array
     */
    public static function addColorOption(array $args, $colorOption)
    {
        if (!$colorOption
            || in_array($colorOption, $args)
            || !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) {
            return $args;
        }

        if (isset($matches[2])) {
            // Handle --color(s)= options
            if (false !== ($index = array_search($matches[2].'auto', $args))) {
                $args[$index] = $colorOption;
                return $args;
            } elseif (preg_grep('/^'.$matches[2].'/', $args)) {
                return $args;
            }
        } elseif (in_array('--no-'.$matches[1], $args)) {
            return $args;
        }

        // Check for NO_COLOR variable (https://no-color.org/)
        if (false !== getenv('NO_COLOR')) {
            return $args;
        }

        if (false !== ($index = array_search('--', $args))) {
            // Position option before double-dash delimiter
            array_splice($args, $index, 0, $colorOption);
        } else {
            $args[] = $colorOption;
        }

        return $args;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $arg  The argument to be escaped
     * @param bool   $meta Additionally escape cmd.exe meta characters
     * @param bool $module The argument is the module to invoke
     *
     * @return string The escaped argument
     */
    public static function escape($arg, $meta = true, $module = false)
    {
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            return "'".str_replace("'", "'\\''", $arg)."'";
        }

        $quote = strpbrk($arg, " \t") !== false || $arg === '';

        $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);

        if ($meta) {
            $meta = $dquotes || preg_match('/%[^%]+%/', $arg);

            if (!$meta) {
                $quote = $quote || strpbrk($arg, '^&|<>()') !== false;
            } elseif ($module && !$dquotes && $quote) {
                $meta = false;
            }
        }

        if ($quote) {
            $arg = '"'.preg_replace('/(\\\\*)$/', '$1$1', $arg).'"';
        }

        if ($meta) {
            $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
        }

        return $arg;
    }

    /**
     * Returns true if the output stream supports colors
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * @param mixed $output A valid CLI output stream
     *
     * @return bool
     */
    public static function supportsColor($output)
    {
        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            return (function_exists('sapi_windows_vt100_support')
                && sapi_windows_vt100_support($output))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        if (function_exists('stream_isatty')) {
            return stream_isatty($output);
        }

        if (function_exists('posix_isatty')) {
            return posix_isatty($output);
        }

        $stat = fstat($output);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }

    /**
     * Makes putenv environment changes available in $_SERVER and $_ENV
     *
     * @param string $name
     * @param string|false $value A false value unsets the variable
     *
     * @return bool Whether the environment variable was set
     */
    public static function setEnv($name, $value = false)
    {
        $unset = false === $value;

        if (!putenv($unset ? $name : $name.'='.$value)) {
            return false;
        }

        if ($unset) {
            unset($_SERVER[$name]);
        } else {
            $_SERVER[$name] = $value;
        }

        // Update $_ENV if it is being used
        if (false !== stripos((string) ini_get('variables_order'), 'E')) {
            if ($unset) {
                unset($_ENV[$name]);
            } else {
                $_ENV[$name] = $value;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Status
{
    const ENV_RESTART = 'XDEBUG_HANDLER_RESTART';
    const CHECK = 'Check';
    const ERROR = 'Error';
    const INFO = 'Info';
    const NORESTART = 'NoRestart';
    const RESTART = 'Restart';
    const RESTARTING = 'Restarting';
    const RESTARTED = 'Restarted';

    private $debug;
    private $envAllowXdebug;
    private $loaded;
    private $logger;
    private $time;

    /**
     * Constructor
     *
     * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name
     * @param bool $debug Whether debug output is required
     */
    public function __construct($envAllowXdebug, $debug)
    {
        $start = getenv(self::ENV_RESTART);
        Process::setEnv(self::ENV_RESTART);
        $this->time = $start ? round((microtime(true) - $start) * 1000) : 0;

        $this->envAllowXdebug = $envAllowXdebug;
        $this->debug = $debug && defined('STDERR');
    }

    /**
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * Calls a handler method to report a message
     *
     * @param string $op The handler constant
     * @param null|string $data Data required by the handler
     */
    public function report($op, $data)
    {
        if ($this->logger || $this->debug) {
            call_user_func(array($this, 'report'.$op), $data);
        }
    }

    /**
     * Outputs a status message
     *
     * @param string $text
     * @param string $level
     */
    private function output($text, $level = null)
    {
        if ($this->logger) {
            $this->logger->log($level ?: LogLevel::DEBUG, $text);
        }

        if ($this->debug) {
            fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL));
        }
    }

    private function reportCheck($loaded)
    {
        $this->loaded = $loaded;
        $this->output('Checking '.$this->envAllowXdebug);
    }

    private function reportError($error)
    {
        $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING);
    }

    private function reportInfo($info)
    {
        $this->output($info);
    }

    private function reportNoRestart()
    {
        $this->output($this->getLoadedMessage());

        if ($this->loaded) {
            $text = sprintf('No restart (%s)', $this->getEnvAllow());
            if (!getenv($this->envAllowXdebug)) {
                $text .= ' Allowed by application';
            }
            $this->output($text);
        }
    }

    private function reportRestart()
    {
        $this->output($this->getLoadedMessage());
        Process::setEnv(self::ENV_RESTART, (string) microtime(true));
    }

    private function reportRestarted()
    {
        $loaded = $this->getLoadedMessage();
        $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded);
        $level = $this->loaded ? LogLevel::WARNING : null;
        $this->output($text, $level);
    }

    private function reportRestarting($command)
    {
        $text = sprintf('Process restarting (%s)', $this->getEnvAllow());
        $this->output($text);
        $text = 'Running '.$command;
        $this->output($text);
    }

    /**
     * Returns the _ALLOW_XDEBUG environment variable as name=value
     *
     * @return string
     */
    private function getEnvAllow()
    {
        return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug);
    }

    /**
     * Returns the Xdebug status and version
     *
     * @return string
     */
    private function getLoadedMessage()
    {
        $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded';
        return 'The Xdebug extension is '.$loaded;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class XdebugHandler
{
    const SUFFIX_ALLOW = '_ALLOW_XDEBUG';
    const SUFFIX_INIS = '_ORIGINAL_INIS';
    const RESTART_ID = 'internal';
    const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS';
    const DEBUG = 'XDEBUG_HANDLER_DEBUG';

    /** @var string|null */
    protected $tmpIni;

    private static $inRestart;
    private static $name;
    private static $skipped;

    private $cli;
    private $colorOption;
    private $debug;
    private $envAllowXdebug;
    private $envOriginalInis;
    private $loaded;
    private $persistent;
    private $script;
    /** @var Status|null */
    private $statusWriter;

    /**
     * Constructor
     *
     * The $envPrefix is used to create distinct environment variables. It is
     * uppercased and prepended to the default base values. For example 'myapp'
     * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS.
     *
     * @param string $envPrefix Value used in environment variables
     * @param string $colorOption Command-line long option to force color output
     * @throws \RuntimeException If a parameter is invalid
     */
    public function __construct($envPrefix, $colorOption = '')
    {
        if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) {
            throw new \RuntimeException('Invalid constructor parameter');
        }

        self::$name = strtoupper($envPrefix);
        $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
        $this->envOriginalInis = self::$name.self::SUFFIX_INIS;

        $this->colorOption = $colorOption;

        if (extension_loaded('xdebug')) {
            $ext = new \ReflectionExtension('xdebug');
            $this->loaded = $ext->getVersion() ?: 'unknown';
        }

        if ($this->cli = PHP_SAPI === 'cli') {
            $this->debug = getenv(self::DEBUG);
        }

        $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug);
    }

    /**
     * Activates status message output to a PSR3 logger
     *
     * @param LoggerInterface $logger
     *
     * @return $this
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->statusWriter->setLogger($logger);
        return $this;
    }

    /**
     * Sets the main script location if it cannot be called from argv
     *
     * @param string $script
     *
     * @return $this
     */
    public function setMainScript($script)
    {
        $this->script = $script;
        return $this;
    }

    /**
     * Persist the settings to keep Xdebug out of sub-processes
     *
     * @return $this
     */
    public function setPersistent()
    {
        $this->persistent = true;
        return $this;
    }

    /**
     * Checks if Xdebug is loaded and the process needs to be restarted
     *
     * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG
     * environment variable to 1. This variable is used internally so that
     * the restarted process is created only once.
     */
    public function check()
    {
        $this->notify(Status::CHECK, $this->loaded);
        $envArgs = explode('|', (string) getenv($this->envAllowXdebug));

        if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) {
            // Restart required
            $this->notify(Status::RESTART);

            if ($this->prepareRestart()) {
                $command = $this->getCommand();
                $this->restart($command);
            }
            return;
        }

        if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) {
            // Restarted, so unset environment variable and use saved values
            $this->notify(Status::RESTARTED);

            Process::setEnv($this->envAllowXdebug);
            self::$inRestart = true;

            if (!$this->loaded) {
                // Skipped version is only set if Xdebug is not loaded
                self::$skipped = $envArgs[1];
            }

            $this->tryEnableSignals();

            // Put restart settings in the environment
            $this->setEnvRestartSettings($envArgs);
            return;
        }

        $this->notify(Status::NORESTART);

        if ($settings = self::getRestartSettings()) {
            // Called with existing settings, so sync our settings
            $this->syncSettings($settings);
        }
    }

    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return array
     */
    public static function getAllIniFiles()
    {
        if (!empty(self::$name)) {
            $env = getenv(self::$name.self::SUFFIX_INIS);

            if (false !== $env) {
                return explode(PATH_SEPARATOR, $env);
            }
        }

        $paths = array((string) php_ini_loaded_file());

        if ($scanned = php_ini_scanned_files()) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }

    /**
     * Returns an array of restart settings or null
     *
     * Settings will be available if the current process was restarted, or
     * called with the settings from an existing restart.
     *
     * @return array|null
     */
    public static function getRestartSettings()
    {
        $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS));

        if (count($envArgs) !== 6
            || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) {
            return null;
        }

        return array(
            'tmpIni' => $envArgs[0],
            'scannedInis' => (bool) $envArgs[1],
            'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2],
            'phprc' => '*' === $envArgs[3] ? false : $envArgs[3],
            'inis' => explode(PATH_SEPARATOR, $envArgs[4]),
            'skipped' => $envArgs[5],
        );
    }

    /**
     * Returns the Xdebug version that triggered a successful restart
     *
     * @return string
     */
    public static function getSkippedVersion()
    {
        return (string) self::$skipped;
    }

    /**
     * Returns true if Xdebug is loaded, or as directed by an extending class
     *
     * @param bool $isLoaded Whether Xdebug is loaded
     *
     * @return bool
     */
    protected function requiresRestart($isLoaded)
    {
        return $isLoaded;
    }

    /**
     * Allows an extending class to access the tmpIni
     *
     * @param string $command
     */
    protected function restart($command)
    {
        $this->doRestart($command);
    }

    /**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param string $command
     */
    private function doRestart($command)
    {
        $this->tryEnableSignals();
        $this->notify(Status::RESTARTING, $command);

        // Prefer proc_open to keep fds intact, because passthru pipes to stdout
        if (function_exists('proc_open')) {
            if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 80000) {
                $command = '"'.$command.'"';
            }
            $process = proc_open($command, array(), $pipes);
            if (is_resource($process)) {
                $exitCode = proc_close($process);
            }
        } else {
            passthru($command, $exitCode);
        }

        if (!isset($exitCode)) {
            // Unlikely that the default shell cannot be invoked
            $this->notify(Status::ERROR, 'Unable to restart process');
            $exitCode = -1;
        } else {
            $this->notify(Status::INFO, 'Restarted process exited '.$exitCode);
        }

        if ($this->debug === '2') {
            $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni);
        } else {
            @unlink($this->tmpIni);
        }

        exit($exitCode);
    }

    /**
     * Returns true if everything was written for the restart
     *
     * If any of the following fails (however unlikely) we must return false to
     * stop potential recursion:
     *   - tmp ini file creation
     *   - environment variable creation
     *
     * @return bool
     */
    private function prepareRestart()
    {
        $error = '';
        $iniFiles = self::getAllIniFiles();
        $scannedInis = count($iniFiles) > 1;
        $tmpDir = sys_get_temp_dir();

        if (!$this->cli) {
            $error = 'Unsupported SAPI: '.PHP_SAPI;
        } elseif (!defined('PHP_BINARY')) {
            $error = 'PHP version is too old: '.PHP_VERSION;
        } elseif (!$this->checkConfiguration($info)) {
            $error = $info;
        } elseif (!$this->checkScanDirConfig()) {
            $error = 'PHP version does not report scanned inis: '.PHP_VERSION;
        } elseif (!$this->checkMainScript()) {
            $error = 'Unable to access main script: '.$this->script;
        } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) {
            $error = $error ?: 'Unable to create temp ini file at: '.$tmpDir;
        } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) {
            $error = 'Unable to set environment variables';
        }

        if ($error) {
            $this->notify(Status::ERROR, $error);
        }

        return empty($error);
    }

    /**
     * Returns true if the tmp ini file was written
     *
     * @param array $iniFiles All ini files used in the current process
     * @param string $tmpDir The system temporary directory
     * @param string $error Set by method if ini file cannot be read
     *
     * @return bool
     */
    private function writeTmpIni(array $iniFiles, $tmpDir, &$error)
    {
        if (!$this->tmpIni = @tempnam($tmpDir, '')) {
            return false;
        }

        // $iniFiles has at least one item and it may be empty
        if (empty($iniFiles[0])) {
            array_shift($iniFiles);
        }

        $content = '';
        $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (($data = @file_get_contents($file)) === false) {
                $error = 'Unable to read ini: '.$file;
                return false;
            }
            $content .= preg_replace($regex, ';$1', $data).PHP_EOL;
        }

        // Merge loaded settings into our ini content, if it is valid
        if ($config = parse_ini_string($content)) {
            $loaded = ini_get_all(null, false);
            $content .= $this->mergeLoadedConfig($loaded, $config);
        }

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= 'opcache.enable_cli=0'.PHP_EOL;

        return @file_put_contents($this->tmpIni, $content);
    }

    /**
     * Returns the restart command line
     *
     * @return string
     */
    private function getCommand()
    {
        $php = array(PHP_BINARY);
        $args = array_slice($_SERVER['argv'], 1);

        if (!$this->persistent) {
            // Use command-line options
            array_push($php, '-n', '-c', $this->tmpIni);
        }

        if (defined('STDOUT') && Process::supportsColor(STDOUT)) {
            $args = Process::addColorOption($args, $this->colorOption);
        }

        $args = array_merge($php, array($this->script), $args);

        $cmd = Process::escape(array_shift($args), true, true);
        foreach ($args as $arg) {
            $cmd .= ' '.Process::escape($arg);
        }

        return $cmd;
    }

    /**
     * Returns true if the restart environment variables were set
     *
     * No need to update $_SERVER since this is set in the restarted process.
     *
     * @param bool $scannedInis Whether there were scanned ini files
     * @param array $iniFiles All ini files used in the current process
     *
     * @return bool
     */
    private function setEnvironment($scannedInis, array $iniFiles)
    {
        $scanDir = getenv('PHP_INI_SCAN_DIR');
        $phprc = getenv('PHPRC');

        // Make original inis available to restarted process
        if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) {
            return false;
        }

        if ($this->persistent) {
            // Use the environment to persist the settings
            if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$this->tmpIni)) {
                return false;
            }
        }

        // Flag restarted process and save values for it to use
        $envArgs = array(
            self::RESTART_ID,
            $this->loaded,
            (int) $scannedInis,
            false === $scanDir ? '*' : $scanDir,
            false === $phprc ? '*' : $phprc,
        );

        return putenv($this->envAllowXdebug.'='.implode('|', $envArgs));
    }

    /**
     * Logs status messages
     *
     * @param string $op Status handler constant
     * @param null|string $data Optional data
     */
    private function notify($op, $data = null)
    {
        $this->statusWriter->report($op, $data);
    }

    /**
     * Returns default, changed and command-line ini settings
     *
     * @param array $loadedConfig All current ini settings
     * @param array $iniConfig Settings from user ini files
     *
     * @return string
     */
    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig)
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            // Value will either be null, string or array (HHVM only)
            if (!is_string($value)
                || strpos($name, 'xdebug') === 0
                || $name === 'apc.mmap_file_mask') {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Double-quote escape each value
                $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL;
            }
        }

        return $content;
    }

    /**
     * Returns true if the script name can be used
     *
     * @return bool
     */
    private function checkMainScript()
    {
        if (null !== $this->script) {
            // Allow an application to set -- for standard input
            return file_exists($this->script) || '--' === $this->script;
        }

        if (file_exists($this->script = $_SERVER['argv'][0])) {
            return true;
        }

        // Use a backtrace to resolve Phar and chdir issues
        $options = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false;
        $trace = debug_backtrace($options);

        if (($main = end($trace)) && isset($main['file'])) {
            return file_exists($this->script = $main['file']);
        }

        return false;
    }

    /**
     * Adds restart settings to the environment
     *
     * @param string[] $envArgs
     */
    private function setEnvRestartSettings($envArgs)
    {
        $settings = array(
            php_ini_loaded_file(),
            $envArgs[2],
            $envArgs[3],
            $envArgs[4],
            getenv($this->envOriginalInis),
            self::$skipped,
        );

        Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings));
    }

    /**
     * Syncs settings and the environment if called with existing settings
     *
     * @param array $settings
     */
    private function syncSettings(array $settings)
    {
        if (false === getenv($this->envOriginalInis)) {
            // Called by another app, so make original inis available
            Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis']));
        }

        self::$skipped = $settings['skipped'];
        $this->notify(Status::INFO, 'Process called with existing restart settings');
    }

    /**
     * Returns true if there are scanned inis and PHP is able to report them
     *
     * php_ini_scanned_files will fail when PHP_CONFIG_FILE_SCAN_DIR is empty.
     * Fixed in 7.1.13 and 7.2.1
     *
     * @return bool
     */
    private function checkScanDirConfig()
    {
        return !(getenv('PHP_INI_SCAN_DIR')
            && !PHP_CONFIG_FILE_SCAN_DIR
            && (PHP_VERSION_ID < 70113
            || PHP_VERSION_ID === 70200));
    }

    /**
     * Returns true if there are no known configuration issues
     *
     * @param string $info Set by method
     * @return bool
     */
    private function checkConfiguration(&$info)
    {
        if (!function_exists('proc_open') && !function_exists('passthru')) {
            $info = 'execution functions have been disabled (proc_open or passthru required)';
            return false;
        }

        if (extension_loaded('uopz') && !ini_get('uopz.disable')) {
            // uopz works at opcode level and disables exit calls
            if (function_exists('uopz_allow_exit')) {
                @uopz_allow_exit(true);
            } else {
                $info = 'uopz extension is not compatible';
                return false;
            }
        }

        return true;
    }

    /**
     * Enables async signals and control interrupts in the restarted process
     *
     * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+.
     */
    private function tryEnableSignals()
    {
        if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            pcntl_async_signals(true);
            $message = 'Async signals enabled';

            if (!self::$inRestart) {
                // Restarting, so ignore SIGINT in parent
                pcntl_signal(SIGINT, SIG_IGN);
                $message .= ' (SIGINT = SIG_IGN)';
            } elseif (is_int(pcntl_signal_get_handler(SIGINT))) {
                // Restarted, no handler set so force default action
                pcntl_signal(SIGINT, SIG_DFL);
                $message .= ' (SIGINT = SIG_DFL)';
            }
            $this->notify(Status::INFO, $message);
        }

        if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) {
            // Restarting, so set a handler to ignore CTRL events in the parent.
            // This ensures that CTRL+C events will be available in the child
            // process without having to enable them there, which is unreliable.
            sapi_windows_set_ctrl_handler(function ($evt) {});
            $this->notify(Status::INFO, 'CTRL signals suppressed');
        }
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class PhpConfig
{
    /**
     * Use the original PHP configuration
     *
     * @return array PHP cli options
     */
    public function useOriginal()
    {
        $this->getDataAndReset();
        return array();
    }

    /**
     * Use standard restart settings
     *
     * @return array PHP cli options
     */
    public function useStandard()
    {
        if ($data = $this->getDataAndReset()) {
            return array('-n', '-c', $data['tmpIni']);
        }

        return array();
    }

    /**
     * Use environment variables to persist settings
     *
     * @return array PHP cli options
     */
    public function usePersistent()
    {
        if ($data = $this->getDataAndReset()) {
            Process::setEnv('PHPRC', $data['tmpIni']);
            Process::setEnv('PHP_INI_SCAN_DIR', '');
        }

        return array();
    }

    /**
     * Returns restart data if available and resets the environment
     *
     * @return array|null
     */
    private function getDataAndReset()
    {
        if ($data = XdebugHandler::getRestartSettings()) {
            Process::setEnv('PHPRC', $data['phprc']);
            Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
        }

        return $data;
    }
}
<?php

// autoload_namespaces.php @generated by Composer

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

return array(
);
<?php

// autoload_files.php @generated by Composer

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

return array(
    'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
    '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
    'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
    '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
    'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
    'd9d39f82a605ebe5918f683dd402334c' => $vendorDir . '/padraic/humbug_get_contents/src/function.php',
    '3a50d90d85c7fe889a94ae1114b921ce' => $vendorDir . '/padraic/humbug_get_contents/src/functions.php',
);
<?php

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

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints($constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();

        if (self::$canGetVendors) {
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
                        self::$installed = $installed[count($installed) - 1];
                    }
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = require __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }
        $installed[] = self::$installed;

        return $installed;
    }
}
<?php

/*
 * This file is part of composer/spdx-licenses.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Spdx;

class SpdxLicenses
{
    /** @var string */
    const LICENSES_FILE = 'spdx-licenses.json';

    /** @var string */
    const EXCEPTIONS_FILE = 'spdx-exceptions.json';

    /**
     * Contains all the licenses.
     *
     * The array is indexed by license identifiers, which contain
     * a numerically indexed array with license details.
     *
     *  [ lowercased license identifier =>
     *      [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string, 2: bool, 3: bool}>
     */
    private $licenses;

    /**
     * @var string
     */
    private $licensesExpression;

    /**
     * Contains all the license exceptions.
     *
     * The array is indexed by license exception identifiers, which contain
     * a numerically indexed array with license exception details.
     *
     *  [ lowercased exception identifier =>
     *      [ 0 => exception identifier (string), 1 => full name (string) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string}>
     */
    private $exceptions;

    /**
     * @var string
     */
    private $exceptionsExpression;

    public function __construct()
    {
        $this->loadLicenses();
        $this->loadExceptions();
    }

    /**
     * Returns license metadata by license identifier.
     *
     * This function adds a link to the full license text to the license metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: bool, 2: string, 3: bool}|null
     */
    public function getLicenseByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->licenses[$key])) {
            return null;
        }

        list($identifier, $name, $isOsiApproved, $isDeprecatedLicenseId) = $this->licenses[$key];

        return array(
            $name,
            $isOsiApproved,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseText',
            $isDeprecatedLicenseId,
        );
    }

    /**
     * Returns all licenses information, keyed by the lowercased license identifier.
     *
     * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     */
    public function getLicenses()
    {
        return $this->licenses;
    }

    /**
     * Returns license exception metadata by license exception identifier.
     *
     * This function adds a link to the full license exception text to the license exception metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => link to license text (string) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: string}|null
     */
    public function getExceptionByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->exceptions[$key])) {
            return null;
        }

        list($identifier, $name) = $this->exceptions[$key];

        return array(
            $name,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText',
        );
    }

    /**
     * Returns the short identifier of a license (or license exception) by full name.
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getIdentifierByName($name)
    {
        foreach ($this->licenses as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        foreach ($this->exceptions as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        return null;
    }

    /**
     * Returns the OSI Approved status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isOsiApprovedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][2];
    }

    /**
     * Returns the deprecation status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isDeprecatedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][3];
    }

    /**
     * @param string[]|string $license
     *
     * @throws \InvalidArgumentException
     *
     * @return bool
     */
    public function validate($license)
    {
        if (is_array($license)) {
            $count = count($license);
            if ($count !== count(array_filter($license, 'is_string'))) {
                throw new \InvalidArgumentException('Array of strings expected.');
            }
            $license = $count > 1  ? '(' . implode(' OR ', $license) . ')' : (string) reset($license);
        }

        if (!is_string($license)) {
            throw new \InvalidArgumentException(sprintf(
                'Array or String expected, %s given.',
                gettype($license)
            ));
        }

        return $this->isValidLicenseString($license);
    }

    /**
     * @return string
     */
    public static function getResourcesDir()
    {
        return dirname(__DIR__) . '/res';
    }

    /**
     * @return void
     */
    private function loadLicenses()
    {
        if (null !== $this->licenses) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE);
        }
        $this->licenses = array();

        foreach (json_decode($json, true) as $identifier => $license) {
            $this->licenses[strtolower($identifier)] = array($identifier, $license[0], $license[1], $license[2]);
        }
    }

    /**
     * @return void
     */
    private function loadExceptions()
    {
        if (null !== $this->exceptions) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        }
        $this->exceptions = array();

        foreach (json_decode($json, true) as $identifier => $exception) {
            $this->exceptions[strtolower($identifier)] = array($identifier, $exception[0]);
        }
    }

    /**
     * @return string
     */
    private function getLicensesExpression()
    {
        if (null === $this->licensesExpression) {
            $licenses = array_map('preg_quote', array_keys($this->licenses));
            rsort($licenses);
            $licenses = implode('|', $licenses);
            $this->licensesExpression = $licenses;
        }

        return $this->licensesExpression;
    }

    /**
     * @return string
     */
    private function getExceptionsExpression()
    {
        if (null === $this->exceptionsExpression) {
            $exceptions = array_map('preg_quote', array_keys($this->exceptions));
            rsort($exceptions);
            $exceptions = implode('|', $exceptions);
            $this->exceptionsExpression = $exceptions;
        }

        return $this->exceptionsExpression;
    }

    /**
     * @param string $license
     *
     * @throws \RuntimeException
     *
     * @return bool
     */
    private function isValidLicenseString($license)
    {
        if (isset($this->licenses[strtolower($license)])) {
            return true;
        }

        $licenses = $this->getLicensesExpression();
        $exceptions = $this->getExceptionsExpression();

        $regex = <<<REGEX
{
(?(DEFINE)
    # idstring: 1*( ALPHA / DIGIT / - / . )
    (?<idstring>[\pL\pN.-]{1,})

    # license-id: taken from list
    (?<licenseid>${licenses})

    # license-exception-id: taken from list
    (?<licenseexceptionid>${exceptions})

    # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring)
    (?<licenseref>(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring))

    # simple-expresssion: license-id / license-id+ / license-ref
    (?<simple_expression>(?&licenseid)\+? | (?&licenseid) | (?&licenseref))

    # compound-expression: 1*(
    #   simple-expression /
    #   simple-expression WITH license-exception-id /
    #   compound-expression AND compound-expression /
    #   compound-expression OR compound-expression
    # ) / ( compound-expression ) )
    (?<compound_head>
        (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))?
            | \( \s* (?&compound_expression) \s* \)
    )
    (?<compound_expression>
        (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))?
    )

    # license-expression: 1*1(simple-expression / compound-expression)
    (?<license_expression>(?&compound_expression) | (?&simple_expression))
) # end of define

^(NONE | NOASSERTION | (?&license_expression))$
}xi
REGEX;

        $match = preg_match($regex, $license);

        if (0 === $match) {
            return false;
        }

        if (false === $match) {
            throw new \RuntimeException('Regex failed to compile/run.');
        }

        return true;
    }
}
<?php

// autoload_classmap.php @generated by Composer

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

return array(
    'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
    'ComposerPatcher\\Event' => $vendorDir . '/mlocati/composer-patcher/src/Event.php',
    'ComposerPatcher\\Event\\Patch' => $vendorDir . '/mlocati/composer-patcher/src/Event/Patch.php',
    'ComposerPatcher\\Exception' => $vendorDir . '/mlocati/composer-patcher/src/Exception.php',
    'ComposerPatcher\\Exception\\CommandNotFound' => $vendorDir . '/mlocati/composer-patcher/src/Exception/CommandNotFound.php',
    'ComposerPatcher\\Exception\\InvalidPackageConfigurationValue' => $vendorDir . '/mlocati/composer-patcher/src/Exception/InvalidPackageConfigurationValue.php',
    'ComposerPatcher\\Exception\\PatchAlreadyApplied' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PatchAlreadyApplied.php',
    'ComposerPatcher\\Exception\\PatchNotApplied' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PatchNotApplied.php',
    'ComposerPatcher\\Exception\\PathNotCreated' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PathNotCreated.php',
    'ComposerPatcher\\Exception\\PathNotFound' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PathNotFound.php',
    'ComposerPatcher\\Exception\\PathNotReadable' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PathNotReadable.php',
    'ComposerPatcher\\Exception\\PathNotWritable' => $vendorDir . '/mlocati/composer-patcher/src/Exception/PathNotWritable.php',
    'ComposerPatcher\\Patch' => $vendorDir . '/mlocati/composer-patcher/src/Patch.php',
    'ComposerPatcher\\PatchCollection' => $vendorDir . '/mlocati/composer-patcher/src/PatchCollection.php',
    'ComposerPatcher\\PatchCollector' => $vendorDir . '/mlocati/composer-patcher/src/PatchCollector.php',
    'ComposerPatcher\\PatchExecutor' => $vendorDir . '/mlocati/composer-patcher/src/PatchExecutor.php',
    'ComposerPatcher\\PatchExecutor\\GitPatcher' => $vendorDir . '/mlocati/composer-patcher/src/PatchExecutor/GitPatcher.php',
    'ComposerPatcher\\PatchExecutor\\PatchPatcher' => $vendorDir . '/mlocati/composer-patcher/src/PatchExecutor/PatchPatcher.php',
    'ComposerPatcher\\Patcher' => $vendorDir . '/mlocati/composer-patcher/src/Patcher.php',
    'ComposerPatcher\\Plugin' => $vendorDir . '/mlocati/composer-patcher/src/Plugin.php',
    'ComposerPatcher\\Util\\PathResolver' => $vendorDir . '/mlocati/composer-patcher/src/Util/PathResolver.php',
    'ComposerPatcher\\Util\\VolatileDirectory' => $vendorDir . '/mlocati/composer-patcher/src/Util/VolatileDirectory.php',
    'Composer\\Autoload\\AutoloadGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
    'Composer\\Autoload\\ClassLoader' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
    'Composer\\Autoload\\ClassMapGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
    'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php',
    'Composer\\Cache' => $vendorDir . '/composer/composer/src/Composer/Cache.php',
    'Composer\\Command\\AboutCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AboutCommand.php',
    'Composer\\Command\\ArchiveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
    'Composer\\Command\\BaseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseCommand.php',
    'Composer\\Command\\BaseDependencyCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
    'Composer\\Command\\CheckPlatformReqsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
    'Composer\\Command\\ClearCacheCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
    'Composer\\Command\\ConfigCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ConfigCommand.php',
    'Composer\\Command\\CreateProjectCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
    'Composer\\Command\\DependsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DependsCommand.php',
    'Composer\\Command\\DiagnoseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
    'Composer\\Command\\DumpAutoloadCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
    'Composer\\Command\\ExecCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ExecCommand.php',
    'Composer\\Command\\FundCommand' => $vendorDir . '/composer/composer/src/Composer/Command/FundCommand.php',
    'Composer\\Command\\GlobalCommand' => $vendorDir . '/composer/composer/src/Composer/Command/GlobalCommand.php',
    'Composer\\Command\\HomeCommand' => $vendorDir . '/composer/composer/src/Composer/Command/HomeCommand.php',
    'Composer\\Command\\InitCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InitCommand.php',
    'Composer\\Command\\InstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InstallCommand.php',
    'Composer\\Command\\LicensesCommand' => $vendorDir . '/composer/composer/src/Composer/Command/LicensesCommand.php',
    'Composer\\Command\\OutdatedCommand' => $vendorDir . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
    'Composer\\Command\\ProhibitsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
    'Composer\\Command\\RemoveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RemoveCommand.php',
    'Composer\\Command\\RequireCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RequireCommand.php',
    'Composer\\Command\\RunScriptCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
    'Composer\\Command\\ScriptAliasCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
    'Composer\\Command\\SearchCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SearchCommand.php',
    'Composer\\Command\\SelfUpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
    'Composer\\Command\\ShowCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ShowCommand.php',
    'Composer\\Command\\StatusCommand' => $vendorDir . '/composer/composer/src/Composer/Command/StatusCommand.php',
    'Composer\\Command\\SuggestsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
    'Composer\\Command\\UpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/UpdateCommand.php',
    'Composer\\Command\\ValidateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ValidateCommand.php',
    'Composer\\Compiler' => $vendorDir . '/composer/composer/src/Composer/Compiler.php',
    'Composer\\Composer' => $vendorDir . '/composer/composer/src/Composer/Composer.php',
    'Composer\\Config' => $vendorDir . '/composer/composer/src/Composer/Config.php',
    'Composer\\Config\\ConfigSourceInterface' => $vendorDir . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
    'Composer\\Config\\JsonConfigSource' => $vendorDir . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
    'Composer\\Console\\Application' => $vendorDir . '/composer/composer/src/Composer/Console/Application.php',
    'Composer\\Console\\HtmlOutputFormatter' => $vendorDir . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
    'Composer\\DependencyResolver\\Decisions' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
    'Composer\\DependencyResolver\\DefaultPolicy' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
    'Composer\\DependencyResolver\\GenericRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
    'Composer\\DependencyResolver\\Operation\\InstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\OperationInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
    'Composer\\DependencyResolver\\Operation\\SolverOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
    'Composer\\DependencyResolver\\Operation\\UninstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\UpdateOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
    'Composer\\DependencyResolver\\PolicyInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
    'Composer\\DependencyResolver\\Pool' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
    'Composer\\DependencyResolver\\Problem' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
    'Composer\\DependencyResolver\\Request' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Request.php',
    'Composer\\DependencyResolver\\Rule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
    'Composer\\DependencyResolver\\Rule2Literals' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
    'Composer\\DependencyResolver\\RuleSet' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
    'Composer\\DependencyResolver\\RuleSetGenerator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
    'Composer\\DependencyResolver\\RuleSetIterator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
    'Composer\\DependencyResolver\\RuleWatchChain' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
    'Composer\\DependencyResolver\\RuleWatchGraph' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
    'Composer\\DependencyResolver\\RuleWatchNode' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
    'Composer\\DependencyResolver\\Solver' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
    'Composer\\DependencyResolver\\SolverBugException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
    'Composer\\DependencyResolver\\SolverProblemsException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
    'Composer\\DependencyResolver\\Transaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
    'Composer\\Downloader\\ArchiveDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
    'Composer\\Downloader\\ChangeReportInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
    'Composer\\Downloader\\DownloadManager' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
    'Composer\\Downloader\\DownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
    'Composer\\Downloader\\DvcsDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
    'Composer\\Downloader\\FileDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
    'Composer\\Downloader\\FilesystemException' => $vendorDir . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
    'Composer\\Downloader\\FossilDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
    'Composer\\Downloader\\GitDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
    'Composer\\Downloader\\GzipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
    'Composer\\Downloader\\HgDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
    'Composer\\Downloader\\PathDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
    'Composer\\Downloader\\PearPackageExtractor' => $vendorDir . '/composer/composer/src/Composer/Downloader/PearPackageExtractor.php',
    'Composer\\Downloader\\PerforceDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
    'Composer\\Downloader\\PharDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
    'Composer\\Downloader\\RarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
    'Composer\\Downloader\\SvnDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
    'Composer\\Downloader\\TarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
    'Composer\\Downloader\\TransportException' => $vendorDir . '/composer/composer/src/Composer/Downloader/TransportException.php',
    'Composer\\Downloader\\VcsCapableDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
    'Composer\\Downloader\\VcsDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
    'Composer\\Downloader\\XzDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
    'Composer\\Downloader\\ZipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
    'Composer\\EventDispatcher\\Event' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/Event.php',
    'Composer\\EventDispatcher\\EventDispatcher' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
    'Composer\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
    'Composer\\EventDispatcher\\ScriptExecutionException' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
    'Composer\\Exception\\NoSslException' => $vendorDir . '/composer/composer/src/Composer/Exception/NoSslException.php',
    'Composer\\Factory' => $vendorDir . '/composer/composer/src/Composer/Factory.php',
    'Composer\\IO\\BaseIO' => $vendorDir . '/composer/composer/src/Composer/IO/BaseIO.php',
    'Composer\\IO\\BufferIO' => $vendorDir . '/composer/composer/src/Composer/IO/BufferIO.php',
    'Composer\\IO\\ConsoleIO' => $vendorDir . '/composer/composer/src/Composer/IO/ConsoleIO.php',
    'Composer\\IO\\IOInterface' => $vendorDir . '/composer/composer/src/Composer/IO/IOInterface.php',
    'Composer\\IO\\NullIO' => $vendorDir . '/composer/composer/src/Composer/IO/NullIO.php',
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'Composer\\Installer' => $vendorDir . '/composer/composer/src/Composer/Installer.php',
    'Composer\\Installer\\BinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
    'Composer\\Installer\\BinaryPresenceInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
    'Composer\\Installer\\InstallationManager' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallationManager.php',
    'Composer\\Installer\\InstallerEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
    'Composer\\Installer\\InstallerEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
    'Composer\\Installer\\InstallerInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
    'Composer\\Installer\\LibraryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
    'Composer\\Installer\\MetapackageInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
    'Composer\\Installer\\NoopInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
    'Composer\\Installer\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvent.php',
    'Composer\\Installer\\PackageEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvents.php',
    'Composer\\Installer\\PearBinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PearBinaryInstaller.php',
    'Composer\\Installer\\PearInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PearInstaller.php',
    'Composer\\Installer\\PluginInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
    'Composer\\Installer\\ProjectInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
    'Composer\\Installer\\SuggestedPackagesReporter' => $vendorDir . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
    'Composer\\Json\\JsonFile' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFile.php',
    'Composer\\Json\\JsonFormatter' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFormatter.php',
    'Composer\\Json\\JsonManipulator' => $vendorDir . '/composer/composer/src/Composer/Json/JsonManipulator.php',
    'Composer\\Json\\JsonValidationException' => $vendorDir . '/composer/composer/src/Composer/Json/JsonValidationException.php',
    'Composer\\Package\\AliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/AliasPackage.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFinder' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
    'Composer\\Package\\Archiver\\ArchiveManager' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
    'Composer\\Package\\Archiver\\ArchiverInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
    'Composer\\Package\\Archiver\\BaseExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
    'Composer\\Package\\Archiver\\ComposerExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
    'Composer\\Package\\Archiver\\GitExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
    'Composer\\Package\\Archiver\\HgExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php',
    'Composer\\Package\\Archiver\\PharArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
    'Composer\\Package\\Archiver\\ZipArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
    'Composer\\Package\\BasePackage' => $vendorDir . '/composer/composer/src/Composer/Package/BasePackage.php',
    'Composer\\Package\\Comparer\\Comparer' => $vendorDir . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
    'Composer\\Package\\CompletePackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackage.php',
    'Composer\\Package\\CompletePackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
    'Composer\\Package\\Dumper\\ArrayDumper' => $vendorDir . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
    'Composer\\Package\\Link' => $vendorDir . '/composer/composer/src/Composer/Package/Link.php',
    'Composer\\Package\\LinkConstraint\\EmptyConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php',
    'Composer\\Package\\LinkConstraint\\LinkConstraintInterface' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php',
    'Composer\\Package\\LinkConstraint\\MultiConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php',
    'Composer\\Package\\LinkConstraint\\SpecificConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php',
    'Composer\\Package\\LinkConstraint\\VersionConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php',
    'Composer\\Package\\Loader\\ArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
    'Composer\\Package\\Loader\\InvalidPackageException' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
    'Composer\\Package\\Loader\\JsonLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
    'Composer\\Package\\Loader\\LoaderInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
    'Composer\\Package\\Loader\\RootPackageLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
    'Composer\\Package\\Loader\\ValidatingArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
    'Composer\\Package\\Locker' => $vendorDir . '/composer/composer/src/Composer/Package/Locker.php',
    'Composer\\Package\\Package' => $vendorDir . '/composer/composer/src/Composer/Package/Package.php',
    'Composer\\Package\\PackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/PackageInterface.php',
    'Composer\\Package\\RootAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
    'Composer\\Package\\RootPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackage.php',
    'Composer\\Package\\RootPackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
    'Composer\\Package\\Version\\VersionGuesser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
    'Composer\\Package\\Version\\VersionParser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
    'Composer\\Package\\Version\\VersionSelector' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
    'Composer\\Plugin\\Capability\\Capability' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
    'Composer\\Plugin\\Capability\\CommandProvider' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
    'Composer\\Plugin\\Capable' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capable.php',
    'Composer\\Plugin\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
    'Composer\\Plugin\\PluginEvents' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
    'Composer\\Plugin\\PluginInterface' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
    'Composer\\Plugin\\PluginManager' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginManager.php',
    'Composer\\Plugin\\PreCommandRunEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
    'Composer\\Plugin\\PreFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
    'Composer\\Question\\StrictConfirmationQuestion' => $vendorDir . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
    'Composer\\Repository\\ArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
    'Composer\\Repository\\ArtifactRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
    'Composer\\Repository\\BaseRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/BaseRepository.php',
    'Composer\\Repository\\ComposerRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
    'Composer\\Repository\\CompositeRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
    'Composer\\Repository\\ConfigurableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
    'Composer\\Repository\\FilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
    'Composer\\Repository\\InstalledArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
    'Composer\\Repository\\InstalledFilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
    'Composer\\Repository\\InstalledRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
    'Composer\\Repository\\InvalidRepositoryException' => $vendorDir . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
    'Composer\\Repository\\PackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PackageRepository.php',
    'Composer\\Repository\\PathRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PathRepository.php',
    'Composer\\Repository\\PearRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PearRepository.php',
    'Composer\\Repository\\Pear\\BaseChannelReader' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/BaseChannelReader.php',
    'Composer\\Repository\\Pear\\ChannelInfo' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/ChannelInfo.php',
    'Composer\\Repository\\Pear\\ChannelReader' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/ChannelReader.php',
    'Composer\\Repository\\Pear\\ChannelRest10Reader' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/ChannelRest10Reader.php',
    'Composer\\Repository\\Pear\\ChannelRest11Reader' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/ChannelRest11Reader.php',
    'Composer\\Repository\\Pear\\DependencyConstraint' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/DependencyConstraint.php',
    'Composer\\Repository\\Pear\\DependencyInfo' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/DependencyInfo.php',
    'Composer\\Repository\\Pear\\PackageDependencyParser' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/PackageDependencyParser.php',
    'Composer\\Repository\\Pear\\PackageInfo' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/PackageInfo.php',
    'Composer\\Repository\\Pear\\ReleaseInfo' => $vendorDir . '/composer/composer/src/Composer/Repository/Pear/ReleaseInfo.php',
    'Composer\\Repository\\PlatformRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
    'Composer\\Repository\\RepositoryFactory' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
    'Composer\\Repository\\RepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
    'Composer\\Repository\\RepositoryManager' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
    'Composer\\Repository\\RepositorySecurityException' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
    'Composer\\Repository\\VcsRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/VcsRepository.php',
    'Composer\\Repository\\Vcs\\BitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php',
    'Composer\\Repository\\Vcs\\FossilDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
    'Composer\\Repository\\Vcs\\GitBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
    'Composer\\Repository\\Vcs\\GitDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
    'Composer\\Repository\\Vcs\\GitHubDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
    'Composer\\Repository\\Vcs\\GitLabDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
    'Composer\\Repository\\Vcs\\HgBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.php',
    'Composer\\Repository\\Vcs\\HgDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
    'Composer\\Repository\\Vcs\\PerforceDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
    'Composer\\Repository\\Vcs\\SvnDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriverInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
    'Composer\\Repository\\VersionCacheInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
    'Composer\\Repository\\WritableArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
    'Composer\\Repository\\WritableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
    'Composer\\Script\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Script/CommandEvent.php',
    'Composer\\Script\\Event' => $vendorDir . '/composer/composer/src/Composer/Script/Event.php',
    'Composer\\Script\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Script/PackageEvent.php',
    'Composer\\Script\\ScriptEvents' => $vendorDir . '/composer/composer/src/Composer/Script/ScriptEvents.php',
    'Composer\\SelfUpdate\\Keys' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
    'Composer\\SelfUpdate\\Versions' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
    'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'Composer\\Semver\\Constraint\\AbstractConstraint' => $vendorDir . '/composer/semver/src/Constraint/AbstractConstraint.php',
    'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'Composer\\Semver\\Constraint\\EmptyConstraint' => $vendorDir . '/composer/semver/src/Constraint/EmptyConstraint.php',
    'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'Composer\\Spdx\\SpdxLicenses' => $vendorDir . '/composer/spdx-licenses/src/SpdxLicenses.php',
    'Composer\\Util\\AuthHelper' => $vendorDir . '/composer/composer/src/Composer/Util/AuthHelper.php',
    'Composer\\Util\\Bitbucket' => $vendorDir . '/composer/composer/src/Composer/Util/Bitbucket.php',
    'Composer\\Util\\ComposerMirror' => $vendorDir . '/composer/composer/src/Composer/Util/ComposerMirror.php',
    'Composer\\Util\\ConfigValidator' => $vendorDir . '/composer/composer/src/Composer/Util/ConfigValidator.php',
    'Composer\\Util\\ErrorHandler' => $vendorDir . '/composer/composer/src/Composer/Util/ErrorHandler.php',
    'Composer\\Util\\Filesystem' => $vendorDir . '/composer/composer/src/Composer/Util/Filesystem.php',
    'Composer\\Util\\Git' => $vendorDir . '/composer/composer/src/Composer/Util/Git.php',
    'Composer\\Util\\GitHub' => $vendorDir . '/composer/composer/src/Composer/Util/GitHub.php',
    'Composer\\Util\\GitLab' => $vendorDir . '/composer/composer/src/Composer/Util/GitLab.php',
    'Composer\\Util\\Hg' => $vendorDir . '/composer/composer/src/Composer/Util/Hg.php',
    'Composer\\Util\\IniHelper' => $vendorDir . '/composer/composer/src/Composer/Util/IniHelper.php',
    'Composer\\Util\\NoProxyPattern' => $vendorDir . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
    'Composer\\Util\\PackageSorter' => $vendorDir . '/composer/composer/src/Composer/Util/PackageSorter.php',
    'Composer\\Util\\Perforce' => $vendorDir . '/composer/composer/src/Composer/Util/Perforce.php',
    'Composer\\Util\\Platform' => $vendorDir . '/composer/composer/src/Composer/Util/Platform.php',
    'Composer\\Util\\ProcessExecutor' => $vendorDir . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
    'Composer\\Util\\RemoteFilesystem' => $vendorDir . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
    'Composer\\Util\\Silencer' => $vendorDir . '/composer/composer/src/Composer/Util/Silencer.php',
    'Composer\\Util\\SpdxLicense' => $vendorDir . '/composer/composer/src/Composer/Util/SpdxLicense.php',
    'Composer\\Util\\StreamContextFactory' => $vendorDir . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
    'Composer\\Util\\Svn' => $vendorDir . '/composer/composer/src/Composer/Util/Svn.php',
    'Composer\\Util\\TlsHelper' => $vendorDir . '/composer/composer/src/Composer/Util/TlsHelper.php',
    'Composer\\Util\\Url' => $vendorDir . '/composer/composer/src/Composer/Util/Url.php',
    'Composer\\Util\\Zip' => $vendorDir . '/composer/composer/src/Composer/Util/Zip.php',
    'Composer\\XdebugHandler' => $vendorDir . '/composer/composer/src/Composer/XdebugHandler.php',
    'Composer\\XdebugHandler\\PhpConfig' => $vendorDir . '/composer/xdebug-handler/src/PhpConfig.php',
    'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php',
    'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php',
    'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php',
    'Humbug\\FileGetContents' => $vendorDir . '/padraic/humbug_get_contents/src/FileGetContents.php',
    'Humbug\\SelfUpdate\\Exception\\ExceptionInterface' => $vendorDir . '/padraic/phar-updater/src/Exception/ExceptionInterface.php',
    'Humbug\\SelfUpdate\\Exception\\FilesystemException' => $vendorDir . '/padraic/phar-updater/src/Exception/FilesystemException.php',
    'Humbug\\SelfUpdate\\Exception\\HttpRequestException' => $vendorDir . '/padraic/phar-updater/src/Exception/HttpRequestException.php',
    'Humbug\\SelfUpdate\\Exception\\InvalidArgumentException' => $vendorDir . '/padraic/phar-updater/src/Exception/InvalidArgumentException.php',
    'Humbug\\SelfUpdate\\Exception\\JsonParsingException' => $vendorDir . '/padraic/phar-updater/src/Exception/JsonParsingException.php',
    'Humbug\\SelfUpdate\\Exception\\NoSignatureException' => $vendorDir . '/padraic/phar-updater/src/Exception/NoSignatureException.php',
    'Humbug\\SelfUpdate\\Exception\\RuntimeException' => $vendorDir . '/padraic/phar-updater/src/Exception/RuntimeException.php',
    'Humbug\\SelfUpdate\\Strategy\\GithubStrategy' => $vendorDir . '/padraic/phar-updater/src/Strategy/GithubStrategy.php',
    'Humbug\\SelfUpdate\\Strategy\\Sha256Strategy' => $vendorDir . '/padraic/phar-updater/src/Strategy/Sha256Strategy.php',
    'Humbug\\SelfUpdate\\Strategy\\ShaStrategy' => $vendorDir . '/padraic/phar-updater/src/Strategy/ShaStrategy.php',
    'Humbug\\SelfUpdate\\Strategy\\ShaStrategyAbstract' => $vendorDir . '/padraic/phar-updater/src/Strategy/ShaStrategyAbstract.php',
    'Humbug\\SelfUpdate\\Strategy\\StrategyInterface' => $vendorDir . '/padraic/phar-updater/src/Strategy/StrategyInterface.php',
    'Humbug\\SelfUpdate\\Updater' => $vendorDir . '/padraic/phar-updater/src/Updater.php',
    'Humbug\\SelfUpdate\\VersionParser' => $vendorDir . '/padraic/phar-updater/src/VersionParser.php',
    'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
    'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
    'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
    'JsonSchema\\Constraints\\Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
    'JsonSchema\\Constraints\\ConstraintInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
    'JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
    'JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
    'JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
    'JsonSchema\\Constraints\\NumberConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
    'JsonSchema\\Constraints\\ObjectConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
    'JsonSchema\\Constraints\\SchemaConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
    'JsonSchema\\Constraints\\StringConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
    'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
    'JsonSchema\\Constraints\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
    'JsonSchema\\Constraints\\UndefinedConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
    'JsonSchema\\Entity\\JsonPointer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
    'JsonSchema\\Exception\\ExceptionInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
    'JsonSchema\\Exception\\InvalidArgumentException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
    'JsonSchema\\Exception\\InvalidConfigException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
    'JsonSchema\\Exception\\InvalidSchemaException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
    'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
    'JsonSchema\\Exception\\InvalidSourceUriException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
    'JsonSchema\\Exception\\JsonDecodingException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
    'JsonSchema\\Exception\\ResourceNotFoundException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
    'JsonSchema\\Exception\\RuntimeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
    'JsonSchema\\Exception\\UnresolvableJsonPointerException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
    'JsonSchema\\Exception\\UriResolverException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
    'JsonSchema\\Exception\\ValidationException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
    'JsonSchema\\Iterator\\ObjectIterator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
    'JsonSchema\\Rfc3339' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
    'JsonSchema\\SchemaStorage' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
    'JsonSchema\\SchemaStorageInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
    'JsonSchema\\UriResolverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
    'JsonSchema\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
    'JsonSchema\\Uri\\Retrievers\\Curl' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
    'JsonSchema\\Uri\\Retrievers\\FileGetContents' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
    'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
    'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
    'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
    'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
    'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
    'Pickle\\Base\\Abstracts\\Console\\Command\\BuildCommand' => $baseDir . '/src/Base/Abstracts/Console/Command/BuildCommand.php',
    'Pickle\\Base\\Abstracts\\Engine' => $baseDir . '/src/Base/Abstracts/Engine.php',
    'Pickle\\Base\\Abstracts\\Engine\\Ini' => $baseDir . '/src/Base/Abstracts/Engine/Ini.php',
    'Pickle\\Base\\Abstracts\\Package' => $baseDir . '/src/Base/Abstracts/Package.php',
    'Pickle\\Base\\Abstracts\\Package\\Build' => $baseDir . '/src/Base/Abstracts/Package/Build.php',
    'Pickle\\Base\\Abstracts\\Package\\Convey\\Command' => $baseDir . '/src/Base/Abstracts/Package/Convey/Command.php',
    'Pickle\\Base\\Archive\\Factory' => $baseDir . '/src/Base/Archive/Factory.php',
    'Pickle\\Base\\Archive\\Infozip' => $baseDir . '/src/Base/Archive/Infozip.php',
    'Pickle\\Base\\Archive\\InfozipUnzipper' => $baseDir . '/src/Base/Archive/InfozipUnzipper.php',
    'Pickle\\Base\\Archive\\InfozipZipper' => $baseDir . '/src/Base/Archive/InfozipZipper.php',
    'Pickle\\Base\\Archive\\PHP' => $baseDir . '/src/Base/Archive/PHP.php',
    'Pickle\\Base\\Archive\\PHPUnzipper' => $baseDir . '/src/Base/Archive/PHPUnzipper.php',
    'Pickle\\Base\\Archive\\PHPZipper' => $baseDir . '/src/Base/Archive/PHPZipper.php',
    'Pickle\\Base\\Interfaces\\Archive\\Unzipper' => $baseDir . '/src/Base/Interfaces/Archive/Unzipper.php',
    'Pickle\\Base\\Interfaces\\Archive\\Zipper' => $baseDir . '/src/Base/Interfaces/Archive/Zipper.php',
    'Pickle\\Base\\Interfaces\\Engine' => $baseDir . '/src/Base/Interfaces/Engine.php',
    'Pickle\\Base\\Interfaces\\Engine\\Ini' => $baseDir . '/src/Base/Interfaces/Engine/Ini.php',
    'Pickle\\Base\\Interfaces\\Package' => $baseDir . '/src/Base/Interfaces/Package.php',
    'Pickle\\Base\\Interfaces\\Package\\Build' => $baseDir . '/src/Base/Interfaces/Package/Build.php',
    'Pickle\\Base\\Interfaces\\Package\\Convey\\Command' => $baseDir . '/src/Base/Interfaces/Package/Convey/Command.php',
    'Pickle\\Base\\Interfaces\\Package\\Convey\\DefaultExecutor' => $baseDir . '/src/Base/Interfaces/Package/Convey/DefaultExecutor.php',
    'Pickle\\Base\\Interfaces\\Package\\Info' => $baseDir . '/src/Base/Interfaces/Package/Info.php',
    'Pickle\\Base\\Interfaces\\Package\\Release' => $baseDir . '/src/Base/Interfaces/Package/Release.php',
    'Pickle\\Base\\Interfaces\\Package\\Validate' => $baseDir . '/src/Base/Interfaces/Package/Validate.php',
    'Pickle\\Base\\Pecl\\Website' => $baseDir . '/src/Base/Pecl/Website.php',
    'Pickle\\Base\\Pecl\\WebsiteFactory' => $baseDir . '/src/Base/Pecl/WebsiteFactory.php',
    'Pickle\\Base\\Util\\FileOps' => $baseDir . '/src/Base/Util/FileOps.php',
    'Pickle\\Base\\Util\\GitIgnore' => $baseDir . '/src/Base/Util/GitIgnore.php',
    'Pickle\\Base\\Util\\TmpDir' => $baseDir . '/src/Base/Util/TmpDir.php',
    'Pickle\\Config' => $baseDir . '/src/Config.php',
    'Pickle\\Console\\Application' => $baseDir . '/src/Console/Application.php',
    'Pickle\\Console\\Command\\ConvertCommand' => $baseDir . '/src/Console/Command/ConvertCommand.php',
    'Pickle\\Console\\Command\\InfoCommand' => $baseDir . '/src/Console/Command/InfoCommand.php',
    'Pickle\\Console\\Command\\InstallerCommand' => $baseDir . '/src/Console/Command/InstallerCommand.php',
    'Pickle\\Console\\Command\\ReleaseCommand' => $baseDir . '/src/Console/Command/ReleaseCommand.php',
    'Pickle\\Console\\Command\\SelfUpdateCommand' => $baseDir . '/src/Console/Command/SelfUpdateCommand.php',
    'Pickle\\Console\\Command\\ValidateCommand' => $baseDir . '/src/Console/Command/ValidateCommand.php',
    'Pickle\\Console\\Helper\\PackageHelper' => $baseDir . '/src/Console/Helper/PackageHelper.php',
    'Pickle\\Downloader\\PECLDownloader' => $baseDir . '/src/Downloader/PECLDownloader.php',
    'Pickle\\Downloader\\TGZDownloader' => $baseDir . '/src/Downloader/TGZDownloader.php',
    'Pickle\\Engine' => $baseDir . '/src/Engine.php',
    'Pickle\\Engine\\Ini' => $baseDir . '/src/Engine/Ini.php',
    'Pickle\\Engine\\PHP' => $baseDir . '/src/Engine/PHP.php',
    'Pickle\\Engine\\PHP\\Ini' => $baseDir . '/src/Engine/PHP/Ini.php',
    'Pickle\\Package' => $baseDir . '/src/Package.php',
    'Pickle\\Package\\Command\\Build' => $baseDir . '/src/Package/Command/Build.php',
    'Pickle\\Package\\Command\\Convert' => $baseDir . '/src/Package/Command/Convert.php',
    'Pickle\\Package\\Command\\Info' => $baseDir . '/src/Package/Command/Info.php',
    'Pickle\\Package\\Command\\Install' => $baseDir . '/src/Package/Command/Install.php',
    'Pickle\\Package\\Command\\Release' => $baseDir . '/src/Package/Command/Release.php',
    'Pickle\\Package\\Command\\Validate' => $baseDir . '/src/Package/Command/Validate.php',
    'Pickle\\Package\\Convey' => $baseDir . '/src/Package/Convey.php',
    'Pickle\\Package\\Convey\\Command\\Any' => $baseDir . '/src/Package/Convey/Command/Any.php',
    'Pickle\\Package\\Convey\\Command\\DefaultExecutor' => $baseDir . '/src/Package/Convey/Command/DefaultExecutor.php',
    'Pickle\\Package\\Convey\\Command\\Factory' => $baseDir . '/src/Package/Convey/Command/Factory.php',
    'Pickle\\Package\\Convey\\Command\\Git' => $baseDir . '/src/Package/Convey/Command/Git.php',
    'Pickle\\Package\\Convey\\Command\\Pickle' => $baseDir . '/src/Package/Convey/Command/Pickle.php',
    'Pickle\\Package\\Convey\\Command\\SrcDir' => $baseDir . '/src/Package/Convey/Command/SrcDir.php',
    'Pickle\\Package\\Convey\\Command\\Tgz' => $baseDir . '/src/Package/Convey/Command/Tgz.php',
    'Pickle\\Package\\Convey\\Command\\Type' => $baseDir . '/src/Package/Convey/Command/Type.php',
    'Pickle\\Package\\PHP\\Command\\Build\\Unix' => $baseDir . '/src/Package/PHP/Command/Build/Unix.php',
    'Pickle\\Package\\PHP\\Command\\Build\\Windows' => $baseDir . '/src/Package/PHP/Command/Build/Windows.php',
    'Pickle\\Package\\PHP\\Command\\Convert' => $baseDir . '/src/Package/PHP/Command/Convert.php',
    'Pickle\\Package\\PHP\\Command\\Info' => $baseDir . '/src/Package/PHP/Command/Info.php',
    'Pickle\\Package\\PHP\\Command\\Install\\Windows\\Binary' => $baseDir . '/src/Package/PHP/Command/Install/Windows/Binary.php',
    'Pickle\\Package\\PHP\\Command\\Release' => $baseDir . '/src/Package/PHP/Command/Release.php',
    'Pickle\\Package\\PHP\\Command\\Release\\Windows\\Binary' => $baseDir . '/src/Package/PHP/Command/Release/Windows/Binary.php',
    'Pickle\\Package\\PHP\\Command\\Validate' => $baseDir . '/src/Package/PHP/Command/Validate.php',
    'Pickle\\Package\\PHP\\Convey\\Command\\DefaultExecutor' => $baseDir . '/src/Package/PHP/Convey/Command/DefaultExecutor.php',
    'Pickle\\Package\\PHP\\Convey\\Command\\Pecl' => $baseDir . '/src/Package/PHP/Convey/Command/Pecl.php',
    'Pickle\\Package\\PHP\\Package' => $baseDir . '/src/Package/PHP/Package.php',
    'Pickle\\Package\\PHP\\Util\\ConvertChangeLog' => $baseDir . '/src/Package/PHP/Util/ConvertChangeLog.php',
    'Pickle\\Package\\PHP\\Util\\PackageJson' => $baseDir . '/src/Package/PHP/Util/PackageJson.php',
    'Pickle\\Package\\PHP\\Util\\PackageXml' => $baseDir . '/src/Package/PHP/Util/PackageXml.php',
    'Pickle\\Package\\PHP\\Util\\XML\\Loader' => $baseDir . '/src/Package/PHP/Util/XML/Loader.php',
    'Pickle\\Package\\Util\\Dumper' => $baseDir . '/src/Package/Util/Dumper.php',
    'Pickle\\Package\\Util\\Header\\Version' => $baseDir . '/src/Package/Util/Header/Version.php',
    'Pickle\\Package\\Util\\JSON\\Dumper' => $baseDir . '/src/Package/Util/JSON/Dumper.php',
    'Pickle\\Package\\Util\\JSON\\Loader' => $baseDir . '/src/Package/Util/JSON/Loader.php',
    'Pickle\\Package\\Util\\Loader' => $baseDir . '/src/Package/Util/Loader.php',
    'Pickle\\Package\\Util\\Windows\\DependencyLib' => $baseDir . '/src/Package/Util/Windows/DependencyLib.php',
    'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php',
    'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php',
    'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php',
    'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
    'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php',
    'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
    'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php',
    'Seld\\JsonLint\\DuplicateKeyException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
    'Seld\\JsonLint\\JsonParser' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
    'Seld\\JsonLint\\Lexer' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
    'Seld\\JsonLint\\ParsingException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
    'Seld\\JsonLint\\Undefined' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
    'Seld\\PharUtils\\Linter' => $vendorDir . '/seld/phar-utils/src/Linter.php',
    'Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php',
    'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
    'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
    'Symfony\\Component\\Console\\Attribute\\AsCommand' => $vendorDir . '/symfony/console/Attribute/AsCommand.php',
    'Symfony\\Component\\Console\\CI\\GithubActionReporter' => $vendorDir . '/symfony/console/CI/GithubActionReporter.php',
    'Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php',
    'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
    'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
    'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
    'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php',
    'Symfony\\Component\\Console\\Command\\CompleteCommand' => $vendorDir . '/symfony/console/Command/CompleteCommand.php',
    'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => $vendorDir . '/symfony/console/Command/DumpCompletionCommand.php',
    'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php',
    'Symfony\\Component\\Console\\Command\\LazyCommand' => $vendorDir . '/symfony/console/Command/LazyCommand.php',
    'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php',
    'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php',
    'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => $vendorDir . '/symfony/console/Command/SignalableCommandInterface.php',
    'Symfony\\Component\\Console\\Completion\\CompletionInput' => $vendorDir . '/symfony/console/Completion/CompletionInput.php',
    'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => $vendorDir . '/symfony/console/Completion/CompletionSuggestions.php',
    'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/BashCompletionOutput.php',
    'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => $vendorDir . '/symfony/console/Completion/Output/CompletionOutputInterface.php',
    'Symfony\\Component\\Console\\Completion\\Suggestion' => $vendorDir . '/symfony/console/Completion/Suggestion.php',
    'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php',
    'Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php',
    'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
    'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php',
    'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
    'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
    'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
    'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => $vendorDir . '/symfony/console/Event/ConsoleSignalEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php',
    'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php',
    'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php',
    'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php',
    'Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php',
    'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php',
    'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php',
    'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php',
    'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
    'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php',
    'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php',
    'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php',
    'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php',
    'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php',
    'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
    'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
    'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
    'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php',
    'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php',
    'Symfony\\Component\\Console\\Helper\\TableCellStyle' => $vendorDir . '/symfony/console/Helper/TableCellStyle.php',
    'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php',
    'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php',
    'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php',
    'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
    'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
    'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
    'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
    'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
    'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
    'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
    'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
    'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
    'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
    'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
    'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php',
    'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php',
    'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php',
    'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php',
    'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php',
    'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php',
    'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => $vendorDir . '/symfony/console/Output/TrimmedBufferOutput.php',
    'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php',
    'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php',
    'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php',
    'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => $vendorDir . '/symfony/console/SignalRegistry/SignalRegistry.php',
    'Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php',
    'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php',
    'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php',
    'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php',
    'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php',
    'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
    'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => $vendorDir . '/symfony/console/Tester/CommandCompletionTester.php',
    'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
    'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => $vendorDir . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php',
    'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php',
    'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php',
    'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php',
    'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php',
    'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => $vendorDir . '/symfony/filesystem/Exception/RuntimeException.php',
    'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php',
    'Symfony\\Component\\Filesystem\\Path' => $vendorDir . '/symfony/filesystem/Path.php',
    'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php',
    'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php',
    'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php',
    'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php',
    'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php',
    'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php',
    'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php',
    'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php',
    'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => $vendorDir . '/symfony/finder/Iterator/LazyIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => $vendorDir . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php',
    'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php',
    'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php',
    'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php',
    'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php',
    'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php',
    'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php',
    'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php',
    'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php',
    'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php',
    'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php',
    'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php',
    'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php',
    'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php',
    'Symfony\\Component\\String\\AbstractString' => $vendorDir . '/symfony/string/AbstractString.php',
    'Symfony\\Component\\String\\AbstractUnicodeString' => $vendorDir . '/symfony/string/AbstractUnicodeString.php',
    'Symfony\\Component\\String\\ByteString' => $vendorDir . '/symfony/string/ByteString.php',
    'Symfony\\Component\\String\\CodePointString' => $vendorDir . '/symfony/string/CodePointString.php',
    'Symfony\\Component\\String\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/string/Exception/ExceptionInterface.php',
    'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/string/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\String\\Exception\\RuntimeException' => $vendorDir . '/symfony/string/Exception/RuntimeException.php',
    'Symfony\\Component\\String\\Inflector\\EnglishInflector' => $vendorDir . '/symfony/string/Inflector/EnglishInflector.php',
    'Symfony\\Component\\String\\Inflector\\FrenchInflector' => $vendorDir . '/symfony/string/Inflector/FrenchInflector.php',
    'Symfony\\Component\\String\\Inflector\\InflectorInterface' => $vendorDir . '/symfony/string/Inflector/InflectorInterface.php',
    'Symfony\\Component\\String\\LazyString' => $vendorDir . '/symfony/string/LazyString.php',
    'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php',
    'Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php',
    'Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php',
    'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php',
    'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => $vendorDir . '/symfony/service-contracts/Attribute/SubscribedService.php',
    'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php',
    'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php',
    'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php',
    'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php',
    'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php',
    'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
    'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
    'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php',
    'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php',
    'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
    'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php',
    'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php',
    'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
    'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
<?php

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

namespace Composer\Plugin;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\DependencyResolver\Pool;
use Composer\Plugin\Capability\Capability;
use Composer\Util\PackageSorter;

/**
 * Plugin manager
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PluginManager
{
    protected $composer;
    protected $io;
    protected $globalComposer;
    protected $versionParser;
    protected $disablePlugins = false;

    protected $plugins = array();
    protected $registeredPlugins = array();

    private static $classCounter = 0;

    /**
     * Initializes plugin manager
     *
     * @param IOInterface $io
     * @param Composer    $composer
     * @param Composer    $globalComposer
     * @param bool        $disablePlugins
     */
    public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false)
    {
        $this->io = $io;
        $this->composer = $composer;
        $this->globalComposer = $globalComposer;
        $this->versionParser = new VersionParser();
        $this->disablePlugins = $disablePlugins;
    }

    /**
     * Loads all plugins from currently installed plugin packages
     */
    public function loadInstalledPlugins()
    {
        if ($this->disablePlugins) {
            return;
        }

        $repo = $this->composer->getRepositoryManager()->getLocalRepository();
        $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
        if ($repo) {
            $this->loadRepository($repo);
        }
        if ($globalRepo) {
            $this->loadRepository($globalRepo);
        }
    }

    /**
     * Gets all currently active plugin instances
     *
     * @return array plugins
     */
    public function getPlugins()
    {
        return $this->plugins;
    }

    /**
     * Gets global composer or null when main composer is not fully loaded
     *
     * @return Composer|null
     */
    public function getGlobalComposer()
    {
        return $this->globalComposer;
    }

    /**
     * Register a plugin package, activate it etc.
     *
     * If it's of type composer-installer it is registered as an installer
     * instead for BC
     *
     * @param PackageInterface $package
     * @param bool             $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception
     *
     * @throws \UnexpectedValueException
     */
    public function registerPackage(PackageInterface $package, $failOnMissingClasses = false)
    {
        if ($this->disablePlugins) {
            return;
        }

        if ($package->getType() === 'composer-plugin') {
            $requiresComposer = null;
            foreach ($package->getRequires() as $link) { /** @var Link $link */
                if ('composer-plugin-api' === $link->getTarget()) {
                    $requiresComposer = $link->getConstraint();
                    break;
                }
            }

            if (!$requiresComposer) {
                throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package.");
            }

            $currentPluginApiVersion = $this->getPluginApiVersion();
            $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion));

            if ($requiresComposer->getPrettyString() === '1.0.0' && $this->getPluginApiVersion() === '1.0.0') {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin requires composer-plugin-api 1.0.0, this *WILL* break in the future and it should be fixed ASAP (require ^1.0 for example).</warning>');
            } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.</warning>');

                return;
            }
        }

        $oldInstallerPlugin = ($package->getType() === 'composer-installer');

        if (in_array($package->getName(), $this->registeredPlugins)) {
            return;
        }

        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }
        $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']);

        $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
        $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;

        $pool = new Pool('dev');
        $pool->addRepository($localRepo);
        if ($globalRepo) {
            $pool->addRepository($globalRepo);
        }

        $autoloadPackages = array($package->getName() => $package);
        $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package);

        $generator = $this->composer->getAutoloadGenerator();
        $autoloads = array();
        foreach ($autoloadPackages as $autoloadPackage) {
            $downloadPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage));
            $autoloads[] = array($autoloadPackage, $downloadPath);
        }

        $map = $generator->parseAutoloads($autoloads, new Package('dummy', '1.0.0.0', '1.0.0'));
        $classLoader = $generator->createLoader($map);
        $classLoader->register();

        foreach ($classes as $class) {
            if (class_exists($class, false)) {
                $class = trim($class, '\\');
                $path = $classLoader->findFile($class);
                $code = file_get_contents($path);
                $separatorPos = strrpos($class, '\\');
                $className = $class;
                if ($separatorPos) {
                    $className = substr($class, $separatorPos + 1);
                }
                $code = preg_replace('{^((?:final\s+)?(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1);
                $code = str_replace('__FILE__', var_export($path, true), $code);
                $code = str_replace('__DIR__', var_export(dirname($path), true), $code);
                $code = str_replace('__CLASS__', var_export($class, true), $code);
                $code = preg_replace('/^\s*<\?(php)?/i', '', $code, 1);
                eval($code);
                $class .= '_composer_tmp'.self::$classCounter;
                self::$classCounter++;
            }

            if ($oldInstallerPlugin) {
                $installer = new $class($this->io, $this->composer);
                $this->composer->getInstallationManager()->addInstaller($installer);
            } elseif (class_exists($class)) {
                $plugin = new $class();
                $this->addPlugin($plugin);
                $this->registeredPlugins[] = $package->getName();
            } elseif ($failOnMissingClasses) {
                throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
            }
        }
    }

    /**
     * Returns the version of the internal composer-plugin-api package.
     *
     * @return string
     */
    protected function getPluginApiVersion()
    {
        return PluginInterface::PLUGIN_API_VERSION;
    }

    /**
     * Adds a plugin, activates it and registers it with the event dispatcher
     *
     * Ideally plugin packages should be registered via registerPackage, but if you use Composer
     * programmatically and want to register a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface $plugin plugin instance
     */
    public function addPlugin(PluginInterface $plugin)
    {
        $this->io->writeError('Loading plugin '.get_class($plugin), true, IOInterface::DEBUG);
        $this->plugins[] = $plugin;
        $plugin->activate($this->composer, $this->io);

        if ($plugin instanceof EventSubscriberInterface) {
            $this->composer->getEventDispatcher()->addSubscriber($plugin);
        }
    }

    /**
     * Load all plugins and installers from a repository
     *
     * Note that plugins in the specified repository that rely on events that
     * have fired prior to loading will be missed. This means you likely want to
     * call this method as early as possible.
     *
     * @param RepositoryInterface $repo Repository to scan for plugins to install
     *
     * @throws \RuntimeException
     */
    private function loadRepository(RepositoryInterface $repo)
    {
        $packages = $repo->getPackages();
        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));
        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }
            if ('composer-plugin' === $package->getType()) {
                $this->registerPackage($package);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->registerPackage($package);
            }
        }
    }

    /**
     * Recursively generates a map of package names to packages for all deps
     *
     * @param Pool             $pool      Package pool of installed packages
     * @param array            $collected Current state of the map for recursion
     * @param PackageInterface $package   The package to analyze
     *
     * @return array Map of package names to packages
     */
    private function collectDependencies(Pool $pool, array $collected, PackageInterface $package)
    {
        $requires = array_merge(
            $package->getRequires(),
            $package->getDevRequires()
        );

        foreach ($requires as $requireLink) {
            $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink);
            if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) {
                $collected[$requiredPackage->getName()] = $requiredPackage;
                $collected = $this->collectDependencies($pool, $collected, $requiredPackage);
            }
        }

        return $collected;
    }

    /**
     * Resolves a package link to a package in the installed pool
     *
     * Since dependencies are already installed this should always find one.
     *
     * @param Pool $pool Pool of installed packages only
     * @param Link $link Package link to look up
     *
     * @return PackageInterface|null The found package
     */
    private function lookupInstalledPackage(Pool $pool, Link $link)
    {
        $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint());

        return !empty($packages) ? $packages[0] : null;
    }

    /**
     * Retrieves the path a package is installed to.
     *
     * @param PackageInterface $package
     * @param bool             $global  Whether this is a global package
     *
     * @return string Install path
     */
    private function getInstallPath(PackageInterface $package, $global = false)
    {
        if (!$global) {
            return $this->composer->getInstallationManager()->getInstallPath($package);
        }

        return $this->globalComposer->getInstallationManager()->getInstallPath($package);
    }

    /**
     * @param  PluginInterface   $plugin
     * @param  string            $capability
     * @throws \RuntimeException On empty or non-string implementation class name value
     * @return null|string       The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it
     */
    protected function getCapabilityImplementationClassName(PluginInterface $plugin, $capability)
    {
        if (!($plugin instanceof Capable)) {
            return null;
        }

        $capabilities = (array) $plugin->getCapabilities();

        if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) {
            return trim($capabilities[$capability]);
        }

        if (
            array_key_exists($capability, $capabilities)
            && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability]))
        ) {
            throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], 1));
        }
    }

    /**
     * @param  PluginInterface $plugin
     * @param  string          $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                              an implementation of.
     * @param  array           $ctorArgs            Arguments passed to Capability's constructor.
     *                                              Keeping it an array will allow future values to be passed w\o changing the signature.
     * @return null|Capability
     */
    public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = array())
    {
        if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) {
            if (!class_exists($capabilityClass)) {
                throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist.");
            }

            $ctorArgs['plugin'] = $plugin;
            $capabilityObj = new $capabilityClass($ctorArgs);

            // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9
            if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) {
                throw new \RuntimeException(
                    'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.'
                );
            }

            return $capabilityObj;
        }
    }

    /**
     * @param  string       $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                           an implementation of.
     * @param  array        $ctorArgs            Arguments passed to Capability's constructor.
     *                                           Keeping it an array will allow future values to be passed w\o changing the signature.
     * @return Capability[]
     */
    public function getPluginCapabilities($capabilityClassName, array $ctorArgs = array())
    {
        $capabilities = array();
        foreach ($this->getPlugins() as $plugin) {
            if ($capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs)) {
                $capabilities[] = $capability;
            }
        }

        return $capabilities;
    }
}
<?php

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

namespace Composer\Plugin;

/**
 * Plugins which need to expose various implementations
 * of the Composer Plugin Capabilities must have their
 * declared Plugin class implementing this interface.
 *
 * @api
 */
interface Capable
{
    /**
     * Method by which a Plugin announces its API implementations, through an array
     * with a special structure.
     *
     * The key must be a string, representing a fully qualified class/interface name
     * which Composer Plugin API exposes.
     * The value must be a string as well, representing the fully qualified class name
     * of the implementing class.
     *
     * @tutorial
     *
     * return array(
     *     'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider',
     *     'Composer\Plugin\Capability\Validator'       => 'My\Validator',
     * );
     *
     * @return string[]
     */
    public function getCapabilities();
}
<?php

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

namespace Composer\Plugin;

use Composer\Composer;
use Composer\IO\IOInterface;

/**
 * Plugin interface
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PluginInterface
{
    /**
     * Version number of the internal composer-plugin-api package
     *
     * @var string
     */
    const PLUGIN_API_VERSION = '1.1.0';

    /**
     * Apply plugin modifications to Composer
     *
     * @param Composer    $composer
     * @param IOInterface $io
     */
    public function activate(Composer $composer, IOInterface $io);
}
<?php

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

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PreCommandRunEvent extends Event
{
    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var string
     */
    private $command;

    /**
     * Constructor.
     *
     * @param string         $name    The event name
     * @param InputInterface $input
     * @param string         $command The command about to be executed
     */
    public function __construct($name, InputInterface $input, $command)
    {
        parent::__construct($name);
        $this->input = $input;
        $this->command = $command;
    }

    /**
     * Returns the console input
     *
     * @return InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Returns the command about to be executed
     *
     * @return string
     */
    public function getCommand()
    {
        return $this->command;
    }
}
<?php

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

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * An event for all commands.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CommandEvent extends Event
{
    /**
     * @var string
     */
    private $commandName;

    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Constructor.
     *
     * @param string          $name        The event name
     * @param string          $commandName The command name
     * @param InputInterface  $input
     * @param OutputInterface $output
     * @param array           $args        Arguments passed by the user
     * @param array           $flags       Optional flags to pass data not as argument
     */
    public function __construct($name, $commandName, $input, $output, array $args = array(), array $flags = array())
    {
        parent::__construct($name, $args, $flags);
        $this->commandName = $commandName;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Returns the command input interface
     *
     * @return InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Retrieves the command output interface
     *
     * @return OutputInterface
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Retrieves the name of the command being run
     *
     * @return string
     */
    public function getCommandName()
    {
        return $this->commandName;
    }
}
<?php

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

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Util\RemoteFilesystem;

/**
 * The pre file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PreFileDownloadEvent extends Event
{
    /**
     * @var RemoteFilesystem
     */
    private $rfs;

    /**
     * @var string
     */
    private $processedUrl;

    /**
     * Constructor.
     *
     * @param string           $name         The event name
     * @param RemoteFilesystem $rfs
     * @param string           $processedUrl
     */
    public function __construct($name, RemoteFilesystem $rfs, $processedUrl)
    {
        parent::__construct($name);
        $this->rfs = $rfs;
        $this->processedUrl = $processedUrl;
    }

    /**
     * Returns the remote filesystem
     *
     * @return RemoteFilesystem
     */
    public function getRemoteFilesystem()
    {
        return $this->rfs;
    }

    /**
     * Sets the remote filesystem
     *
     * @param RemoteFilesystem $rfs
     */
    public function setRemoteFilesystem(RemoteFilesystem $rfs)
    {
        $this->rfs = $rfs;
    }

    /**
     * Retrieves the processed URL this remote filesystem will be used for
     *
     * @return string
     */
    public function getProcessedUrl()
    {
        return $this->processedUrl;
    }
}
<?php

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

namespace Composer\Plugin;

/**
 * The Plugin Events.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginEvents
{
    /**
     * The INIT event occurs after a Composer instance is done being initialized
     *
     * The event listener method receives a
     * Composer\EventDispatcher\Event instance.
     *
     * @var string
     */
    const INIT = 'init';

    /**
     * The COMMAND event occurs as a command begins
     *
     * The event listener method receives a
     * Composer\Plugin\CommandEvent instance.
     *
     * @var string
     */
    const COMMAND = 'command';

    /**
     * The PRE_FILE_DOWNLOAD event occurs before downloading a file
     *
     * The event listener method receives a
     * Composer\Plugin\PreFileDownloadEvent instance.
     *
     * @var string
     */
    const PRE_FILE_DOWNLOAD = 'pre-file-download';

    /**
     * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options
     *
     * The event listener method receives a
     * Composer\Plugin\PreCommandRunEvent instance.
     *
     * @var string
     */
    const PRE_COMMAND_RUN = 'pre-command-run';
}
<?php

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

namespace Composer\Plugin\Capability;

/**
 * Marker interface for Plugin capabilities.
 * Every new Capability which is added to the Plugin API must implement this interface.
 *
 * @api
 */
interface Capability
{
}
<?php

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

namespace Composer\Plugin\Capability;

/**
 * Commands Provider Interface
 *
 * This capability will receive an array with 'composer' and 'io' keys as
 * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface
 * instances. It also contains a 'plugin' key containing the plugin instance that
 * created the capability.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
interface CommandProvider extends Capability
{
    /**
     * Retrieves an array of commands
     *
     * @return \Composer\Command\BaseCommand[]
     */
    public function getCommands();
}
<?php

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

namespace Composer\SelfUpdate;

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

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

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

namespace Composer\SelfUpdate;

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

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Versions
{
    public static $channels = array('stable', 'preview', 'snapshot', '1', '2');

    private $rfs;
    private $config;
    private $channel;
    private $versionsData;

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

    public function getChannel()
    {
        if ($this->channel) {
            return $this->channel;
        }

        $channelFile = $this->config->get('home').'/update-channel';
        if (file_exists($channelFile)) {
            $channel = trim(file_get_contents($channelFile));
            if (in_array($channel, array('stable', 'preview', 'snapshot'), true)) {
                return $this->channel = $channel;
            }
        }

        return $this->channel = 'stable';
    }

    public function setChannel($channel)
    {
        if (!in_array($channel, self::$channels, true)) {
            throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::$channels));
        }

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

    public function getLatest($channel = null)
    {
        $versions = $this->getVersionsData();

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

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

    private function getVersionsData()
    {
        if (!$this->versionsData) {
            if ($this->config->get('disable-tls') === true) {
                $protocol = 'http';
            } else {
                $protocol = 'https';
            }

            $this->versionsData = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false));
        }

        return $this->versionsData;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * Stores decisions on installing, removing or keeping packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Decisions implements \Iterator, \Countable
{
    const DECISION_LITERAL = 0;
    const DECISION_REASON = 1;

    protected $pool;
    protected $decisionMap;
    protected $decisionQueue = array();

    public function __construct($pool)
    {
        $this->pool = $pool;
        $this->decisionMap = array();
    }

    public function decide($literal, $level, $why)
    {
        $this->addDecision($literal, $level);
        $this->decisionQueue[] = array(
            self::DECISION_LITERAL => $literal,
            self::DECISION_REASON => $why,
        );
    }

    public function satisfy($literal)
    {
        $packageId = abs($literal);

        return (
            $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
            $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
        );
    }

    public function conflict($literal)
    {
        $packageId = abs($literal);

        return (
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
        );
    }

    public function decided($literalOrPackageId)
    {
        return !empty($this->decisionMap[abs($literalOrPackageId)]);
    }

    public function undecided($literalOrPackageId)
    {
        return empty($this->decisionMap[abs($literalOrPackageId)]);
    }

    public function decidedInstall($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);

        return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
    }

    public function decisionLevel($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);
        if (isset($this->decisionMap[$packageId])) {
            return abs($this->decisionMap[$packageId]);
        }

        return 0;
    }

    public function decisionRule($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);

        foreach ($this->decisionQueue as $i => $decision) {
            if ($packageId === abs($decision[self::DECISION_LITERAL])) {
                return $decision[self::DECISION_REASON];
            }
        }

        return null;
    }

    public function atOffset($queueOffset)
    {
        return $this->decisionQueue[$queueOffset];
    }

    public function validOffset($queueOffset)
    {
        return $queueOffset >= 0 && $queueOffset < count($this->decisionQueue);
    }

    public function lastReason()
    {
        return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_REASON];
    }

    public function lastLiteral()
    {
        return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_LITERAL];
    }

    public function reset()
    {
        while ($decision = array_pop($this->decisionQueue)) {
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function resetToOffset($offset)
    {
        while (count($this->decisionQueue) > $offset + 1) {
            $decision = array_pop($this->decisionQueue);
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function revertLast()
    {
        $this->decisionMap[abs($this->lastLiteral())] = 0;
        array_pop($this->decisionQueue);
    }

    public function count()
    {
        return count($this->decisionQueue);
    }

    public function rewind()
    {
        end($this->decisionQueue);
    }

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

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

    public function next()
    {
        return prev($this->decisionQueue);
    }

    public function valid()
    {
        return false !== current($this->decisionQueue);
    }

    public function isEmpty()
    {
        return count($this->decisionQueue) === 0;
    }

    protected function addDecision($literal, $level)
    {
        $packageId = abs($literal);

        $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
        if ($previousDecision != 0) {
            $literalString = $this->pool->literalToPrettyString($literal, array());
            $package = $this->pool->literalToPackage($literal);
            throw new SolverBugException(
                "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
            );
        }

        if ($literal > 0) {
            $this->decisionMap[$packageId] = $level;
        } else {
            $this->decisionMap[$packageId] = -$level;
        }
    }

    public function __toString()
    {
        $decisionMap = $this->decisionMap;
        ksort($decisionMap);
        $str = '[';
        foreach ($decisionMap as $packageId => $level) {
            $str .= $packageId.':'.$level.',';
        }
        $str .= ']';
        return $str;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSetIterator implements \Iterator
{
    protected $rules;
    protected $types;

    protected $currentOffset;
    protected $currentType;
    protected $currentTypeOffset;

    public function __construct(array $rules)
    {
        $this->rules = $rules;
        $this->types = array_keys($rules);
        sort($this->types);

        $this->rewind();
    }

    public function current()
    {
        return $this->rules[$this->currentType][$this->currentOffset];
    }

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

    public function next()
    {
        $this->currentOffset++;

        if (!isset($this->rules[$this->currentType])) {
            return;
        }

        if ($this->currentOffset >= count($this->rules[$this->currentType])) {
            $this->currentOffset = 0;

            do {
                $this->currentTypeOffset++;

                if (!isset($this->types[$this->currentTypeOffset])) {
                    $this->currentType = -1;
                    break;
                }

                $this->currentType = $this->types[$this->currentTypeOffset];
            } while (isset($this->types[$this->currentTypeOffset]) && !count($this->rules[$this->currentType]));
        }
    }

    public function rewind()
    {
        $this->currentOffset = 0;

        $this->currentTypeOffset = -1;
        $this->currentType = -1;

        do {
            $this->currentTypeOffset++;

            if (!isset($this->types[$this->currentTypeOffset])) {
                $this->currentType = -1;
                break;
            }

            $this->currentType = $this->types[$this->currentTypeOffset];
        } while (isset($this->types[$this->currentTypeOffset]) && !count($this->rules[$this->currentType]));
    }

    public function valid()
    {
        return isset($this->rules[$this->currentType])
               && isset($this->rules[$this->currentType][$this->currentOffset]);
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * Wrapper around a Rule which keeps track of the two literals it watches
 *
 * Used by RuleWatchGraph to store rules in two RuleWatchChains.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchNode
{
    public $watch1;
    public $watch2;

    protected $rule;

    /**
     * Creates a new node watching the first and second literals of the rule.
     *
     * @param Rule $rule The rule to wrap
     */
    public function __construct($rule)
    {
        $this->rule = $rule;

        $literals = $rule->getLiterals();

        $literalCount = count($literals);
        $this->watch1 = $literalCount > 0 ? $literals[0] : 0;
        $this->watch2 = $literalCount > 1 ? $literals[1] : 0;
    }

    /**
     * Places the second watch on the rule's literal, decided at the highest level
     *
     * Useful for learned rules where the literal for the highest rule is most
     * likely to quickly lead to further decisions.
     *
     * @param Decisions $decisions The decisions made so far by the solver
     */
    public function watch2OnHighest(Decisions $decisions)
    {
        $literals = $this->rule->getLiterals();

        // if there are only 2 elements, both are being watched anyway
        if (count($literals) < 3) {
            return;
        }

        $watchLevel = 0;

        foreach ($literals as $literal) {
            $level = $decisions->decisionLevel($literal);

            if ($level > $watchLevel) {
                $this->watch2 = $literal;
                $watchLevel = $level;
            }
        }
    }

    /**
     * Returns the rule this node wraps
     *
     * @return Rule
     */
    public function getRule()
    {
        return $this->rule;
    }

    /**
     * Given one watched literal, this method returns the other watched literal
     *
     * @param  int $literal The watched literal that should not be returned
     * @return int A literal
     */
    public function getOtherWatch($literal)
    {
        if ($this->watch1 == $literal) {
            return $this->watch2;
        }

        return $this->watch1;
    }

    /**
     * Moves a watch from one literal to another
     *
     * @param int $from The previously watched literal
     * @param int $to   The literal to be watched now
     */
    public function moveWatch($from, $to)
    {
        if ($this->watch1 == $from) {
            $this->watch1 = $to;
        } else {
            $this->watch2 = $to;
        }
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\CompletePackageInterface;

/**
 * Represents a problem detected while solving dependencies
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Problem
{
    /**
     * A map containing the id of each rule part of this problem as a key
     * @var array
     */
    protected $reasonSeen;

    /**
     * A set of reasons for the problem, each is a rule or a job and a rule
     * @var array
     */
    protected $reasons = array();

    protected $section = 0;

    protected $pool;

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    /**
     * Add a rule as a reason
     *
     * @param Rule $rule A rule which is a reason for this problem
     */
    public function addRule(Rule $rule)
    {
        $this->addReason(spl_object_hash($rule), array(
            'rule' => $rule,
            'job' => $rule->getJob(),
        ));
    }

    /**
     * Retrieve all reasons for this problem
     *
     * @return array The problem's reasons
     */
    public function getReasons()
    {
        return $this->reasons;
    }

    /**
     * A human readable textual representation of the problem's reasons
     *
     * @param  array  $installedMap A map of all installed packages
     * @return string
     */
    public function getPrettyString(array $installedMap = array())
    {
        $reasons = call_user_func_array('array_merge', array_reverse($this->reasons));

        if (count($reasons) === 1) {
            reset($reasons);
            $reason = current($reasons);

            $job = $reason['job'];

            $packageName = $job['packageName'];
            $constraint = $job['constraint'];

            if (isset($constraint)) {
                $packages = $this->pool->whatProvides($packageName, $constraint);
            } else {
                $packages = array();
            }

            if ($job && $job['cmd'] === 'install' && empty($packages)) {

                // handle php/hhvm
                if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
                    $version = phpversion();
                    $available = $this->pool->whatProvides($packageName);

                    if (count($available)) {
                        $firstAvailable = reset($available);
                        $version = $firstAvailable->getPrettyVersion();
                        $extra = $firstAvailable->getExtra();
                        if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
                            $version .= '; ' . $firstAvailable->getDescription();
                        }
                    }

                    $msg = "\n    - This package requires ".$packageName.$this->constraintToText($constraint).' but ';

                    if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
                        return $msg . 'your HHVM version does not satisfy that requirement.';
                    }

                    if ($packageName === 'hhvm') {
                        return $msg . 'you are running this with PHP and not HHVM.';
                    }

                    return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.';
                }

                // handle php extensions
                if (0 === stripos($packageName, 'ext-')) {
                    if (false !== strpos($packageName, ' ')) {
                        return "\n    - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
                    }

                    $ext = substr($packageName, 4);
                    $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';

                    return "\n    - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
                }

                // handle linked libs
                if (0 === stripos($packageName, 'lib-')) {
                    if (strtolower($packageName) === 'lib-icu') {
                        $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';

                        return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
                    }

                    return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
                }

                if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
                    $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);

                    return "\n    - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
                }

                if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) {
                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
                }

                if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
                }

                return "\n    - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
            }
        }

        $messages = array();

        foreach ($reasons as $reason) {
            $rule = $reason['rule'];
            $job = $reason['job'];

            if ($job) {
                $messages[] = $this->jobToText($job);
            } elseif ($rule) {
                if ($rule instanceof Rule) {
                    $messages[] = $rule->getPrettyString($this->pool, $installedMap);
                }
            }
        }

        return "\n    - ".implode("\n    - ", $messages);
    }

    /**
     * Store a reason descriptor but ignore duplicates
     *
     * @param string $id     A canonical identifier for the reason
     * @param string $reason The reason descriptor
     */
    protected function addReason($id, $reason)
    {
        if (!isset($this->reasonSeen[$id])) {
            $this->reasonSeen[$id] = true;
            $this->reasons[$this->section][] = $reason;
        }
    }

    public function nextSection()
    {
        $this->section++;
    }

    /**
     * Turns a job into a human readable description
     *
     * @param  array  $job
     * @return string
     */
    protected function jobToText($job)
    {
        $packageName = $job['packageName'];
        $constraint = $job['constraint'];
        switch ($job['cmd']) {
            case 'install':
                $packages = $this->pool->whatProvides($packageName, $constraint);
                if (!$packages) {
                    return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
                }

                return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.';
            case 'update':
                return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
            case 'remove':
                return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
        }

        if (isset($constraint)) {
            $packages = $this->pool->whatProvides($packageName, $constraint);
        } else {
            $packages = array();
        }

        return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])';
    }

    protected function getPackageList($packages)
    {
        $prepared = array();
        foreach ($packages as $package) {
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
        }
        foreach ($prepared as $name => $package) {
            $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $prepared);
    }

    /**
     * Turns a constraint into text usable in a sentence describing a job
     *
     * @param  \Composer\Semver\Constraint\ConstraintInterface $constraint
     * @return string
     */
    protected function constraintToText($constraint)
    {
        return $constraint ? ' '.$constraint->getPrettyString() : '';
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\Link;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Rule2Literals extends Rule
{
    protected $literal1;
    protected $literal2;

    /**
     * @param int                   $literal1
     * @param int                   $literal2
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct($literal1, $literal2, $reason, $reasonData, $job = null)
    {
        parent::__construct($reason, $reasonData, $job);

        if ($literal1 < $literal2) {
            $this->literal1 = $literal1;
            $this->literal2 = $literal2;
        } else {
            $this->literal1 = $literal2;
            $this->literal2 = $literal1;
        }
    }

    public function getLiterals()
    {
        return array($this->literal1, $this->literal2);
    }

    public function getHash()
    {
        return $this->literal1.','.$this->literal2;
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule)
    {
        // specialized fast-case
        if ($rule instanceof self) {
            if ($this->literal1 !== $rule->literal1) {
                return false;
            }

            if ($this->literal2 !== $rule->literal2) {
                return false;
            }

            return true;
        }

        $literals = $rule->getLiterals();
        if (2 != count($literals)) {
            return false;
        }

        if ($this->literal1 !== $literals[0]) {
            return false;
        }

        if ($this->literal2 !== $literals[1]) {
            return false;
        }

        return true;
    }

    public function isAssertion()
    {
        return false;
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     *
     * @return string
     */
    public function __toString()
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        $result .= $this->literal1 . '|' . $this->literal2 . ')';

        return $result;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * An extension of SplDoublyLinkedList with seek and removal of current element
 *
 * SplDoublyLinkedList only allows deleting a particular offset and has no
 * method to set the internal iterator to a particular offset.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchChain extends \SplDoublyLinkedList
{
    protected $offset = 0;

    /**
     * Moves the internal iterator to the specified offset
     *
     * @param int $offset The offset to seek to.
     */
    public function seek($offset)
    {
        $this->rewind();
        for ($i = 0; $i < $offset; $i++, $this->next());
    }

    /**
     * Removes the current element from the list
     *
     * As SplDoublyLinkedList only allows deleting a particular offset and
     * incorrectly sets the internal iterator if you delete the current value
     * this method sets the internal iterator back to the following element
     * using the seek method.
     */
    public function remove()
    {
        $offset = $this->key();
        $this->offsetUnset($offset);
        $this->seek($offset);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PolicyInterface
{
    public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);

    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);

    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null);
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Transaction
{
    protected $policy;
    protected $pool;
    protected $installedMap;
    protected $decisions;
    protected $transaction;

    public function __construct($policy, $pool, $installedMap, $decisions)
    {
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installedMap = $installedMap;
        $this->decisions = $decisions;
        $this->transaction = array();
    }

    public function getOperations()
    {
        $installMeansUpdateMap = $this->findUpdates();

        $updateMap = array();
        $installMap = array();
        $uninstallMap = array();

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $reason = $decision[Decisions::DECISION_REASON];

            $package = $this->pool->literalToPackage($literal);

            // wanted & installed || !wanted & !installed
            if (($literal > 0) == isset($this->installedMap[$package->id])) {
                continue;
            }

            if ($literal > 0) {
                if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
                    $source = $installMeansUpdateMap[abs($literal)];

                    $updateMap[$package->id] = array(
                        'package' => $package,
                        'source' => $source,
                        'reason' => $reason,
                    );

                    // avoid updates to one package from multiple origins
                    unset($installMeansUpdateMap[abs($literal)]);
                    $ignoreRemove[$source->id] = true;
                } else {
                    $installMap[$package->id] = array(
                        'package' => $package,
                        'reason' => $reason,
                    );
                }
            }
        }

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $reason = $decision[Decisions::DECISION_REASON];
            $package = $this->pool->literalToPackage($literal);

            if ($literal <= 0 &&
                isset($this->installedMap[$package->id]) &&
                !isset($ignoreRemove[$package->id])) {
                $uninstallMap[$package->id] = array(
                    'package' => $package,
                    'reason' => $reason,
                );
            }
        }

        $this->transactionFromMaps($installMap, $updateMap, $uninstallMap);

        return $this->transaction;
    }

    protected function transactionFromMaps($installMap, $updateMap, $uninstallMap)
    {
        $queue = array_map(
            function ($operation) {
                return $operation['package'];
            },
            $this->findRootPackages($installMap, $updateMap)
        );

        $visited = array();

        while (!empty($queue)) {
            $package = array_pop($queue);
            $packageId = $package->id;

            if (!isset($visited[$packageId])) {
                $queue[] = $package;

                if ($package instanceof AliasPackage) {
                    $queue[] = $package->getAliasOf();
                } else {
                    foreach ($package->getRequires() as $link) {
                        $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                        foreach ($possibleRequires as $require) {
                            $queue[] = $require;
                        }
                    }
                }

                $visited[$package->id] = true;
            } else {
                if (isset($installMap[$packageId])) {
                    $this->install(
                        $installMap[$packageId]['package'],
                        $installMap[$packageId]['reason']
                    );
                    unset($installMap[$packageId]);
                }
                if (isset($updateMap[$packageId])) {
                    $this->update(
                        $updateMap[$packageId]['source'],
                        $updateMap[$packageId]['package'],
                        $updateMap[$packageId]['reason']
                    );
                    unset($updateMap[$packageId]);
                }
            }
        }

        foreach ($uninstallMap as $uninstall) {
            $this->uninstall($uninstall['package'], $uninstall['reason']);
        }
    }

    protected function findRootPackages($installMap, $updateMap)
    {
        $packages = $installMap + $updateMap;
        $roots = $packages;

        foreach ($packages as $packageId => $operation) {
            $package = $operation['package'];

            if (!isset($roots[$packageId])) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                foreach ($possibleRequires as $require) {
                    if ($require !== $package) {
                        unset($roots[$require->id]);
                    }
                }
            }
        }

        return $roots;
    }

    protected function findUpdates()
    {
        $installMeansUpdateMap = array();

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $package = $this->pool->literalToPackage($literal);

            if ($package instanceof AliasPackage) {
                continue;
            }

            // !wanted & installed
            if ($literal <= 0 && isset($this->installedMap[$package->id])) {
                $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);

                $literals = array($package->id);

                foreach ($updates as $update) {
                    $literals[] = $update->id;
                }

                foreach ($literals as $updateLiteral) {
                    if ($updateLiteral !== $literal) {
                        $installMeansUpdateMap[abs($updateLiteral)] = $package;
                    }
                }
            }
        }

        return $installMeansUpdateMap;
    }

    protected function install($package, $reason)
    {
        if ($package instanceof AliasPackage) {
            return $this->markAliasInstalled($package, $reason);
        }

        $this->transaction[] = new Operation\InstallOperation($package, $reason);
    }

    protected function update($from, $to, $reason)
    {
        $this->transaction[] = new Operation\UpdateOperation($from, $to, $reason);
    }

    protected function uninstall($package, $reason)
    {
        if ($package instanceof AliasPackage) {
            return $this->markAliasUninstalled($package, $reason);
        }

        $this->transaction[] = new Operation\UninstallOperation($package, $reason);
    }

    protected function markAliasInstalled($package, $reason)
    {
        $this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason);
    }

    protected function markAliasUninstalled($package, $reason)
    {
        $this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\PlatformRepository;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSetGenerator
{
    protected $policy;
    protected $pool;
    protected $rules;
    protected $jobs;
    protected $installedMap;
    protected $allowListedMap;
    protected $addedMap;
    protected $conflictAddedMap;
    protected $addedPackages;
    protected $addedPackagesByNames;

    public function __construct(PolicyInterface $policy, Pool $pool)
    {
        $this->policy = $policy;
        $this->pool = $pool;
    }

    /**
     * Creates a new rule for the requirements of a package
     *
     * This rule is of the form (-A|B|C), where B and C are the providers of
     * one requirement of the package A.
     *
     * @param  PackageInterface $package    The package with a requirement
     * @param  array            $providers  The providers of the requirement
     * @param  int              $reason     A RULE_* constant describing the
     *                                      reason for generating this rule
     * @param  mixed            $reasonData Any data, e.g. the requirement name,
     *                                      that goes with the reason
     * @return Rule|null             The generated rule or null if tautological
     */
    protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
    {
        $literals = array(-$package->id);

        foreach ($providers as $provider) {
            // self fulfilling rule?
            if ($provider === $package) {
                return null;
            }
            $literals[] = $provider->id;
        }

        return new GenericRule($literals, $reason, $reasonData);
    }

    /**
     * Creates a rule to install at least one of a set of packages
     *
     * The rule is (A|B|C) with A, B and C different packages. If the given
     * set of packages is empty an impossible rule is generated.
     *
     * @param  array $packages The set of packages to choose from
     * @param  int   $reason   A RULE_* constant describing the reason for
     *                         generating this rule
     * @param  array $job      The job this rule was created from
     * @return Rule  The generated rule
     */
    protected function createInstallOneOfRule(array $packages, $reason, $job)
    {
        $literals = array();
        foreach ($packages as $package) {
            $literals[] = $package->id;
        }

        return new GenericRule($literals, $reason, $job['packageName'], $job);
    }

    /**
     * Creates a rule to remove a package
     *
     * The rule for a package A is (-A).
     *
     * @param  PackageInterface $package The package to be removed
     * @param  int              $reason  A RULE_* constant describing the
     *                                   reason for generating this rule
     * @param  array            $job     The job this rule was created from
     * @return Rule             The generated rule
     */
    protected function createRemoveRule(PackageInterface $package, $reason, $job)
    {
        return new GenericRule(array(-$package->id), $reason, $job['packageName'], $job);
    }

    /**
     * Creates a rule for two conflicting packages
     *
     * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
     * and B the provider.
     *
     * @param  PackageInterface $issuer     The package declaring the conflict
     * @param  PackageInterface $provider   The package causing the conflict
     * @param  int              $reason     A RULE_* constant describing the
     *                                      reason for generating this rule
     * @param  mixed            $reasonData Any data, e.g. the package name, that
     *                                      goes with the reason
     * @return Rule|null             The generated rule
     */
    protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
    {
        // ignore self conflict
        if ($issuer === $provider) {
            return null;
        }

        return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
    }

    /**
     * Adds a rule unless it duplicates an existing one of any type
     *
     * To be able to directly pass in the result of one of the rule creation
     * methods null is allowed which will not insert a rule.
     *
     * @param int  $type    A TYPE_* constant defining the rule type
     * @param Rule $newRule The rule about to be added
     */
    private function addRule($type, Rule $newRule = null)
    {
        if (!$newRule) {
            return;
        }

        $this->rules->add($newRule, $type);
    }

    protected function allowListFromPackage(PackageInterface $package)
    {
        // call original method for BC
        $this->whitelistFromPackage($package);
    }

    /**
     * @deprecated use whitelistFromPackage instead
     */
    protected function whitelistFromPackage(PackageInterface $package)
    {
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            $package = $workQueue->dequeue();
            if (isset($this->allowListedMap[$package->id])) {
                continue;
            }

            $this->allowListedMap[$package->id] = true;

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }

            $obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);

            foreach ($obsoleteProviders as $provider) {
                if ($provider === $package) {
                    continue;
                }

                if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                    $workQueue->enqueue($provider);
                }
            }
        }
    }

    protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs)
    {
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            /** @var PackageInterface $package */
            $package = $workQueue->dequeue();
            if (isset($this->addedMap[$package->id])) {
                continue;
            }

            $this->addedMap[$package->id] = true;

            $this->addedPackages[] = $package;
            foreach ($package->getNames() as $name) {
                $this->addedPackagesByNames[$name][] = $package;
            }

            foreach ($package->getRequires() as $link) {
                if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                    continue;
                }

                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }

            $packageName = $package->getName();
            $obsoleteProviders = $this->pool->whatProvides($packageName, null);

            foreach ($obsoleteProviders as $provider) {
                if ($provider === $package) {
                    continue;
                }

                if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
                } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                    $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
                }
            }
        }
    }

    protected function addConflictRules($ignorePlatformReqs = false)
    {
        /** @var PackageInterface $package */
        foreach ($this->addedPackages as $package) {
            foreach ($package->getConflicts() as $link) {
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                    continue;
                }

                /** @var PackageInterface $possibleConflict */
                foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
                    $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);

                    if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
                    }

                }
            }

            // check obsoletes and implicit obsoletes of a package
            $isInstalled = isset($this->installedMap[$package->id]);

            foreach ($package->getReplaces() as $link) {
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                /** @var PackageInterface $possibleConflict */
                foreach ($this->addedPackagesByNames[$link->getTarget()] as $provider) {
                    if ($provider === $package) {
                        continue;
                    }

                    if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                        $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
                    }
                }
            }
        }
    }

    protected function obsoleteImpossibleForAlias($package, $provider)
    {
        $packageIsAlias = $package instanceof AliasPackage;
        $providerIsAlias = $provider instanceof AliasPackage;

        $impossible = (
            ($packageIsAlias && $package->getAliasOf() === $provider) ||
            ($providerIsAlias && $provider->getAliasOf() === $package) ||
            ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
        );

        return $impossible;
    }

    protected function allowListFromJobs()
    {
        // call original method for BC
        $this->whitelistFromJobs();
    }

    /**
     * @deprecated use allowListFromJobs instead
     */
    protected function whitelistFromJobs()
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'install':
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
                    foreach ($packages as $package) {
                        $this->allowListFromPackage($package);
                    }
                    break;
            }
        }
    }

    protected function addRulesForJobs($ignorePlatformReqs)
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'install':
                    if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                        break;
                    }

                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    if ($packages) {
                        foreach ($packages as $package) {
                            if (!isset($this->installedMap[$package->id])) {
                                $this->addRulesForPackage($package, $ignorePlatformReqs);
                            }
                        }

                        $rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job);
                        $this->addRule(RuleSet::TYPE_JOB, $rule);
                    }
                    break;
                case 'remove':
                    // remove all packages with this name including uninstalled
                    // ones to make sure none of them are picked as replacements
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    foreach ($packages as $package) {
                        $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
                        $this->addRule(RuleSet::TYPE_JOB, $rule);
                    }
                    break;
            }
        }
    }

    public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false)
    {
        $this->jobs = $jobs;
        $this->rules = new RuleSet;
        $this->installedMap = $installedMap;

        $this->allowListedMap = array();
        foreach ($this->installedMap as $package) {
            $this->allowListFromPackage($package);
        }
        $this->allowListFromJobs();

        $this->pool->setAllowList($this->allowListedMap);

        $this->addedMap = array();
        $this->conflictAddedMap = array();
        $this->addedPackages = array();
        $this->addedPackagesByNames = array();
        foreach ($this->installedMap as $package) {
            $this->addRulesForPackage($package, $ignorePlatformReqs);
        }

        $this->addRulesForJobs($ignorePlatformReqs);

        $this->addConflictRules($ignorePlatformReqs);

        // Remove references to packages
        $this->addedPackages = $this->addedPackagesByNames = null;

        return $this->rules;
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Ruben Gonzalez <rubenrua@gmail.com>
 */
abstract class Rule
{
    // reason constants
    const RULE_INTERNAL_ALLOW_UPDATE = 1;
    const RULE_JOB_INSTALL = 2;
    const RULE_JOB_REMOVE = 3;
    const RULE_PACKAGE_CONFLICT = 6;
    const RULE_PACKAGE_REQUIRES = 7;
    const RULE_PACKAGE_OBSOLETES = 8;
    const RULE_INSTALLED_PACKAGE_OBSOLETES = 9;
    const RULE_PACKAGE_SAME_NAME = 10;
    const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11;
    const RULE_LEARNED = 12;
    const RULE_PACKAGE_ALIAS = 13;

    // bitfield defs
    const BITFIELD_TYPE = 0;
    const BITFIELD_REASON = 8;
    const BITFIELD_DISABLED = 16;

    protected $bitfield;
    protected $job;
    protected $reasonData;

    /**
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct($reason, $reasonData, $job = null)
    {
        $this->reasonData = $reasonData;

        if ($job) {
            $this->job = $job;
        }

        $this->bitfield = (0 << self::BITFIELD_DISABLED) |
            ($reason << self::BITFIELD_REASON) |
            (255 << self::BITFIELD_TYPE);
    }

    abstract public function getLiterals();

    abstract public function getHash();

    public function getJob()
    {
        return $this->job;
    }

    abstract public function equals(Rule $rule);

    public function getReason()
    {
        return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
    }

    public function getReasonData()
    {
        return $this->reasonData;
    }

    public function getRequiredPackage()
    {
        if ($this->getReason() === self::RULE_JOB_INSTALL) {
            return $this->reasonData;
        }

        if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) {
            return $this->reasonData->getTarget();
        }
    }

    public function setType($type)
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE);
    }

    public function getType()
    {
        return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
    }

    public function disable()
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED);
    }

    public function enable()
    {
        $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
    }

    public function isDisabled()
    {
        return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    public function isEnabled()
    {
        return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    abstract public function isAssertion();

    public function getPrettyString(Pool $pool, array $installedMap = array())
    {
        $literals = $this->getLiterals();

        $ruleText = '';
        foreach ($literals as $i => $literal) {
            if ($i != 0) {
                $ruleText .= '|';
            }
            $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
        }

        switch ($this->getReason()) {
            case self::RULE_INTERNAL_ALLOW_UPDATE:
                return $ruleText;

            case self::RULE_JOB_INSTALL:
                return "Install command rule ($ruleText)";

            case self::RULE_JOB_REMOVE:
                return "Remove command rule ($ruleText)";

            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $pool->literalToPackage($literals[0]);
                $package2 = $pool->literalToPackage($literals[1]);

                return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.';

            case self::RULE_PACKAGE_REQUIRES:
                $sourceLiteral = array_shift($literals);
                $sourcePackage = $pool->literalToPackage($sourceLiteral);

                $requires = array();
                foreach ($literals as $literal) {
                    $requires[] = $pool->literalToPackage($literal);
                }

                $text = $this->reasonData->getPrettyString($sourcePackage);
                if ($requires) {
                    $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.';
                } else {
                    $targetName = $this->reasonData->getTarget();

                    if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') {
                        // handle php/hhvm
                        if (defined('HHVM_VERSION')) {
                            return $text . ' -> your HHVM version does not satisfy that requirement.';
                        }

                        $packages = $pool->whatProvides($targetName);
                        $package = count($packages) ? current($packages) : phpversion();

                        if ($targetName === 'hhvm') {
                            if ($package instanceof CompletePackage) {
                                return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                            } else {
                                return $text . ' -> you are running this with PHP and not HHVM.';
                            }
                        }


                        if (!($package instanceof CompletePackage)) {
                            return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
                        }

                        $extra = $package->getExtra();

                        if (!empty($extra['config.platform'])) {
                            $text .= ' -> your PHP version ('.phpversion().') overridden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                        } else {
                            $text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                        }

                        return $text;
                    }

                    if (0 === strpos($targetName, 'ext-')) {
                        // handle php extensions
                        $ext = substr($targetName, 4);
                        $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';

                        return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.';
                    }

                    if (0 === strpos($targetName, 'lib-')) {
                        // handle linked libs
                        $lib = substr($targetName, 4);

                        return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.';
                    }

                    if ($providers = $pool->whatProvides($targetName, $this->reasonData->getConstraint(), true, true)) {
                        return $text . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $providers) .' but these conflict with your requirements or minimum-stability.';
                    }

                    return $text . ' -> no matching package found.';
                }

                return $text;

            case self::RULE_PACKAGE_OBSOLETES:
                return $ruleText;
            case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
                return $ruleText;
            case self::RULE_PACKAGE_SAME_NAME:
                return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
            case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
                return $ruleText;
            case self::RULE_LEARNED:
                return 'Conclusion: '.$ruleText;
            case self::RULE_PACKAGE_ALIAS:
                return $ruleText;
            default:
                return '('.$ruleText.')';
        }
    }

    /**
     * @param Pool  $pool
     * @param array $packages
     *
     * @return string
     */
    protected function formatPackagesUnique($pool, array $packages)
    {
        $prepared = array();
        foreach ($packages as $package) {
            if (!is_object($package)) {
                $package = $pool->literalToPackage($package);
            }
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
        }
        foreach ($prepared as $name => $package) {
            $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $prepared);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Request
{
    protected $jobs;

    public function __construct()
    {
        $this->jobs = array();
    }

    public function install($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'install', $constraint);
    }

    public function update($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'update', $constraint);
    }

    public function remove($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'remove', $constraint);
    }

    /**
     * Mark an existing package as being installed and having to remain installed
     *
     * These jobs will not be tempered with by the solver
     *
     * @param string                   $packageName
     * @param ConstraintInterface|null $constraint
     */
    public function fix($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'install', $constraint, true);
    }

    protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false)
    {
        $packageName = strtolower($packageName);

        $this->jobs[] = array(
            'cmd' => $cmd,
            'packageName' => $packageName,
            'constraint' => $constraint,
            'fixed' => $fixed,
        );
    }

    public function updateAll()
    {
        $this->jobs[] = array('cmd' => 'update-all');
    }

    public function getJobs()
    {
        return $this->jobs;
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;

/**
 * Solver update operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UpdateOperation extends SolverOperation
{
    protected $initialPackage;
    protected $targetPackage;

    /**
     * Initializes update operation.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  target package (updated)
     * @param string           $reason  update reason
     */
    public function __construct(PackageInterface $initial, PackageInterface $target, $reason = null)
    {
        parent::__construct($reason);

        $this->initialPackage = $initial;
        $this->targetPackage = $target;
    }

    /**
     * Returns initial package.
     *
     * @return PackageInterface
     */
    public function getInitialPackage()
    {
        return $this->initialPackage;
    }

    /**
     * Returns target package.
     *
     * @return PackageInterface
     */
    public function getTargetPackage()
    {
        return $this->targetPackage;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'update';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        $actionName = VersionParser::isUpgrade($this->initialPackage->getVersion(), $this->targetPackage->getVersion()) ? 'Updating' : 'Downgrading';

        return $actionName.' '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '.
            $this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasUninstalledOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param AliasPackage $package package instance
     * @param string       $reason  operation reason
     */
    public function __construct(AliasPackage $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'markAliasUninstalled';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Abstract solver operation class.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class SolverOperation implements OperationInterface
{
    protected $reason;

    /**
     * Initializes operation.
     *
     * @param string $reason operation reason
     */
    public function __construct($reason = null)
    {
        $this->reason = $reason;
    }

    /**
     * Returns operation reason.
     *
     * @return string
     */
    public function getReason()
    {
        return $this->reason;
    }

    protected function formatVersion(PackageInterface $package)
    {
        return $package->getFullPrettyVersion();
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver uninstall operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UninstallOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param PackageInterface $package package instance
     * @param string           $reason  operation reason
     */
    public function __construct(PackageInterface $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'uninstall';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

/**
 * Solver operation interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OperationInterface
{
    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType();

    /**
     * Returns operation reason.
     *
     * @return string
     */
    public function getReason();

    /**
     * Serializes the operation in a human readable format
     *
     * @return string
     */
    public function __toString();
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class InstallOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param PackageInterface $package package instance
     * @param string           $reason  operation reason
     */
    public function __construct(PackageInterface $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'install';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasInstalledOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param AliasPackage $package package instance
     * @param string       $reason  operation reason
     */
    public function __construct(AliasPackage $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'markAliasInstalled';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverBugException extends \RuntimeException
{
    public function __construct($message)
    {
        parent::__construct(
            $message."\nThis exception was most likely caused by a bug in Composer.\n".
            "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"
        );
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Util\IniHelper;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverProblemsException extends \RuntimeException
{
    protected $problems;
    protected $installedMap;

    public function __construct(array $problems, array $installedMap)
    {
        $this->problems = $problems;
        $this->installedMap = $installedMap;

        parent::__construct($this->createMessage(), 2);
    }

    protected function createMessage()
    {
        $text = "\n";
        $hasExtensionProblems = false;
        foreach ($this->problems as $i => $problem) {
            $text .= "  Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n";

            if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
                $hasExtensionProblems = true;
            }
        }

        if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
            $text .= "\nPotential causes:\n - This package is not installable via Composer 1.x, see <https://blog.packagist.com/deprecating-composer-1-support/>\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
        }

        if ($hasExtensionProblems) {
            $text .= $this->createExtensionHint();
        }

        return $text;
    }

    public function getProblems()
    {
        return $this->problems;
    }

    private function createExtensionHint()
    {
        $paths = IniHelper::getAll();

        if (count($paths) === 1 && empty($paths[0])) {
            return '';
        }

        $text = "\n  To enable extensions, verify that they are enabled in your .ini files:\n    - ";
        $text .= implode("\n    - ", $paths);
        $text .= "\n  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.";

        return $text;
    }

    private function hasExtensionProblems(array $reasonSets)
    {
        foreach ($reasonSets as $reasonSet) {
            foreach ($reasonSet as $reason) {
                if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) {
                    return true;
                }
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DefaultPolicy implements PolicyInterface
{
    private $preferStable;
    private $preferLowest;

    public function __construct($preferStable = false, $preferLowest = false)
    {
        $this->preferStable = $preferStable;
        $this->preferLowest = $preferLowest;
    }

    public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
    {
        if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) {
            return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB];
        }

        $constraint = new Constraint($operator, $b->getVersion());
        $version = new Constraint('==', $a->getVersion());

        return $constraint->matchSpecific($version, true);
    }

    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
    {
        $packages = array();

        foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
            if ($candidate !== $package) {
                $packages[] = $candidate;
            }
        }

        return $packages;
    }

    public function getPriority(Pool $pool, PackageInterface $package)
    {
        return $pool->getPriority($package->getRepository());
    }

    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
    {
        $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);

        foreach ($packages as &$literals) {
            $policy = $this;
            usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
                return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
            });
        }

        foreach ($packages as &$literals) {
            $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);

            $literals = $this->pruneToBestVersion($pool, $literals);

            $literals = $this->pruneRemoteAliases($pool, $literals);
        }

        $selected = call_user_func_array('array_merge', array_values($packages));

        // now sort the result across all packages to respect replaces across packages
        usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
            return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
        });

        return $selected;
    }

    protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
    {
        $packages = array();
        foreach ($literals as $literal) {
            $packageName = $pool->literalToPackage($literal)->getName();

            if (!isset($packages[$packageName])) {
                $packages[$packageName] = array();
            }

            if (isset($installedMap[abs($literal)])) {
                array_unshift($packages[$packageName], $literal);
            } else {
                $packages[$packageName][] = $literal;
            }
        }

        return $packages;
    }

    /**
     * @protected
     */
    public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
    {
        if ($a->getRepository() === $b->getRepository()) {
            // prefer aliases to the original package
            if ($a->getName() === $b->getName()) {
                $aAliased = $a instanceof AliasPackage;
                $bAliased = $b instanceof AliasPackage;
                if ($aAliased && !$bAliased) {
                    return -1; // use a
                }
                if (!$aAliased && $bAliased) {
                    return 1; // use b
                }
            }

            if (!$ignoreReplace) {
                // return original, not replaced
                if ($this->replaces($a, $b)) {
                    return 1; // use b
                }
                if ($this->replaces($b, $a)) {
                    return -1; // use a
                }

                // for replacers not replacing each other, put a higher prio on replacing
                // packages with the same vendor as the required package
                if ($requiredPackage && false !== ($pos = strpos($requiredPackage, '/'))) {
                    $requiredVendor = substr($requiredPackage, 0, $pos);

                    $aIsSameVendor = substr($a->getName(), 0, $pos) === $requiredVendor;
                    $bIsSameVendor = substr($b->getName(), 0, $pos) === $requiredVendor;

                    if ($bIsSameVendor !== $aIsSameVendor) {
                        return $aIsSameVendor ? -1 : 1;
                    }
                }
            }

            // priority equal, sort by package id to make reproducible
            if ($a->id === $b->id) {
                return 0;
            }

            return ($a->id < $b->id) ? -1 : 1;
        }

        if (isset($installedMap[$a->id])) {
            return -1;
        }

        if (isset($installedMap[$b->id])) {
            return 1;
        }

        return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1;
    }

    /**
     * Checks if source replaces a package with the same name as target.
     *
     * Replace constraints are ignored. This method should only be used for
     * prioritisation, not for actual constraint verification.
     *
     * @param  PackageInterface $source
     * @param  PackageInterface $target
     * @return bool
     */
    protected function replaces(PackageInterface $source, PackageInterface $target)
    {
        foreach ($source->getReplaces() as $link) {
            if ($link->getTarget() === $target->getName()
//                && (null === $link->getConstraint() ||
//                $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) {
                ) {
                return true;
            }
        }

        return false;
    }

    protected function pruneToBestVersion(Pool $pool, $literals)
    {
        $operator = $this->preferLowest ? '<' : '>';
        $bestLiterals = array($literals[0]);
        $bestPackage = $pool->literalToPackage($literals[0]);
        foreach ($literals as $i => $literal) {
            if (0 === $i) {
                continue;
            }

            $package = $pool->literalToPackage($literal);

            if ($this->versionCompare($package, $bestPackage, $operator)) {
                $bestPackage = $package;
                $bestLiterals = array($literal);
            } elseif ($this->versionCompare($package, $bestPackage, '==')) {
                $bestLiterals[] = $literal;
            }
        }

        return $bestLiterals;
    }

    /**
     * Assumes that installed packages come first and then all highest priority packages
     */
    protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
    {
        $selected = array();

        $priority = null;

        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if (isset($installedMap[$package->id])) {
                $selected[] = $literal;
                continue;
            }

            if (null === $priority) {
                $priority = $this->getPriority($pool, $package);
            }

            if ($this->getPriority($pool, $package) != $priority) {
                break;
            }

            $selected[] = $literal;
        }

        return $selected;
    }

    /**
     * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
     *
     * If no package is a local alias, nothing happens
     */
    protected function pruneRemoteAliases(Pool $pool, array $literals)
    {
        $hasLocalAlias = false;

        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $hasLocalAlias = true;
                break;
            }
        }

        if (!$hasLocalAlias) {
            return $literals;
        }

        $selected = array();
        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $selected[] = $literal;
            }
        }

        return $selected;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSet implements \IteratorAggregate, \Countable
{
    // highest priority => lowest number
    const TYPE_PACKAGE = 0;
    const TYPE_JOB = 1;
    const TYPE_LEARNED = 4;

    /**
     * READ-ONLY: Lookup table for rule id to rule object
     *
     * @var Rule[]
     */
    public $ruleById;

    protected static $types = array(
        255 => 'UNKNOWN',
        self::TYPE_PACKAGE => 'PACKAGE',
        self::TYPE_JOB => 'JOB',
        self::TYPE_LEARNED => 'LEARNED',
    );

    protected $rules;
    protected $nextRuleId;

    protected $rulesByHash;

    public function __construct()
    {
        $this->nextRuleId = 0;

        foreach ($this->getTypes() as $type) {
            $this->rules[$type] = array();
        }

        $this->rulesByHash = array();
    }

    public function add(Rule $rule, $type)
    {
        if (!isset(self::$types[$type])) {
            throw new \OutOfBoundsException('Unknown rule type: ' . $type);
        }

        $hash = $rule->getHash();

        // Do not add if rule already exists
        if (isset($this->rulesByHash[$hash])) {
            $potentialDuplicates = $this->rulesByHash[$hash];
            if (is_array($potentialDuplicates)) {
                foreach ($potentialDuplicates as $potentialDuplicate) {
                    if ($rule->equals($potentialDuplicate)) {
                        return;
                    }
                }
            } else {
                if ($rule->equals($potentialDuplicates)) {
                    return;
                }
            }
        }

        if (!isset($this->rules[$type])) {
            $this->rules[$type] = array();
        }

        $this->rules[$type][] = $rule;
        $this->ruleById[$this->nextRuleId] = $rule;
        $rule->setType($type);

        $this->nextRuleId++;

        if (!isset($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash] = $rule;
        } elseif (is_array($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash][] = $rule;
        } else {
            $originalRule = $this->rulesByHash[$hash];
            $this->rulesByHash[$hash] = array($originalRule, $rule);
        }
    }

    public function count()
    {
        return $this->nextRuleId;
    }

    public function ruleById($id)
    {
        return $this->ruleById[$id];
    }

    public function getRules()
    {
        return $this->rules;
    }

    public function getIterator()
    {
        return new RuleSetIterator($this->getRules());
    }

    public function getIteratorFor($types)
    {
        if (!is_array($types)) {
            $types = array($types);
        }

        $allRules = $this->getRules();
        $rules = array();

        foreach ($types as $type) {
            $rules[$type] = $allRules[$type];
        }

        return new RuleSetIterator($rules);
    }

    public function getIteratorWithout($types)
    {
        if (!is_array($types)) {
            $types = array($types);
        }

        $rules = $this->getRules();

        foreach ($types as $type) {
            unset($rules[$type]);
        }

        return new RuleSetIterator($rules);
    }

    public function getTypes()
    {
        $types = self::$types;
        unset($types[255]);

        return array_keys($types);
    }

    public function getPrettyString(Pool $pool = null)
    {
        $string = "\n";
        foreach ($this->rules as $type => $rules) {
            $string .= str_pad(self::$types[$type], 8, ' ') . ": ";
            foreach ($rules as $rule) {
                $string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
            }
            $string .= "\n\n";
        }

        return $string;
    }

    public function __toString()
    {
        return $this->getPrettyString(null);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\PlatformRepository;
use Composer\Package\PackageInterface;

/**
 * A package pool contains repositories that provide packages.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Pool implements \Countable
{
    const MATCH_NAME = -1;
    const MATCH_NONE = 0;
    const MATCH = 1;
    const MATCH_PROVIDE = 2;
    const MATCH_REPLACE = 3;
    const MATCH_FILTERED = 4;

    protected $repositories = array();
    protected $providerRepos = array();
    protected $packages = array();
    protected $packageByName = array();
    protected $packageByExactName = array();
    protected $acceptableStabilities;
    protected $stabilityFlags;
    protected $versionParser;
    protected $providerCache = array();
    protected $filterRequires;
    protected $whitelist = null; // TODO 2.0 rename to allowList
    protected $id = 1;

    public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
    {
        $this->versionParser = new VersionParser;
        $this->acceptableStabilities = array();
        foreach (BasePackage::$stabilities as $stability => $value) {
            if ($value <= BasePackage::$stabilities[$minimumStability]) {
                $this->acceptableStabilities[$stability] = $value;
            }
        }
        $this->stabilityFlags = $stabilityFlags;
        $this->filterRequires = $filterRequires;
        foreach ($filterRequires as $name => $constraint) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
                unset($this->filterRequires[$name]);
            }
        }
    }

    public function setAllowList($allowList)
    {
        // call original method for BC
        $this->setWhitelist($allowList);
    }

    /**
     * @deprecated use setAllowList instead
     */
    public function setWhitelist($whitelist)
    {
        $this->whitelist = $whitelist;
        $this->providerCache = array();
    }

    /**
     * Adds a repository and its packages to this package pool
     *
     * @param RepositoryInterface $repo        A package repository
     * @param array               $rootAliases
     */
    public function addRepository(RepositoryInterface $repo, $rootAliases = array())
    {
        if ($repo instanceof CompositeRepository) {
            $repos = $repo->getRepositories();
        } else {
            $repos = array($repo);
        }

        foreach ($repos as $repo) {
            $this->repositories[] = $repo;

            $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;

            if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
                $this->providerRepos[] = $repo;
                $repo->setRootAliases($rootAliases);
                $repo->resetPackageIds();
            } else {
                foreach ($repo->getPackages() as $package) {
                    $names = $package->getNames();
                    $stability = $package->getStability();
                    if ($exempt || $this->isPackageAcceptable($names, $stability)) {
                        $package->setId($this->id++);
                        $this->packages[] = $package;
                        $this->packageByExactName[$package->getName()][$package->id] = $package;

                        foreach ($names as $provided) {
                            $this->packageByName[$provided][] = $package;
                        }

                        // handle root package aliases
                        $name = $package->getName();
                        if (isset($rootAliases[$name][$package->getVersion()])) {
                            $alias = $rootAliases[$name][$package->getVersion()];
                            if ($package instanceof AliasPackage) {
                                $package = $package->getAliasOf();
                            }
                            $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                            $aliasPackage->setRootPackageAlias(true);
                            $aliasPackage->setId($this->id++);

                            $package->getRepository()->addPackage($aliasPackage);
                            $this->packages[] = $aliasPackage;
                            $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;

                            foreach ($aliasPackage->getNames() as $name) {
                                $this->packageByName[$name][] = $aliasPackage;
                            }
                        }
                    }
                }
            }
        }
    }

    public function getPriority(RepositoryInterface $repo)
    {
        $priority = array_search($repo, $this->repositories, true);

        if (false === $priority) {
            throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
        }

        return -$priority;
    }

    /**
     * Retrieves the package object for a given package id.
     *
     * @param  int              $id
     * @return PackageInterface
     */
    public function packageById($id)
    {
        return $this->packages[$id - 1];
    }

    /**
     * Returns how many packages have been loaded into the pool
     */
    public function count()
    {
        return count($this->packages);
    }

    /**
     * Searches all packages providing the given package name and match the constraint
     *
     * @param  string              $name          The package name to be searched for
     * @param  ConstraintInterface $constraint    A constraint that all returned
     *                                            packages must match or null to return all
     * @param  bool                $mustMatchName Whether the name of returned packages
     *                                            must match the given name
     * @param  bool                $bypassFilters If enabled, filterRequires and stability matching is ignored
     * @return PackageInterface[]  A set of packages
     */
    public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false, $bypassFilters = false)
    {
        if ($bypassFilters) {
            return $this->computeWhatProvides($name, $constraint, $mustMatchName, true);
        }

        $key = ((int) $mustMatchName).$constraint;
        if (isset($this->providerCache[$name][$key])) {
            return $this->providerCache[$name][$key];
        }

        return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName, $bypassFilters);
    }

    /**
     * @see whatProvides
     */
    private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false)
    {
        $candidates = array();

        foreach ($this->providerRepos as $repo) {
            foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) {
                $candidates[] = $candidate;
                if ($candidate->id < 1) {
                    $candidate->setId($this->id++);
                    $this->packages[$this->id - 2] = $candidate;
                }
            }
        }

        if ($mustMatchName) {
            $candidates = array_filter($candidates, function ($candidate) use ($name) {
                return $candidate->getName() == $name;
            });
            if (isset($this->packageByExactName[$name])) {
                $candidates = array_merge($candidates, $this->packageByExactName[$name]);
            }
        } elseif (isset($this->packageByName[$name])) {
            $candidates = array_merge($candidates, $this->packageByName[$name]);
        }

        $matches = $provideMatches = array();
        $nameMatch = false;

        foreach ($candidates as $candidate) {
            $aliasOfCandidate = null;

            // alias packages are not white listed, make sure that the package
            // being aliased is white listed
            if ($candidate instanceof AliasPackage) {
                $aliasOfCandidate = $candidate->getAliasOf();
            }

            if ($this->whitelist !== null && !$bypassFilters && (
                (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
                ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
            )) {
                continue;
            }
            switch ($this->match($candidate, $name, $constraint, $bypassFilters)) {
                case self::MATCH_NONE:
                    break;

                case self::MATCH_NAME:
                    $nameMatch = true;
                    break;

                case self::MATCH:
                    $nameMatch = true;
                    $matches[] = $candidate;
                    break;

                case self::MATCH_PROVIDE:
                    $provideMatches[] = $candidate;
                    break;

                case self::MATCH_REPLACE:
                    $matches[] = $candidate;
                    break;

                case self::MATCH_FILTERED:
                    break;

                default:
                    throw new \UnexpectedValueException('Unexpected match type');
            }
        }

        // if a package with the required name exists, we ignore providers
        if ($nameMatch) {
            return $matches;
        }

        return array_merge($matches, $provideMatches);
    }

    public function literalToPackage($literal)
    {
        $packageId = abs($literal);

        return $this->packageById($packageId);
    }

    public function literalToPrettyString($literal, $installedMap)
    {
        $package = $this->literalToPackage($literal);

        if (isset($installedMap[$package->id])) {
            $prefix = ($literal > 0 ? 'keep' : 'remove');
        } else {
            $prefix = ($literal > 0 ? 'install' : 'don\'t install');
        }

        return $prefix.' '.$package->getPrettyString();
    }

    public function isPackageAcceptable($name, $stability)
    {
        foreach ((array) $name as $n) {
            // allow if package matches the global stability requirement and has no exception
            if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
                return true;
            }

            // allow if package matches the package-specific stability flag
            if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks if the package matches the given constraint directly or through
     * provided or replaced packages
     *
     * @param  PackageInterface       $candidate
     * @param  string                 $name       Name of the package to be matched
     * @param  ConstraintInterface    $constraint The constraint to verify
     * @return int                    One of the MATCH* constants of this class or 0 if there is no match
     */
    public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters)
    {
        $candidateName = $candidate->getName();
        $candidateVersion = $candidate->getVersion();
        $isDev = $candidate->getStability() === 'dev';
        $isAlias = $candidate instanceof AliasPackage;

        if (!$bypassFilters && !$isDev && !$isAlias && isset($this->filterRequires[$name])) {
            $requireFilter = $this->filterRequires[$name];
        } else {
            $requireFilter = new EmptyConstraint;
        }

        if ($candidateName === $name) {
            $pkgConstraint = new Constraint('==', $candidateVersion);

            if ($constraint === null || $constraint->matches($pkgConstraint)) {
                return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED;
            }

            return self::MATCH_NAME;
        }

        $provides = $candidate->getProvides();
        $replaces = $candidate->getReplaces();

        // aliases create multiple replaces/provides for one target so they can not use the shortcut below
        if (isset($replaces[0]) || isset($provides[0])) {
            foreach ($provides as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return $requireFilter->matches($link->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
                }
            }

            foreach ($replaces as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return $requireFilter->matches($link->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
                }
            }

            return self::MATCH_NONE;
        }

        if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
            return $requireFilter->matches($provides[$name]->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
        }

        if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
            return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
        }

        return self::MATCH_NONE;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * The RuleWatchGraph efficiently propagates decisions to other rules
 *
 * All rules generated for solving a SAT problem should be inserted into the
 * graph. When a decision on a literal is made, the graph can be used to
 * propagate the decision to all other rules involving the literal, leading to
 * other trivial decisions resulting from unit clauses.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchGraph
{
    protected $watchChains = array();

    /**
     * Inserts a rule node into the appropriate chains within the graph
     *
     * The node is prepended to the watch chains for each of the two literals it
     * watches.
     *
     * Assertions are skipped because they only depend on a single package and
     * have no alternative literal that could be true, so there is no need to
     * watch changes in any literals.
     *
     * @param RuleWatchNode $node The rule node to be inserted into the graph
     */
    public function insert(RuleWatchNode $node)
    {
        if ($node->getRule()->isAssertion()) {
            return;
        }

        foreach (array($node->watch1, $node->watch2) as $literal) {
            if (!isset($this->watchChains[$literal])) {
                $this->watchChains[$literal] = new RuleWatchChain;
            }

            $this->watchChains[$literal]->unshift($node);
        }
    }

    /**
     * Propagates a decision on a literal to all rules watching the literal
     *
     * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
     * (-A|+B|+C) now need to satisfy at least one of the other literals, so
     * that the rule as a whole becomes true, since with +A applied the rule
     * is now (false|+B|+C) so essentially (+B|+C).
     *
     * This means that all rules watching the literal -A need to be updated to
     * watch 2 other literals which can still be satisfied instead. So literals
     * that conflict with previously made decisions are not an option.
     *
     * Alternatively it can occur that a unit clause results: e.g. if in the
     * above example the rule was (-A|+B), then A turning true means that
     * B must now be decided true as well.
     *
     * @param  int       $decidedLiteral The literal which was decided (A in our example)
     * @param  int       $level          The level at which the decision took place and at which
     *                                   all resulting decisions should be made.
     * @param  Decisions $decisions      Used to check previous decisions and to
     *                                   register decisions resulting from propagation
     * @return Rule|null If a conflict is found the conflicting rule is returned
     */
    public function propagateLiteral($decidedLiteral, $level, $decisions)
    {
        // we invert the decided literal here, example:
        // A was decided => (-A|B) now requires B to be true, so we look for
        // rules which are fulfilled by -A, rather than A.
        $literal = -$decidedLiteral;

        if (!isset($this->watchChains[$literal])) {
            return null;
        }

        $chain = $this->watchChains[$literal];

        $chain->rewind();
        while ($chain->valid()) {
            $node = $chain->current();
            $otherWatch = $node->getOtherWatch($literal);

            if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
                $ruleLiterals = $node->getRule()->getLiterals();

                $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
                    return $literal !== $ruleLiteral &&
                        $otherWatch !== $ruleLiteral &&
                        !$decisions->conflict($ruleLiteral);
                });

                if ($alternativeLiterals) {
                    reset($alternativeLiterals);
                    $this->moveWatch($literal, current($alternativeLiterals), $node);
                    continue;
                }

                if ($decisions->conflict($otherWatch)) {
                    return $node->getRule();
                }

                $decisions->decide($otherWatch, $level, $node->getRule());
            }

            $chain->next();
        }

        return null;
    }

    /**
     * Moves a rule node from one watch chain to another
     *
     * The rule node's watched literals are updated accordingly.
     *
     * @param int           $fromLiteral A literal the node used to watch
     * @param int           $toLiteral A literal the node should watch now
     * @param RuleWatchNode $node The rule node to be moved
     */
    protected function moveWatch($fromLiteral, $toLiteral, $node)
    {
        if (!isset($this->watchChains[$toLiteral])) {
            $this->watchChains[$toLiteral] = new RuleWatchChain;
        }

        $node->moveWatch($fromLiteral, $toLiteral);
        $this->watchChains[$fromLiteral]->remove();
        $this->watchChains[$toLiteral]->unshift($node);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\IO\IOInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Solver
{
    const BRANCH_LITERALS = 0;
    const BRANCH_LEVEL = 1;

    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;
    /** @var RepositoryInterface */
    protected $installed;
    /** @var RuleSet */
    protected $rules;
    /** @var RuleSetGenerator */
    protected $ruleSetGenerator;
    /** @var array */
    protected $jobs;

    /** @var int[] */
    protected $updateMap = array();
    /** @var RuleWatchGraph */
    protected $watchGraph;
    /** @var Decisions */
    protected $decisions;
    /** @var int[] */
    protected $installedMap;

    /** @var int */
    protected $propagateIndex;
    /** @var array[] */
    protected $branches = array();
    /** @var Problem[] */
    protected $problems = array();
    /** @var array */
    protected $learnedPool = array();
    /** @var array */
    protected $learnedWhy = array();

    /** @var bool */
    public $testFlagLearnedPositiveLiteral = false;

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

    /**
     * @param PolicyInterface     $policy
     * @param Pool                $pool
     * @param RepositoryInterface $installed
     * @param IOInterface         $io
     */
    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
    {
        $this->io = $io;
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installed = $installed;
        $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
    }

    /**
     * @return int
     */
    public function getRuleSetSize()
    {
        return count($this->rules);
    }

    // aka solver_makeruledecisions

    private function makeAssertionRuleDecisions()
    {
        $decisionStart = count($this->decisions) - 1;

        $rulesCount = count($this->rules);
        for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
            $rule = $this->rules->ruleById[$ruleIndex];

            if (!$rule->isAssertion() || $rule->isDisabled()) {
                continue;
            }

            $literals = $rule->getLiterals();
            $literal = $literals[0];

            if (!$this->decisions->decided($literal)) {
                $this->decisions->decide($literal, 1, $rule);
                continue;
            }

            if ($this->decisions->satisfy($literal)) {
                continue;
            }

            // found a conflict
            if (RuleSet::TYPE_LEARNED === $rule->getType()) {
                $rule->disable();
                continue;
            }

            $conflict = $this->decisions->decisionRule($literal);

            if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
                $problem = new Problem($this->pool);

                $problem->addRule($rule);
                $problem->addRule($conflict);
                $this->disableProblem($rule);
                $this->problems[] = $problem;
                continue;
            }

            // conflict with another job
            $problem = new Problem($this->pool);
            $problem->addRule($rule);
            $problem->addRule($conflict);

            // push all of our rules (can only be job rules)
            // asserting this literal on the problem stack
            foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
                if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
                    continue;
                }

                $assertRuleLiterals = $assertRule->getLiterals();
                $assertRuleLiteral = $assertRuleLiterals[0];

                if (abs($literal) !== abs($assertRuleLiteral)) {
                    continue;
                }

                $problem->addRule($assertRule);
                $this->disableProblem($assertRule);
            }
            $this->problems[] = $problem;

            $this->decisions->resetToOffset($decisionStart);
            $ruleIndex = -1;
        }
    }

    protected function setupInstalledMap()
    {
        $this->installedMap = array();
        foreach ($this->installed->getPackages() as $package) {
            $this->installedMap[$package->id] = $package;
        }
    }

    /**
     * @param bool $ignorePlatformReqs
     */
    protected function checkForRootRequireProblems($ignorePlatformReqs)
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'update':
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    foreach ($packages as $package) {
                        if (isset($this->installedMap[$package->id])) {
                            $this->updateMap[$package->id] = true;
                        }
                    }
                    break;

                case 'update-all':
                    foreach ($this->installedMap as $package) {
                        $this->updateMap[$package->id] = true;
                    }
                    break;

                case 'install':
                    if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                        break;
                    }

                    if (!$this->pool->whatProvides($job['packageName'], $job['constraint'])) {
                        $problem = new Problem($this->pool);
                        $problem->addRule(new GenericRule(array(), null, null, $job));
                        $this->problems[] = $problem;
                    }
                    break;
            }
        }
    }

    /**
     * @param  Request $request
     * @param  bool    $ignorePlatformReqs
     * @return array
     */
    public function solve(Request $request, $ignorePlatformReqs = false)
    {
        $this->jobs = $request->getJobs();

        $this->setupInstalledMap();
        $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
        $this->checkForRootRequireProblems($ignorePlatformReqs);
        $this->decisions = new Decisions($this->pool);
        $this->watchGraph = new RuleWatchGraph;

        foreach ($this->rules as $rule) {
            $this->watchGraph->insert(new RuleWatchNode($rule));
        }

        /* make decisions based on job/update assertions */
        $this->makeAssertionRuleDecisions();

        $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
        $before = microtime(true);
        $this->runSat(true);
        $this->io->writeError('', true, IOInterface::DEBUG);
        $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);

        // decide to remove everything that's installed and undecided
        foreach ($this->installedMap as $packageId => $void) {
            if ($this->decisions->undecided($packageId)) {
                $this->decisions->decide(-$packageId, 1, null);
            }
        }

        if ($this->problems) {
            throw new SolverProblemsException($this->problems, $this->installedMap);
        }

        $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);

        return $transaction->getOperations();
    }

    /**
     * Makes a decision and propagates it to all rules.
     *
     * Evaluates each term affected by the decision (linked through watches)
     * If we find unit rules we make new decisions based on them
     *
     * @param  int       $level
     * @return Rule|null A rule on conflict, otherwise null.
     */
    protected function propagate($level)
    {
        while ($this->decisions->validOffset($this->propagateIndex)) {
            $decision = $this->decisions->atOffset($this->propagateIndex);

            $conflict = $this->watchGraph->propagateLiteral(
                $decision[Decisions::DECISION_LITERAL],
                $level,
                $this->decisions
            );

            $this->propagateIndex++;

            if ($conflict) {
                return $conflict;
            }
        }

        return null;
    }

    /**
     * Reverts a decision at the given level.
     *
     * @param int $level
     */
    private function revert($level)
    {
        while (!$this->decisions->isEmpty()) {
            $literal = $this->decisions->lastLiteral();

            if ($this->decisions->undecided($literal)) {
                break;
            }

            $decisionLevel = $this->decisions->decisionLevel($literal);

            if ($decisionLevel <= $level) {
                break;
            }

            $this->decisions->revertLast();
            $this->propagateIndex = count($this->decisions);
        }

        while (!empty($this->branches) && $this->branches[count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) {
            array_pop($this->branches);
        }
    }

    /**
     * setpropagatelearn
     *
     * add free decision (a positive literal) to decision queue
     * increase level and propagate decision
     * return if no conflict.
     *
     * in conflict case, analyze conflict rule, add resulting
     * rule to learnt rule set, make decision from learnt
     * rule (always unit) and re-propagate.
     *
     * returns the new solver level or 0 if unsolvable
     *
     * @param  int        $level
     * @param  string|int $literal
     * @param  bool       $disableRules
     * @param  Rule       $rule
     * @return int
     */
    private function setPropagateLearn($level, $literal, $disableRules, Rule $rule)
    {
        $level++;

        $this->decisions->decide($literal, $level, $rule);

        while (true) {
            $rule = $this->propagate($level);

            if (!$rule) {
                break;
            }

            if ($level == 1) {
                return $this->analyzeUnsolvable($rule, $disableRules);
            }

            // conflict
            list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);

            if ($newLevel <= 0 || $newLevel >= $level) {
                throw new SolverBugException(
                    "Trying to revert to invalid level ".(int) $newLevel." from level ".(int) $level."."
                );
            } elseif (!$newRule) {
                throw new SolverBugException(
                    "No rule was learned from analyzing $rule at level $level."
                );
            }

            $level = $newLevel;

            $this->revert($level);

            $this->rules->add($newRule, RuleSet::TYPE_LEARNED);

            $this->learnedWhy[spl_object_hash($newRule)] = $why;

            $ruleNode = new RuleWatchNode($newRule);
            $ruleNode->watch2OnHighest($this->decisions);
            $this->watchGraph->insert($ruleNode);

            $this->decisions->decide($learnLiteral, $level, $newRule);
        }

        return $level;
    }

    /**
     * @param  int   $level
     * @param  array $decisionQueue
     * @param  bool  $disableRules
     * @param  Rule  $rule
     * @return int
     */
    private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
    {
        // choose best package to install from decisionQueue
        $literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage());

        $selectedLiteral = array_shift($literals);

        // if there are multiple candidates, then branch
        if (count($literals)) {
            $this->branches[] = array($literals, $level);
        }

        return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
    }

    /**
     * @param  int   $level
     * @param  Rule  $rule
     * @return array
     */
    protected function analyze($level, Rule $rule)
    {
        $analyzedRule = $rule;
        $ruleLevel = 1;
        $num = 0;
        $l1num = 0;
        $seen = array();
        $learnedLiterals = array(null);

        $decisionId = count($this->decisions);

        $this->learnedPool[] = array();

        while (true) {
            $this->learnedPool[count($this->learnedPool) - 1][] = $rule;

            foreach ($rule->getLiterals() as $literal) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }

                if (isset($seen[abs($literal)])) {
                    continue;
                }
                $seen[abs($literal)] = true;

                $l = $this->decisions->decisionLevel($literal);

                if (1 === $l) {
                    $l1num++;
                } elseif ($level === $l) {
                    $num++;
                } else {
                    // not level1 or conflict level, add to new rule
                    $learnedLiterals[] = $literal;

                    if ($l > $ruleLevel) {
                        $ruleLevel = $l;
                    }
                }
            }

            $l1retry = true;
            while ($l1retry) {
                $l1retry = false;

                if (!$num && !--$l1num) {
                    // all level 1 literals done
                    break 2;
                }

                while (true) {
                    if ($decisionId <= 0) {
                        throw new SolverBugException(
                            "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule."
                        );
                    }

                    $decisionId--;

                    $decision = $this->decisions->atOffset($decisionId);
                    $literal = $decision[Decisions::DECISION_LITERAL];

                    if (isset($seen[abs($literal)])) {
                        break;
                    }
                }

                unset($seen[abs($literal)]);

                if ($num && 0 === --$num) {
                    if ($literal < 0) {
                        $this->testFlagLearnedPositiveLiteral = true;
                    }
                    $learnedLiterals[0] = -$literal;

                    if (!$l1num) {
                        break 2;
                    }

                    foreach ($learnedLiterals as $i => $learnedLiteral) {
                        if ($i !== 0) {
                            unset($seen[abs($learnedLiteral)]);
                        }
                    }
                    // only level 1 marks left
                    $l1num++;
                    $l1retry = true;
                }
            }

            $decision = $this->decisions->atOffset($decisionId);
            $rule = $decision[Decisions::DECISION_REASON];
        }

        $why = count($this->learnedPool) - 1;

        if (!$learnedLiterals[0]) {
            throw new SolverBugException(
                "Did not find a learnable literal in analyzed rule $analyzedRule."
            );
        }

        $newRule = new GenericRule($learnedLiterals, Rule::RULE_LEARNED, $why);

        return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
    }

    /**
     * @param Problem $problem
     * @param Rule    $conflictRule
     */
    private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule)
    {
        if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
            $why = spl_object_hash($conflictRule);
            $learnedWhy = $this->learnedWhy[$why];
            $problemRules = $this->learnedPool[$learnedWhy];

            foreach ($problemRules as $problemRule) {
                $this->analyzeUnsolvableRule($problem, $problemRule);
            }

            return;
        }

        if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
            // package rules cannot be part of a problem
            return;
        }

        $problem->nextSection();
        $problem->addRule($conflictRule);
    }

    /**
     * @param  Rule $conflictRule
     * @param  bool $disableRules
     * @return int
     */
    private function analyzeUnsolvable(Rule $conflictRule, $disableRules)
    {
        $problem = new Problem($this->pool);
        $problem->addRule($conflictRule);

        $this->analyzeUnsolvableRule($problem, $conflictRule);

        $this->problems[] = $problem;

        $seen = array();
        $literals = $conflictRule->getLiterals();

        foreach ($literals as $literal) {
            // skip the one true literal
            if ($this->decisions->satisfy($literal)) {
                continue;
            }
            $seen[abs($literal)] = true;
        }

        foreach ($this->decisions as $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];

            // skip literals that are not in this rule
            if (!isset($seen[abs($literal)])) {
                continue;
            }

            $why = $decision[Decisions::DECISION_REASON];

            $problem->addRule($why);
            $this->analyzeUnsolvableRule($problem, $why);

            $literals = $why->getLiterals();

            foreach ($literals as $literal) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }
                $seen[abs($literal)] = true;
            }
        }

        if ($disableRules) {
            foreach ($this->problems[count($this->problems) - 1] as $reason) {
                $this->disableProblem($reason['rule']);
            }

            $this->resetSolver();

            return 1;
        }

        return 0;
    }

    /**
     * @param Rule $why
     */
    private function disableProblem(Rule $why)
    {
        $job = $why->getJob();

        if (!$job) {
            $why->disable();

            return;
        }

        // disable all rules of this job
        foreach ($this->rules as $rule) {
            /** @var Rule $rule */
            if ($job === $rule->getJob()) {
                $rule->disable();
            }
        }
    }

    private function resetSolver()
    {
        $this->decisions->reset();

        $this->propagateIndex = 0;
        $this->branches = array();

        $this->enableDisableLearnedRules();
        $this->makeAssertionRuleDecisions();
    }

    /**
     * enable/disable learnt rules
     *
     * we have enabled or disabled some of our rules. We now re-enable all
     * of our learnt rules except the ones that were learnt from rules that
     * are now disabled.
     */
    private function enableDisableLearnedRules()
    {
        foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
            $why = $this->learnedWhy[spl_object_hash($rule)];
            $problemRules = $this->learnedPool[$why];

            $foundDisabled = false;
            foreach ($problemRules as $problemRule) {
                if ($problemRule->isDisabled()) {
                    $foundDisabled = true;
                    break;
                }
            }

            if ($foundDisabled && $rule->isEnabled()) {
                $rule->disable();
            } elseif (!$foundDisabled && $rule->isDisabled()) {
                $rule->enable();
            }
        }
    }

    /**
     * @param bool $disableRules
     */
    private function runSat($disableRules = true)
    {
        $this->propagateIndex = 0;

        /*
         * here's the main loop:
         * 1) propagate new decisions (only needed once)
         * 2) fulfill jobs
         * 3) fulfill all unresolved rules
         * 4) minimalize solution if we had choices
         * if we encounter a problem, we rewind to a safe level and restart
         * with step 1
         */

        $decisionQueue = array();
        /**
         * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
         */
        $disableRules = array();

        $level = 1;
        $systemLevel = $level + 1;

        while (true) {
            if (1 === $level) {
                $conflictRule = $this->propagate($level);
                if (null !== $conflictRule) {
                    if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
                        continue;
                    }

                    return;
                }
            }

            // handle job rules
            if ($level < $systemLevel) {
                $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
                foreach ($iterator as $rule) {
                    if ($rule->isEnabled()) {
                        $decisionQueue = array();
                        $noneSatisfied = true;

                        foreach ($rule->getLiterals() as $literal) {
                            if ($this->decisions->satisfy($literal)) {
                                $noneSatisfied = false;
                                break;
                            }
                            if ($literal > 0 && $this->decisions->undecided($literal)) {
                                $decisionQueue[] = $literal;
                            }
                        }

                        if ($noneSatisfied && count($decisionQueue)) {
                            // prune all update packages until installed version
                            // except for requested updates
                            if (count($this->installed) != count($this->updateMap)) {
                                $prunedQueue = array();
                                foreach ($decisionQueue as $literal) {
                                    if (isset($this->installedMap[abs($literal)])) {
                                        $prunedQueue[] = $literal;
                                        if (isset($this->updateMap[abs($literal)])) {
                                            $prunedQueue = $decisionQueue;
                                            break;
                                        }
                                    }
                                }
                                $decisionQueue = $prunedQueue;
                            }
                        }

                        if ($noneSatisfied && count($decisionQueue)) {
                            $oLevel = $level;
                            $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);

                            if (0 === $level) {
                                return;
                            }
                            if ($level <= $oLevel) {
                                break;
                            }
                        }
                    }
                }

                $systemLevel = $level + 1;

                // jobs left
                $iterator->next();
                if ($iterator->valid()) {
                    continue;
                }
            }

            if ($level < $systemLevel) {
                $systemLevel = $level;
            }

            $rulesCount = count($this->rules);
            $pass = 1;

            $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
            for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
                if ($i == $rulesCount) {
                    if (1 === $pass) {
                        $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
                    } else {
                        $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
                    }

                    $i = 0;
                    $pass++;
                }

                $rule = $this->rules->ruleById[$i];
                $literals = $rule->getLiterals();

                if ($rule->isDisabled()) {
                    continue;
                }

                $decisionQueue = array();

                // make sure that
                // * all negative literals are installed
                // * no positive literal is installed
                // i.e. the rule is not fulfilled and we
                // just need to decide on the positive literals
                //
                foreach ($literals as $literal) {
                    if ($literal <= 0) {
                        if (!$this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                    } else {
                        if ($this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                        if ($this->decisions->undecided($literal)) {
                            $decisionQueue[] = $literal;
                        }
                    }
                }

                // need to have at least 2 item to pick from
                if (count($decisionQueue) < 2) {
                    continue;
                }

                $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);

                if (0 === $level) {
                    return;
                }

                // something changed, so look at all rules again
                $rulesCount = count($this->rules);
                $n = -1;
            }

            if ($level < $systemLevel) {
                continue;
            }

            // minimization step
            if (count($this->branches)) {
                $lastLiteral = null;
                $lastLevel = null;
                $lastBranchIndex = 0;
                $lastBranchOffset = 0;

                for ($i = count($this->branches) - 1; $i >= 0; $i--) {
                    list($literals, $l) = $this->branches[$i];

                    foreach ($literals as $offset => $literal) {
                        if ($literal && $literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) {
                            $lastLiteral = $literal;
                            $lastBranchIndex = $i;
                            $lastBranchOffset = $offset;
                            $lastLevel = $l;
                        }
                    }
                }

                if ($lastLiteral) {
                    unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]);

                    $level = $lastLevel;
                    $this->revert($level);

                    $why = $this->decisions->lastReason();

                    $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);

                    if ($level == 0) {
                        return;
                    }

                    continue;
                }
            }

            break;
        }
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\Link;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class GenericRule extends Rule
{
    protected $literals;

    /**
     * @param array                 $literals
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct(array $literals, $reason, $reasonData, $job = null)
    {
        parent::__construct($reason, $reasonData, $job);

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    public function getLiterals()
    {
        return $this->literals;
    }

    public function getHash()
    {
        $data = unpack('ihash', md5(implode(',', $this->literals), true));

        return $data['hash'];
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule)
    {
        return $this->literals === $rule->getLiterals();
    }

    public function isAssertion()
    {
        return 1 === count($this->literals);
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     *
     * @return string
     */
    public function __toString()
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        foreach ($this->literals as $i => $literal) {
            if ($i != 0) {
                $result .= '|';
            }
            $result .= $literal;
        }

        $result .= ')';

        return $result;
    }
}
<?php

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

namespace Composer\IO;

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

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

        $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter);

        parent::__construct($input, $output, new HelperSet(array(
            new QuestionHelper(),
        )));
    }

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

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

        $output = preg_replace_callback("{(?<=^|\n|\x08)(.+?)(\x08+)}", function ($matches) {
            $pre = strip_tags($matches[1]);

            if (strlen($pre) === strlen($matches[2])) {
                return '';
            }

            // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs
            return rtrim($matches[1])."\n";
        }, $output);

        return $output;
    }

    public function setUserInputs(array $inputs)
    {
        if (!$this->input instanceof StreamableInputInterface) {
            throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.');
        }

        $this->input->setStream($this->createStream($inputs));
        $this->input->setInteractive(true);
    }

    private function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        foreach ($inputs as $input) {
            fwrite($stream, $input.PHP_EOL);
        }

        rewind($stream);

        return $stream;
    }
}
<?php

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

namespace Composer\IO;

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

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

    /** @var float */
    private $startTime;
    /** @var array<int, int> */
    private $verbosityMap;

    /**
     * Constructor.
     *
     * @param InputInterface  $input     The input instance
     * @param OutputInterface $output    The output instance
     * @param HelperSet       $helperSet The helperSet instance
     */
    public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet)
    {
        $this->input = $input;
        $this->output = $output;
        $this->helperSet = $helperSet;
        $this->verbosityMap = array(
            self::QUIET => OutputInterface::VERBOSITY_QUIET,
            self::NORMAL => OutputInterface::VERBOSITY_NORMAL,
            self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
            self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
            self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
        );
    }

    /**
     * @param float $startTime
     */
    public function enableDebugging($startTime)
    {
        $this->startTime = $startTime;
    }

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

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

    /**
     * {@inheritDoc}
     */
    public function isVerbose()
    {
        return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE;
    }

    /**
     * {@inheritDoc}
     */
    public function isVeryVerbose()
    {
        return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE;
    }

    /**
     * {@inheritDoc}
     */
    public function isDebug()
    {
        return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;
    }

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

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

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

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

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

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

        if ($raw) {
            if ($sfVerbosity === OutputInterface::OUTPUT_NORMAL) {
                $sfVerbosity = OutputInterface::OUTPUT_RAW;
            } else {
                $sfVerbosity |= OutputInterface::OUTPUT_RAW;
            }
        }

        if (null !== $this->startTime) {
            $memoryUsage = memory_get_usage() / 1024 / 1024;
            $timeSpent = microtime(true) - $this->startTime;
            $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) {
                return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
            }, (array) $messages);
        }

        if (true === $stderr && $this->output instanceof ConsoleOutputInterface) {
            $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity);
            $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages);

            return;
        }

        $this->output->write($messages, $newline, $sfVerbosity);
        $this->lastMessage = implode($newline ? "\n" : '', (array) $messages);
    }

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

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

    /**
     * @param array|string $messages
     * @param bool         $newline
     * @param int|null     $size
     * @param bool         $stderr
     * @param int          $verbosity
     */
    private function doOverwrite($messages, $newline, $size, $stderr, $verbosity)
    {
        // messages can be an array, let's convert it to string anyway
        $messages = implode($newline ? "\n" : '', (array) $messages);

        // since overwrite is supposed to overwrite last message...
        if (!isset($size)) {
            // removing possible formatting of lastMessage with strip_tags
            $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage));
        }
        // ...let's fill its length with backspaces
        $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity);

        // write the new message
        $this->doWrite($messages, false, $stderr, $verbosity);

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

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

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

    /**
     * {@inheritDoc}
     */
    public function ask($question, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question, $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * {@inheritDoc}
     */
    public function askConfirmation($question, $default = true)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new StrictConfirmationQuestion($question, $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * {@inheritDoc}
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question, $default);
        $question->setValidator($validator);
        $question->setMaxAttempts($attempts);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * {@inheritDoc}
     */
    public function askAndHideAnswer($question)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question);
        $question->setHidden(true);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * {@inheritDoc}
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new ChoiceQuestion($question, $choices, $default);
        $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int
        $question->setErrorMessage($errorMessage);
        $question->setMultiselect($multiselect);

        $result = $helper->ask($this->input, $this->getErrorOutput(), $question);

        if (!is_array($result)) {
            return (string) array_search($result, $choices, true);
        }

        $results = array();
        foreach ($choices as $index => $choice) {
            if (in_array($choice, $result, true)) {
                $results[] = (string) $index;
            }
        }

        return $results;
    }

    /**
     * @return OutputInterface
     */
    private function getErrorOutput()
    {
        if ($this->output instanceof ConsoleOutputInterface) {
            return $this->output->getErrorOutput();
        }

        return $this->output;
    }
}
<?php

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

namespace Composer\IO;

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

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

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

    /**
     * {@inheritDoc}
     */
    public function resetAuthentications()
    {
        $this->authentications = array();
    }

    /**
     * {@inheritDoc}
     */
    public function hasAuthentication($repositoryName)
    {
        return isset($this->authentications[$repositoryName]);
    }

    /**
     * {@inheritDoc}
     */
    public function getAuthentication($repositoryName)
    {
        if (isset($this->authentications[$repositoryName])) {
            return $this->authentications[$repositoryName];
        }

        return array('username' => null, 'password' => null);
    }

    /**
     * {@inheritDoc}
     */
    public function setAuthentication($repositoryName, $username, $password = null)
    {
        $this->authentications[$repositoryName] = array('username' => $username, 'password' => $password);
    }

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

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

    /**
     * Check for overwrite and set the authentication information for the repository.
     *
     * @param string $repositoryName The unique name of repository
     * @param string $username       The username
     * @param string $password       The password
     */
    protected function checkAndSetAuthentication($repositoryName, $username, $password = null)
    {
        if ($this->hasAuthentication($repositoryName)) {
            $auth = $this->getAuthentication($repositoryName);
            if ($auth['username'] === $username && $auth['password'] === $password) {
                return;
            }

            $this->writeError(
                sprintf(
                    "<warning>Warning: You should avoid overwriting already defined auth settings for %s.</warning>",
                    $repositoryName
                )
            );
        }
        $this->setAuthentication($repositoryName, $username, $password);
    }

    /**
     * {@inheritDoc}
     */
    public function loadConfiguration(Config $config)
    {
        $bitbucketOauth = $config->get('bitbucket-oauth') ?: array();
        $githubOauth = $config->get('github-oauth') ?: array();
        $gitlabOauth = $config->get('gitlab-oauth') ?: array();
        $gitlabToken = $config->get('gitlab-token') ?: array();
        $httpBasic = $config->get('http-basic') ?: array();
        $bearerToken = $config->get('bearer') ?: array();

        // reload oauth tokens from config if available

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

        foreach ($githubOauth as $domain => $token) {
            // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/
            // plus dots which were at some point used for GH app integration tokens
            if (!preg_match('{^[.A-Za-z0-9_]+$}', $token)) {
                throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
            }
            $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic');
        }

        foreach ($gitlabOauth as $domain => $token) {
            $this->checkAndSetAuthentication($domain, $token, 'oauth2');
        }

        foreach ($gitlabToken as $domain => $token) {
            $this->checkAndSetAuthentication($domain, $token, 'private-token');
        }

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

        foreach ($bearerToken as $domain => $token) {
            $this->checkAndSetAuthentication($domain, $token, 'bearer');
        }

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

    /**
     * System is unusable.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function emergency($message, array $context = array())
    {
        return $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function alert($message, array $context = array())
    {
        return $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function critical($message, array $context = array())
    {
        return $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function error($message, array $context = array())
    {
        return $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function warning($message, array $context = array())
    {
        return $this->log(LogLevel::WARNING, $message, $context);
    }

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

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

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

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

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

namespace Composer\IO;

/**
 * IOInterface that is not interactive and never writes the output
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class NullIO extends BaseIO
{
    /**
     * {@inheritDoc}
     */
    public function isInteractive()
    {
        return false;
    }

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

    /**
     * {@inheritDoc}
     */
    public function isVeryVerbose()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function isDebug()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function isDecorated()
    {
        return false;
    }

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

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

    /**
     * {@inheritDoc}
     */
    public function overwrite($messages, $newline = true, $size = 80, $verbosity = self::NORMAL)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function overwriteError($messages, $newline = true, $size = 80, $verbosity = self::NORMAL)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function ask($question, $default = null)
    {
        return $default;
    }

    /**
     * {@inheritDoc}
     */
    public function askConfirmation($question, $default = true)
    {
        return $default;
    }

    /**
     * {@inheritDoc}
     */
    public function askAndValidate($question, $validator, $attempts = false, $default = null)
    {
        return $default;
    }

    /**
     * {@inheritDoc}
     */
    public function askAndHideAnswer($question)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        return $default;
    }
}
<?php

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

namespace Composer\IO;

use Composer\Config;

/**
 * The Input/Output helper interface.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
interface IOInterface
{
    const QUIET = 1;
    const NORMAL = 2;
    const VERBOSE = 4;
    const VERY_VERBOSE = 8;
    const DEBUG = 16;

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Is this output verbose?
     *
     * @return bool
     */
    public function isVerbose();

    /**
     * Is the output very verbose?
     *
     * @return bool
     */
    public function isVeryVerbose();

    /**
     * Is the output in debug verbosity?
     *
     * @return bool
     */
    public function isDebug();

    /**
     * Is this output decorated?
     *
     * @return bool
     */
    public function isDecorated();

    /**
     * Writes a message to the output.
     *
     * @param string|array $messages  The message as an array of lines or a single string
     * @param bool         $newline   Whether to add a newline or not
     * @param int          $verbosity Verbosity level from the VERBOSITY_* constants
     */
    public function write($messages, $newline = true, $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output.
     *
     * @param string|array $messages  The message as an array of lines or a single string
     * @param bool         $newline   Whether to add a newline or not
     * @param int          $verbosity Verbosity level from the VERBOSITY_* constants
     */
    public function writeError($messages, $newline = true, $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the output.
     *
     * @param string|array $messages  The message as an array of lines or a single string
     * @param bool         $newline   Whether to add a newline or not
     * @param int          $size      The size of line
     * @param int          $verbosity Verbosity level from the VERBOSITY_* constants
     */
    public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the error output.
     *
     * @param string|array $messages  The message as an array of lines or a single string
     * @param bool         $newline   Whether to add a newline or not
     * @param int          $size      The size of line
     * @param int          $verbosity Verbosity level from the VERBOSITY_* constants
     */
    public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL);

    /**
     * Asks a question to the user.
     *
     * @param string $question The question to ask
     * @param string $default  The default answer if none is given by the user
     *
     * @throws \RuntimeException If there is no data to read in the input stream
     * @return string            The user answer
     */
    public function ask($question, $default = null);

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param string $question The question to ask
     * @param bool   $default  The default answer if the user enters nothing
     *
     * @return bool true if the user has confirmed, false otherwise
     */
    public function askConfirmation($question, $default = true);

    /**
     * Asks for a value and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param string   $question  The question to ask
     * @param callable $validator A PHP callback
     * @param null|int $attempts  Max number of times to ask before giving up (default of null means infinite)
     * @param mixed    $default   The default answer if none is given by the user
     *
     * @throws \Exception When any of the validators return an error
     * @return mixed
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null);

    /**
     * Asks a question to the user and hide the answer.
     *
     * @param string $question The question to ask
     *
     * @return string The answer
     */
    public function askAndHideAnswer($question);

    /**
     * Asks the user to select a value.
     *
     * @param string      $question     The question to ask
     * @param array       $choices      List of choices to pick from
     * @param bool|string $default      The default answer if the user enters nothing
     * @param bool|int    $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string      $errorMessage Message which will be shown if invalid value from choice list would be picked
     * @param bool        $multiselect  Select more than one value separated by comma
     *
     * @throws \InvalidArgumentException
     * @return int|string|array          The selected value or values (the key of the choices array)
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);

    /**
     * Get all authentication information entered.
     *
     * @return array The map of authentication data
     */
    public function getAuthentications();

    /**
     * Verify if the repository has a authentication information.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return bool
     */
    public function hasAuthentication($repositoryName);

    /**
     * Get the username and password of repository.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return array The 'username' and 'password'
     */
    public function getAuthentication($repositoryName);

    /**
     * Set the authentication information for the repository.
     *
     * @param string $repositoryName The unique name of repository
     * @param string $username       The username
     * @param string $password       The password
     */
    public function setAuthentication($repositoryName, $username, $password = null);

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

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

namespace Composer\Command;

use Composer\Json\JsonFile;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Benoît Merlet <benoit.merlet@gmail.com>
 */
class LicensesCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('licenses')
            ->setDescription('Shows information about licenses of dependencies.')
            ->setDefinition(array(
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
            ))
            ->setHelp(
                <<<EOT
The license command displays detailed information about the licenses of
the installed dependencies.

Read more at https://getcomposer.org/doc/03-cli.md#licenses
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $root = $composer->getPackage();
        $repo = $composer->getRepositoryManager()->getLocalRepository();

        if ($input->getOption('no-dev')) {
            $packages = $this->filterRequiredPackages($repo, $root);
        } else {
            $packages = $this->appendPackages($repo->getPackages(), array());
        }

        ksort($packages);
        $io = $this->getIO();

        switch ($format = $input->getOption('format')) {
            case 'text':
                $io->write('Name: <comment>'.$root->getPrettyName().'</comment>');
                $io->write('Version: <comment>'.$root->getFullPrettyVersion().'</comment>');
                $io->write('Licenses: <comment>'.(implode(', ', $root->getLicense()) ?: 'none').'</comment>');
                $io->write('Dependencies:');
                $io->write('');

                $table = new Table($output);
                $table->setStyle('compact');
                $tableStyle = $table->getStyle();
                if (method_exists($tableStyle, 'setVerticalBorderChars')) {
                    $tableStyle->setVerticalBorderChars('');
                } else {
                    $tableStyle->setVerticalBorderChar('');
                }
                $tableStyle->setCellRowContentFormat('%s  ');
                $table->setHeaders(array('Name', 'Version', 'License'));
                foreach ($packages as $package) {
                    $table->addRow(array(
                        $package->getPrettyName(),
                        $package->getFullPrettyVersion(),
                        implode(', ', $package->getLicense()) ?: 'none',
                    ));
                }
                $table->render();
                break;

            case 'json':
                $dependencies = array();
                foreach ($packages as $package) {
                    $dependencies[$package->getPrettyName()] = array(
                        'version' => $package->getFullPrettyVersion(),
                        'license' => $package->getLicense(),
                    );
                }

                $io->write(JsonFile::encode(array(
                    'name' => $root->getPrettyName(),
                    'version' => $root->getFullPrettyVersion(),
                    'license' => $root->getLicense(),
                    'dependencies' => $dependencies,
                )));
                break;

            default:
                throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
        }

        return 0;
    }

    /**
     * Find package requires and child requires
     *
     * @param  RepositoryInterface $repo
     * @param  PackageInterface    $package
     * @param  array               $bucket
     * @return array
     */
    private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $package, $bucket = array())
    {
        $requires = array_keys($package->getRequires());

        $packageListNames = array_keys($bucket);
        $packages = array_filter(
            $repo->getPackages(),
            function ($package) use ($requires, $packageListNames) {
                return in_array($package->getName(), $requires) && !in_array($package->getName(), $packageListNames);
            }
        );

        $bucket = $this->appendPackages($packages, $bucket);

        foreach ($packages as $package) {
            $bucket = $this->filterRequiredPackages($repo, $package, $bucket);
        }

        return $bucket;
    }

    /**
     * Adds packages to the package list
     *
     * @param  array $packages the list of packages to add
     * @param  array $bucket   the list to add packages to
     * @return array
     */
    public function appendPackages(array $packages, array $bucket)
    {
        foreach ($packages as $package) {
            $bucket[$package->getName()] = $package;
        }

        return $bucket;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator;
use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\Util\StreamContextFactory;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\NullIO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DiagnoseCommand extends BaseCommand
{
    /** @var RemoteFilesystem */
    protected $rfs;

    /** @var ProcessExecutor */
    protected $process;

    /** @var int */
    protected $exitCode = 0;

    protected function configure()
    {
        $this
            ->setName('diagnose')
            ->setDescription('Diagnoses the system to identify common errors.')
            ->setHelp(
                <<<EOT
The <info>diagnose</info> command checks common errors to help debugging problems.

The process exit code will be 1 in case of warnings and 2 for errors.

Read more at https://getcomposer.org/doc/03-cli.md#diagnose
EOT
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer(false);
        $io = $this->getIO();

        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

            $io->write('Checking composer.json: ', false);
            $this->outputResult($this->checkComposerSchema());
        }

        if ($composer) {
            $config = $composer->getConfig();
        } else {
            $config = Factory::createConfig();
        }

        $config->merge(array('config' => array('secure-http' => false)));
        $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);

        $this->rfs = Factory::createRemoteFilesystem($io, $config);
        $this->process = new ProcessExecutor($io);

        $io->write('Checking platform settings: ', false);
        $this->outputResult($this->checkPlatform());

        $io->write('Checking git settings: ', false);
        $this->outputResult($this->checkGit());

        $io->write('Checking http connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('http', $config));

        $io->write('Checking https connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('https', $config));

        $opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org'));
        if (!empty($opts['http']['proxy'])) {
            $io->write('Checking HTTP proxy: ', false);
            $this->outputResult($this->checkHttpProxy());
            $io->write('Checking HTTP proxy support for request_fulluri: ', false);
            $this->outputResult($this->checkHttpProxyFullUriRequestParam());
            $io->write('Checking HTTPS proxy support for request_fulluri: ', false);
            $this->outputResult($this->checkHttpsProxyFullUriRequestParam());
        }

        if ($oauth = $config->get('github-oauth')) {
            foreach ($oauth as $domain => $token) {
                $io->write('Checking '.$domain.' oauth access: ', false);
                $this->outputResult($this->checkGithubOauth($domain, $token));
            }
        } else {
            $io->write('Checking github.com rate limit: ', false);
            try {
                $rate = $this->getGithubRateLimit('github.com');
                if (!is_array($rate)) {
                    $this->outputResult($rate);
                } elseif (10 > $rate['remaining']) {
                    $io->write('<warning>WARNING</warning>');
                    $io->write(sprintf(
                        '<comment>Github has a rate limit on their API. '
                        . 'You currently have <options=bold>%u</options=bold> '
                        . 'out of <options=bold>%u</options=bold> requests left.' . PHP_EOL
                        . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL
                        . '    https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens</comment>',
                        $rate['remaining'],
                        $rate['limit']
                    ));
                } else {
                    $this->outputResult(true);
                }
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getCode() === 401) {
                    $this->outputResult('<comment>The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it</comment>');
                } else {
                    $this->outputResult($e);
                }
            }
        }

        $io->write('Checking disk free space: ', false);
        $this->outputResult($this->checkDiskSpace($config));

        if ('phar:' === substr(__FILE__, 0, 5)) {
            $io->write('Checking pubkeys: ', false);
            $this->outputResult($this->checkPubKeys($config));

            $io->write('Checking composer version: ', false);
            $this->outputResult($this->checkVersion($config));
        }

        $io->write(sprintf('Composer version: <comment>%s</comment>', Composer::VERSION));

        $platformOverrides = $config->get('platform') ?: array();
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $phpPkg = $platformRepo->findPackage('php', '*');
        $phpVersion = $phpPkg->getPrettyVersion();
        if (false !== strpos($phpPkg->getDescription(), 'overridden')) {
            $phpVersion .= ' - ' . $phpPkg->getDescription();
        }

        $io->write(sprintf('PHP version: <comment>%s</comment>', $phpVersion));

        if (defined('PHP_BINARY')) {
            $io->write(sprintf('PHP binary path: <comment>%s</comment>', PHP_BINARY));
        }

        $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? '<comment>'.OPENSSL_VERSION_TEXT.'</comment>' : '<error>missing</error>'));

        return $this->exitCode;
    }

    private function checkComposerSchema()
    {
        $validator = new ConfigValidator($this->getIO());
        list($errors, , $warnings) = $validator->validate(Factory::getComposerFile());

        if ($errors || $warnings) {
            $messages = array(
                'error' => $errors,
                'warning' => $warnings,
            );

            $output = '';
            foreach ($messages as $style => $msgs) {
                foreach ($msgs as $msg) {
                    $output .= '<' . $style . '>' . $msg . '</' . $style . '>' . PHP_EOL;
                }
            }

            return rtrim($output);
        }

        return true;
    }

    private function checkGit()
    {
        $this->process->execute('git config color.ui', $output);
        if (strtolower(trim($output)) === 'always') {
            return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
        }

        return true;
    }

    private function checkHttp($proto, Config $config)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $disableTls = false;
        $result = array();
        if ($proto === 'https' && $config->get('disable-tls') === true) {
            $disableTls = true;
            $result[] = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }
        if ($proto === 'https' && !extension_loaded('openssl') && !$disableTls) {
            $result[] = '<error>Composer is configured to use SSL/TLS protection but the openssl extension is not available.</error>';
        }

        try {
            $this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false);
        } catch (TransportException $e) {
            if (false !== strpos($e->getMessage(), 'cafile')) {
                $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
                $result[] = '<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>';
                $result[] = '<error>You can alternatively disable this error, at your own risk, by enabling the \'disable-tls\' option.</error>';
            } else {
                array_unshift($result, '[' . get_class($e) . '] ' . $e->getMessage());
            }
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    private function checkHttpProxy()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $protocol = extension_loaded('openssl') ? 'https' : 'http';
        try {
            $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true);
            $hash = reset($json['provider-includes']);
            $hash = $hash['sha256'];
            $path = str_replace('%hash%', $hash, key($json['provider-includes']));
            $provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false);

            if (hash('sha256', $provider) !== $hash) {
                return 'It seems that your proxy is modifying http traffic on the fly';
            }
        } catch (\Exception $e) {
            return $e;
        }

        return true;
    }

    /**
     * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter,
     * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825.
     * This method will test, if you need to disable this parameter via setting extra environment variable in your system.
     *
     * @return bool|string
     */
    private function checkHttpProxyFullUriRequestParam()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $url = 'http://repo.packagist.org/packages.json';
        try {
            $this->rfs->getContents('packagist.org', $url, false);
        } catch (TransportException $e) {
            try {
                $this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
            } catch (TransportException $e) {
                return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
            }

            return 'It seems there is a problem with your proxy server, try setting the "HTTP_PROXY_REQUEST_FULLURI" and "HTTPS_PROXY_REQUEST_FULLURI" environment variables to "false"';
        }

        return true;
    }

    /**
     * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter,
     * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825.
     * This method will test, if you need to disable this parameter via setting extra environment variable in your system.
     *
     * @return bool|string
     */
    private function checkHttpsProxyFullUriRequestParam()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        if (!extension_loaded('openssl')) {
            return 'You need the openssl extension installed for this check';
        }

        $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
        try {
            $this->rfs->getContents('github.com', $url, false);
        } catch (TransportException $e) {
            try {
                $this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false)));
            } catch (TransportException $e) {
                return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
            }

            return 'It seems there is a problem with your proxy server, try setting the "HTTPS_PROXY_REQUEST_FULLURI" environment variable to "false"';
        }

        return true;
    }

    private function checkGithubOauth($domain, $token)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        try {
            $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';

            return $this->rfs->getContents($domain, $url, false, array(
                'retry-auth-failure' => false,
            )) ? true : 'Unexpected error';
        } catch (\Exception $e) {
            if ($e instanceof TransportException && $e->getCode() === 401) {
                return '<comment>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</comment>';
            }

            return $e;
        }
    }

    /**
     * @param  string             $domain
     * @param  string             $token
     * @throws TransportException
     * @return array|string
     */
    private function getGithubRateLimit($domain, $token = null)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        if ($token) {
            $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        }

        $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
        $json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false));
        $data = json_decode($json, true);

        return $data['resources']['core'];
    }

    private function checkDiskSpace($config)
    {
        $minSpaceFree = 1024 * 1024;
        if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
            || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
        ) {
            return '<error>The disk hosting '.$dir.' is full</error>';
        }

        return true;
    }

    private function checkPubKeys($config)
    {
        $home = $config->get('home');
        $errors = array();
        $io = $this->getIO();

        if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) {
            $io->write('');
        }

        if (file_exists($home.'/keys.tags.pub')) {
            $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for tags verification</error>';
        }

        if (file_exists($home.'/keys.dev.pub')) {
            $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for dev verification</error>';
        }

        if ($errors) {
            $errors[] = '<error>Run composer self-update --update-keys to set them up</error>';
        }

        return $errors ?: true;
    }

    private function checkVersion($config)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $versionsUtil = new Versions($config, $this->rfs);
        try {
            $latest = $versionsUtil->getLatest();
        } catch (\Exception $e) {
            return $e;
        }

        if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
            return '<comment>You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')</comment>';
        }

        return true;
    }

    /**
     * @param bool|string|\Exception $result
     */
    private function outputResult($result)
    {
        $io = $this->getIO();
        if (true === $result) {
            $io->write('<info>OK</info>');

            return;
        }

        $hadError = false;
        $hadWarning = false;
        if ($result instanceof \Exception) {
            $result = '<error>['.get_class($result).'] '.$result->getMessage().'</error>';
        }

        if (!$result) {
            // falsey results should be considered as an error, even if there is nothing to output
            $hadError = true;
        } else {
            if (!is_array($result)) {
                $result = array($result);
            }
            foreach ($result as $message) {
                if (false !== strpos($message, '<error>')) {
                    $hadError = true;
                } elseif (false !== strpos($message, '<warning>')) {
                    $hadWarning = true;
                }
            }
        }

        if ($hadError) {
            $io->write('<error>FAIL</error>');
            $this->exitCode = max($this->exitCode, 2);
        } elseif ($hadWarning) {
            $io->write('<warning>WARNING</warning>');
            $this->exitCode = max($this->exitCode, 1);
        }

        if ($result) {
            foreach ($result as $message) {
                $io->write($message);
            }
        }
    }

    private function checkPlatform()
    {
        $output = '';
        $out = function ($msg, $style) use (&$output) {
            $output .= '<'.$style.'>'.$msg.'</'.$style.'>'.PHP_EOL;
        };

        // code below taken from getcomposer.org/installer, any changes should be made there and replicated here
        $errors = array();
        $warnings = array();
        $displayIniMessage = false;

        $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
        $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';

        if (!function_exists('json_decode')) {
            $errors['json'] = true;
        }

        if (!extension_loaded('Phar')) {
            $errors['phar'] = true;
        }

        if (!extension_loaded('filter')) {
            $errors['filter'] = true;
        }

        if (!extension_loaded('hash')) {
            $errors['hash'] = true;
        }

        if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
            $errors['iconv_mbstring'] = true;
        }

        if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errors['allow_url_fopen'] = true;
        }

        if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
            $errors['ioncube'] = ioncube_loader_version();
        }

        if (PHP_VERSION_ID < 50302) {
            $errors['php'] = PHP_VERSION;
        }

        if (!isset($errors['php']) && PHP_VERSION_ID < 50304) {
            $warnings['php'] = PHP_VERSION;
        }

        if (!extension_loaded('openssl')) {
            $errors['openssl'] = true;
        }

        if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
            $warnings['openssl_version'] = true;
        }

        if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['apc_cli'] = true;
        }

        if (!extension_loaded('zlib')) {
            $warnings['zlib'] = true;
        }

        ob_start();
        phpinfo(INFO_GENERAL);
        $phpinfo = ob_get_clean();
        if (preg_match('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
            $configure = $match[1];

            if (false !== strpos($configure, '--enable-sigchild')) {
                $warnings['sigchild'] = true;
            }

            if (false !== strpos($configure, '--with-curlwrappers')) {
                $warnings['curlwrappers'] = true;
            }
        }

        if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['xdebug_profile'] = true;
        } elseif (extension_loaded('xdebug')) {
            $warnings['xdebug_loaded'] = true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')
            && (version_compare(PHP_VERSION, '7.2.23', '<')
            || (version_compare(PHP_VERSION, '7.3.0', '>=')
            && version_compare(PHP_VERSION, '7.3.10', '<')))) {
            $warnings['onedrive'] = PHP_VERSION;
        }

        if (!empty($errors)) {
            foreach ($errors as $error => $current) {
                switch ($error) {
                    case 'json':
                        $text = PHP_EOL."The json extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-json";
                        break;

                    case 'phar':
                        $text = PHP_EOL."The phar extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-phar";
                        break;

                    case 'filter':
                        $text = PHP_EOL."The filter extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-filter";
                        break;

                    case 'hash':
                        $text = PHP_EOL."The hash extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-hash";
                        break;

                    case 'iconv_mbstring':
                        $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL;
                        $text .= "Install either of them or recompile php without --disable-iconv";
                        break;

                    case 'unicode':
                        $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "    detect_unicode = Off";
                        $displayIniMessage = true;
                        break;

                    case 'suhosin':
                        $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
                        $text .= "    suhosin.executor.include.whitelist = phar ".$current;
                        $displayIniMessage = true;
                        break;

                    case 'php':
                        $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
                        break;

                    case 'allow_url_fopen':
                        $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "    allow_url_fopen = On";
                        $displayIniMessage = true;
                        break;

                    case 'ioncube':
                        $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL;
                        $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL;
                        $text .= "    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so";
                        $displayIniMessage = true;
                        break;

                    case 'openssl':
                        $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL;
                        $text .= "If possible you should enable it or recompile php with --with-openssl";
                        break;
                }
                $out($text, 'error');
            }

            $output .= PHP_EOL;
        }

        if (!empty($warnings)) {
            foreach ($warnings as $warning => $current) {
                switch ($warning) {
                    case 'apc_cli':
                        $text = "The apc.enable_cli setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "  apc.enable_cli = Off";
                        $displayIniMessage = true;
                        break;

                    case 'zlib':
                        $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL;
                        $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL;
                        $displayIniMessage = true;
                        break;

                    case 'sigchild':
                        $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
                        $text .= "Recompile it without this flag if possible, see also:".PHP_EOL;
                        $text .= "  https://bugs.php.net/bug.php?id=22999";
                        break;

                    case 'curlwrappers':
                        $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
                        $text .= " Recompile it without this flag if possible";
                        break;

                    case 'php':
                        $text = "Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL;
                        $text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues.";
                        break;

                    case 'openssl_version':
                        // Attempt to parse version number out, fallback to whole string value.
                        $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true);
                        $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT;

                        $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL;
                        $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above.";
                        break;

                    case 'xdebug_loaded':
                        $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
                        $text .= " Disabling it when using Composer is recommended.";
                        break;

                    case 'xdebug_profile':
                        $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL;
                        $text .= "  xdebug.profiler_enabled = 0";
                        $displayIniMessage = true;
                        break;

                    case 'onedrive':
                        $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL;
                        $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL;
                        break;
                }
                $out($text, 'comment');
            }
        }

        if ($displayIniMessage) {
            $out($iniMessage, 'comment');
        }

        return !$warnings && !$errors ? true : $output;
    }


    /**
     * Check if allow_url_fopen is ON
     *
     * @return bool|string
     */
    private function checkConnectivity()
    {
        if (!ini_get('allow_url_fopen')) {
            $result = '<info>Skipped because allow_url_fopen is missing.</info>';
            return $result;
        }

        return true;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Composer;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Script\ScriptEvents;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Creates an archive of a package for distribution.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArchiveCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('archive')
            ->setDescription('Creates an archive of this composer package.')
            ->setDefinition(array(
                new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'),
                new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar or zip'),
                new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'),
                new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.'
                    .' Note that the format will be appended.'),
                new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'),
            ))
            ->setHelp(
                <<<EOT
The <info>archive</info> command creates an archive of the specified format
containing the files and directories of the Composer project or the specified
package in the specified version and writes it to the specified directory.

<info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>

Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer(false);
        $config = null;

        if ($composer) {
            $config = $composer->getConfig();
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
            $eventDispatcher = $composer->getEventDispatcher();
            $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);
            $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
        }

        if (!$config) {
            $config = Factory::createConfig();
        }

        if (null === $input->getOption('format')) {
            $input->setOption('format', $config->get('archive-format'));
        }
        if (null === $input->getOption('dir')) {
            $input->setOption('dir', $config->get('archive-dir'));
        }

        $returnCode = $this->archive(
            $this->getIO(),
            $config,
            $input->getArgument('package'),
            $input->getArgument('version'),
            $input->getOption('format'),
            $input->getOption('dir'),
            $input->getOption('file'),
            $input->getOption('ignore-filters'),
            $composer
        );

        if (0 === $returnCode && $composer) {
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
        }

        return $returnCode;
    }

    protected function archive(IOInterface $io, Config $config, $packageName = null, $version = null, $format = 'tar', $dest = '.', $fileName = null, $ignoreFilters = false, Composer $composer = null)
    {
        if ($composer) {
            $archiveManager = $composer->getArchiveManager();
        } else {
            $factory = new Factory;
            $downloadManager = $factory->createDownloadManager($io, $config);
            $archiveManager = $factory->createArchiveManager($config, $downloadManager);
        }

        if ($packageName) {
            $package = $this->selectPackage($io, $packageName, $version);

            if (!$package) {
                return 1;
            }
        } else {
            $package = $this->getComposer()->getPackage();
        }

        $io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
        $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters);
        $fs = new Filesystem;
        $shortPath = $fs->findShortestPath(getcwd(), $packagePath, true);

        $io->writeError('Created: ', false);
        $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath);

        return 0;
    }

    protected function selectPackage(IOInterface $io, $packageName, $version = null)
    {
        $io->writeError('<info>Searching for the specified package.</info>');

        if ($composer = $this->getComposer(false)) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories()));
        } else {
            $defaultRepos = RepositoryFactory::defaultRepos($this->getIO());
            $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos)));
            $repo = new CompositeRepository($defaultRepos);
        }

        $packages = $repo->findPackages($packageName, $version);

        if (count($packages) > 1) {
            $package = reset($packages);
            $io->writeError('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
            $io->writeError('Alternatives were '.implode(', ', array_map(function ($p) {
                return $p->getPrettyString();
            }, $packages)).'.');
            $io->writeError('<comment>Please use a more specific constraint to pick a different package.</comment>');
        } elseif ($packages) {
            $package = reset($packages);
            $io->writeError('<info>Found an exact match '.$package->getPrettyString().'.</info>');
        } else {
            $io->writeError('<error>Could not find a package matching '.$packageName.'.</error>');

            return false;
        }

        return $package;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Repository\PlatformRepository;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SuggestsCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('suggests')
            ->setDescription('Shows package suggestions.')
            ->setDefinition(array(
                new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package'),
                new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'),
            ))
            ->setHelp(
                <<<EOT

The <info>%command.name%</info> command shows a sorted list of suggested packages.

Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.

Read more at https://getcomposer.org/doc/03-cli.md#suggests
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $lock = $this->getComposer()->getLocker()->getLockData();

        if (empty($lock)) {
            throw new \RuntimeException('Lockfile seems to be empty?');
        }

        $packages = $lock['packages'];

        if (!$input->getOption('no-dev')) {
            $packages += $lock['packages-dev'];
        }

        $filter = $input->getArgument('packages');

        // First assemble lookup list of packages that are installed, replaced or provided
        $installed = array();
        foreach ($packages as $package) {
            $installed[] = $package['name'];

            if (!empty($package['provide'])) {
                $installed = array_merge($installed, array_keys($package['provide']));
            }

            if (!empty($package['replace'])) {
                $installed = array_merge($installed, array_keys($package['replace']));
            }
        }

        // Undub and sort the install list into a sorted lookup array
        $installed = array_flip($installed);
        ksort($installed);

        // Init platform repo
        $platform = new PlatformRepository(array(), $this->getComposer()->getConfig()->get('platform') ?: array());

        // Next gather all suggestions that are not in that list
        $suggesters = array();
        $suggested = array();
        foreach ($packages as $package) {
            $packageName = $package['name'];
            if ((!empty($filter) && !in_array($packageName, $filter)) || empty($package['suggest'])) {
                continue;
            }
            foreach ($package['suggest'] as $suggestion => $reason) {
                if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
                    continue;
                }
                if (!isset($installed[$suggestion])) {
                    $suggesters[$packageName][$suggestion] = $reason;
                    $suggested[$suggestion][$packageName] = $reason;
                }
            }
        }
        ksort($suggesters);
        ksort($suggested);

        // Determine output mode
        $mode = 0;
        $io = $this->getIO();
        if ($input->getOption('by-package') || $io->isVerbose()) {
            $mode |= 1;
        }
        if ($input->getOption('by-suggestion')) {
            $mode |= 2;
        }

        // Simple mode
        if ($mode === 0) {
            foreach (array_keys($suggested) as $suggestion) {
                $io->write(sprintf('<info>%s</info>', $suggestion));
            }

            return 0;
        }

        // Grouped by package
        if ($mode & 1) {
            foreach ($suggesters as $suggester => $suggestions) {
                $io->write(sprintf('<comment>%s</comment> suggests:', $suggester));

                foreach ($suggestions as $suggestion => $reason) {
                    $io->write(sprintf(' - <info>%s</info>: %s', $suggestion, $reason ?: '*'));
                }
                $io->write('');
            }
        }

        // Grouped by suggestion
        if ($mode & 2) {
            // Improve readability in full mode
            if ($mode & 1) {
                $io->write(str_repeat('-', 78));
            }
            foreach ($suggested as $suggestion => $suggesters) {
                $io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));

                foreach ($suggesters as $suggester => $reason) {
                    $io->write(sprintf(' - <info>%s</info>: %s', $suggester, $reason ?: '*'));
                }
                $io->write('');
            }
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Downloader\ChangeReportInterface;
use Composer\Downloader\DvcsDownloaderInterface;
use Composer\Downloader\VcsCapableDownloaderInterface;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;

/**
 * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
 * @author Rui Marinho <rui.marinho@seegno.com>
 */
class StatusCommand extends BaseCommand
{
    const EXIT_CODE_ERRORS = 1;
    const EXIT_CODE_UNPUSHED_CHANGES = 2;
    const EXIT_CODE_VERSION_CHANGES = 4;

    /**
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     */
    protected function configure()
    {
        $this
            ->setName('status')
            ->setDescription('Shows a list of locally modified packages.')
            ->setDefinition(array(
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'),
            ))
            ->setHelp(
                <<<EOT
The status command displays a list of dependencies that have
been modified locally.

Read more at https://getcomposer.org/doc/03-cli.md#status
EOT
            )
        ;
    }

    /**
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        // Dispatch pre-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);

        $exitCode = $this->doExecute($input, $output);

        // Dispatch post-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);

        return $exitCode;
    }

    /**
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    private function doExecute(InputInterface $input, OutputInterface $output)
    {
        // init repos
        $composer = $this->getComposer();

        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();

        $dm = $composer->getDownloadManager();
        $im = $composer->getInstallationManager();

        $errors = array();
        $io = $this->getIO();
        $unpushedChanges = array();
        $vcsVersionChanges = array();

        $parser = new VersionParser;
        $guesser = new VersionGuesser($composer->getConfig(), new ProcessExecutor($io), $parser);
        $dumper = new ArrayDumper;

        // list packages
        foreach ($installedRepo->getCanonicalPackages() as $package) {
            $downloader = $dm->getDownloaderForInstalledPackage($package);
            $targetDir = $im->getInstallPath($package);

            if ($downloader instanceof ChangeReportInterface) {
                if (is_link($targetDir)) {
                    $errors[$targetDir] = $targetDir . ' is a symbolic link.';
                }

                if ($changes = $downloader->getLocalChanges($package, $targetDir)) {
                    $errors[$targetDir] = $changes;
                }
            }

            if ($downloader instanceof VcsCapableDownloaderInterface) {
                if ($currentRef = $downloader->getVcsReference($package, $targetDir)) {
                    switch ($package->getInstallationSource()) {
                        case 'source':
                            $previousRef = $package->getSourceReference();
                            break;
                        case 'dist':
                            $previousRef = $package->getDistReference();
                            break;
                        default:
                            $previousRef = null;
                    }

                    $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir);

                    if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef) {
                        $vcsVersionChanges[$targetDir] = array(
                            'previous' => array(
                                'version' => $package->getPrettyVersion(),
                                'ref' => $previousRef,
                            ),
                            'current' => array(
                                'version' => $currentVersion['pretty_version'],
                                'ref' => $currentVersion['commit'],
                            ),
                        );
                    }
                }
            }

            if ($downloader instanceof DvcsDownloaderInterface) {
                if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) {
                    $unpushedChanges[$targetDir] = $unpushed;
                }
            }
        }

        // output errors/warnings
        if (!$errors && !$unpushedChanges && !$vcsVersionChanges) {
            $io->writeError('<info>No local changes</info>');

            return 0;
        }

        if ($errors) {
            $io->writeError('<error>You have changes in the following dependencies:</error>');

            foreach ($errors as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(function ($line) {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($unpushedChanges) {
            $io->writeError('<warning>You have unpushed changes on the current branch in the following dependencies:</warning>');

            foreach ($unpushedChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(function ($line) {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($vcsVersionChanges) {
            $io->writeError('<warning>You have version variations in the following dependencies:</warning>');

            foreach ($vcsVersionChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    // If we don't can't find a version, use the ref instead.
                    $currentVersion = $changes['current']['version'] ?: $changes['current']['ref'];
                    $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref'];

                    if ($io->isVeryVerbose()) {
                        // Output the ref regardless of whether or not it's being used as the version
                        $currentVersion .= sprintf(' (%s)', $changes['current']['ref']);
                        $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']);
                    }

                    $io->write('<info>'.$path.'</info>:');
                    $io->write(sprintf('    From <comment>%s</comment> to <comment>%s</comment>', $previousVersion, $currentVersion));
                } else {
                    $io->write($path);
                }
            }
        }

        if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) {
            $io->writeError('Use --verbose (-v) to see a list of files');
        }

        return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0);
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

/**
 * @author Davey Shafik <me@daveyshafik.com>
 */
class ExecCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('exec')
            ->setDescription('Executes a vendored binary/script.')
            ->setDefinition(array(
                new InputOption('list', 'l', InputOption::VALUE_NONE),
                new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit'),
                new InputArgument(
                    'args',
                    InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
                    'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
                ),
            ))
            ->setHelp(
                <<<EOT
Executes a vendored binary/script.

Read more at https://getcomposer.org/doc/03-cli.md#exec
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();
        $binDir = $composer->getConfig()->get('bin-dir');
        if ($input->getOption('list') || !$input->getArgument('binary')) {
            $bins = glob($binDir . '/*');
            $bins = array_merge($bins, array_map(function ($e) {
                return "$e (local)";
            }, $composer->getPackage()->getBinaries()));

            if (!$bins) {
                throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)");
            }

            $this->getIO()->write(
                <<<EOT
<comment>Available binaries:</comment>
EOT
            );

            foreach ($bins as $bin) {
                // skip .bat copies
                if (isset($previousBin) && $bin === $previousBin.'.bat') {
                    continue;
                }

                $previousBin = $bin;
                $bin = basename($bin);
                $this->getIO()->write(
                    <<<EOT
<info>- $bin</info>
EOT
                );
            }

            return 0;
        }

        $binary = $input->getArgument('binary');

        $dispatcher = $composer->getEventDispatcher();
        $dispatcher->addListener('__exec_command', $binary);
        if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
        }

        // If the CWD was modified, we restore it to what it was initially, as it was
        // most likely modified by the global command, and we want exec to run in the local working directory
        // not the global one
        if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory()) {
            try {
                chdir($this->getApplication()->getInitialWorkingDirectory());
            } catch (\Exception $e) {
                throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e);
            }
        }

        return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args'));
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class DependsCommand extends BaseDependencyCommand
{
    /**
     * Configure command metadata.
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('depends')
            ->setAliases(array('why'))
            ->setDescription('Shows which packages cause the given package to be installed.')
            ->setHelp(
                <<<EOT
Displays detailed information about where a package is referenced.

<info>php composer.phar depends composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#depends-why-
EOT
            )
        ;
    }

    /**
     * Execute the function.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return parent::doExecute($input, $output, false);
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptAliasCommand extends BaseCommand
{
    private $script;
    private $description;

    public function __construct($script, $description)
    {
        $this->script = $script;
        $this->description = empty($description) ? 'Runs the '.$script.' script as defined in composer.json.' : $description;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName($this->script)
            ->setDescription($this->description)
            ->setDefinition(array(
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ))
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $args = $input->getArguments();

        return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args']);
    }
}
<?php

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

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\PlatformRepository;

class CheckPlatformReqsCommand extends BaseCommand
{
    protected function configure()
    {
        $this->setName('check-platform-reqs')
            ->setDescription('Check that platform requirements are satisfied.')
            ->setDefinition(array(
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'),
            ))
            ->setHelp(
                <<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages.

Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.

<info>php composer.phar check-platform-reqs</info>

EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $requires = $composer->getPackage()->getRequires();
        if ($input->getOption('no-dev')) {
            $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
        } else {
            $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
            // fallback to lockfile if installed repo is empty
            if (!$dependencies) {
                $dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
            }
            $requires += $composer->getPackage()->getDevRequires();
        }
        foreach ($requires as $require => $link) {
            $requires[$require] = array($link);
        }

        foreach ($dependencies as $package) {
            foreach ($package->getRequires() as $require => $link) {
                $requires[$require][] = $link;
            }
        }

        ksort($requires);

        $platformRepo = new PlatformRepository(array(), array());
        $currentPlatformPackages = $platformRepo->getPackages();
        $currentPlatformPackageMap = array();

        /**
         * @var PackageInterface $currentPlatformPackage
         */
        foreach ($currentPlatformPackages as $currentPlatformPackage) {
            $currentPlatformPackageMap[$currentPlatformPackage->getName()] = $currentPlatformPackage;
        }

        $results = array();

        $exitCode = 0;

        /**
         * @var Link[] $links
         */
        foreach ($requires as $require => $links) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) {
                if (isset($currentPlatformPackageMap[$require])) {
                    $pass = true;
                    $version = $currentPlatformPackageMap[$require]->getVersion();

                    foreach ($links as $link) {
                        if (!$link->getConstraint()->matches(new Constraint('=', $version))) {
                            $results[] = array(
                                $currentPlatformPackageMap[$require]->getPrettyName(),
                                $currentPlatformPackageMap[$require]->getPrettyVersion(),
                                $link,
                                '<error>failed</error>',
                            );
                            $pass = false;

                            $exitCode = max($exitCode, 1);
                        }
                    }

                    if ($pass) {
                        $results[] = array(
                            $currentPlatformPackageMap[$require]->getPrettyName(),
                            $currentPlatformPackageMap[$require]->getPrettyVersion(),
                            null,
                            '<info>success</info>',
                        );
                    }
                } else {
                    $results[] = array(
                        $require,
                        'n/a',
                        $links[0],
                        '<error>missing</error>',
                    );

                    $exitCode = max($exitCode, 2);
                }
            }
        }

        $this->printTable($output, $results);

        return $exitCode;
    }

    protected function printTable(OutputInterface $output, $results)
    {
        $table = array();
        $rows = array();
        foreach ($results as $result) {
            /**
             * @var Link|null $link
             */
            list($platformPackage, $version, $link, $status) = $result;
            $rows[] = array(
                $platformPackage,
                $version,
                $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '',
                $status,
            );
        }
        $table = array_merge($rows, $table);

        // Render table
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        if (method_exists($rendererStyle, 'setVerticalBorderChars')) {
            $rendererStyle->setVerticalBorderChars('');
        } else {
            $rendererStyle->setVerticalBorderChar('');
        }
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();
    }
}
<?php

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

namespace Composer\Command;

use Composer\DependencyResolver\Pool;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base implementation for commands mapping dependency relationships.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class BaseDependencyCommand extends BaseCommand
{
    const ARGUMENT_PACKAGE = 'package';
    const ARGUMENT_CONSTRAINT = 'constraint';
    const OPTION_RECURSIVE = 'recursive';
    const OPTION_TREE = 'tree';

    protected $colors;

    /**
     * Set common options and arguments.
     */
    protected function configure()
    {
        $this->setDefinition(array(
            new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
            new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
            new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
            new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
        ));
    }

    /**
     * Execute the command.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
     * @return int             Exit code of the operation.
     */
    protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
    {
        // Emit command event on startup
        $composer = $this->getComposer();
        $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        // Prepare repositories and set up a pool
        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
        $repository = new CompositeRepository(array(
            new ArrayRepository(array($composer->getPackage())),
            $composer->getRepositoryManager()->getLocalRepository(),
            new PlatformRepository(array(), $platformOverrides),
        ));
        $pool = new Pool();
        $pool->addRepository($repository);

        // Parse package name and constraint
        list($needle, $textConstraint) = array_pad(
            explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
            2,
            $input->getArgument(self::ARGUMENT_CONSTRAINT)
        );

        // Find packages that are or provide the requested package first
        $packages = $pool->whatProvides(strtolower($needle));
        if (empty($packages)) {
            throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
        }

        // If the version we ask for is not installed then we need to locate it in remote repos and add it.
        // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
        if (!$repository->findPackage($needle, $textConstraint)) {
            $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
            if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
                $repository->addRepository(new ArrayRepository(array(clone $match)));
            }
        }

        // Include replaced packages for inverted lookups as they are then the actual starting point to consider
        $needles = array($needle);
        if ($inverted) {
            foreach ($packages as $package) {
                $needles = array_merge($needles, array_map(function (Link $link) {
                    return $link->getTarget();
                }, $package->getReplaces()));
            }
        }

        // Parse constraint if one was supplied
        if ('*' !== $textConstraint) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($textConstraint);
        } else {
            $constraint = null;
        }

        // Parse rendering options
        $renderTree = $input->getOption(self::OPTION_TREE);
        $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);

        // Resolve dependencies
        $results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
        if (empty($results)) {
            $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
            $this->getIO()->writeError(sprintf(
                '<info>There is no installed package depending on "%s"%s</info>',
                $needle,
                $extra
            ));
        } elseif ($renderTree) {
            $this->initStyles($output);
            $root = $packages[0];
            $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
            $this->printTree($results);
        } else {
            $this->printTable($output, $results);
        }

        return 0;
    }

    /**
     * Assembles and prints a bottom-up table of the dependencies.
     *
     * @param OutputInterface $output
     * @param array           $results
     */
    protected function printTable(OutputInterface $output, $results)
    {
        $table = array();
        $doubles = array();
        do {
            $queue = array();
            $rows = array();
            foreach ($results as $result) {
                /**
                 * @var PackageInterface $package
                 * @var Link             $link
                 */
                list($package, $link, $children) = $result;
                $unique = (string) $link;
                if (isset($doubles[$unique])) {
                    continue;
                }
                $doubles[$unique] = true;
                $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
                $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
                if ($children) {
                    $queue = array_merge($queue, $children);
                }
            }
            $results = $queue;
            $table = array_merge($rows, $table);
        } while (!empty($results));

        // Render table
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        if (method_exists($rendererStyle, 'setVerticalBorderChars')) {
            $rendererStyle->setVerticalBorderChars('');
        } else {
            $rendererStyle->setVerticalBorderChar('');
        }
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();
    }

    /**
     * Init styles for tree
     *
     * @param OutputInterface $output
     */
    protected function initStyles(OutputInterface $output)
    {
        $this->colors = array(
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        );

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Recursively prints a tree of the selected results.
     *
     * @param array  $results Results to be printed at this level.
     * @param string $prefix  Prefix of the current tree level.
     * @param int    $level   Current level of recursion.
     */
    protected function printTree($results, $prefix = '', $level = 1)
    {
        $count = count($results);
        $idx = 0;
        foreach ($results as $result) {
            /**
             * @var PackageInterface $package
             * @var Link             $link
             * @var array|bool       $children
             */
            list($package, $link, $children) = $result;

            $color = $this->colors[$level % count($this->colors)];
            $prevColor = $this->colors[($level - 1) % count($this->colors)];
            $isLast = (++$idx == $count);
            $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
            $packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $package->getPrettyName(), $versionText));
            $linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
            $circularWarn = $children === false ? '(circular dependency aborted here)' : '';
            $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
            if ($children) {
                $this->printTree($children, $prefix . ($isLast ? '   ' : '│  '), $level + 1);
            }
        }
    }

    private function writeTreeLine($line)
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(array('└', '├', '──', '│'), array('`-', '|-', '-', '|'), $line);
        }

        $io->write($line);
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Installer;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('install')
            ->setAliases(array('i'))
            ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.')
            ->setDefinition(array(
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
            ))
            ->setHelp(
                <<<EOT
The <info>install</info> command reads the composer.lock file from
the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not
exist it will look for composer.json and do the same.

<info>php composer.phar install</info>

Read more at https://getcomposer.org/doc/03-cli.md#install-i
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();
        if ($args = $input->getArgument('packages')) {
            $io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');

            return 1;
        }

        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }

        $io->writeError('<warning>You are using Composer 1 which is deprecated. You should upgrade to Composer 2, see https://blog.packagist.com/deprecating-composer-1-support/</warning>');

        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class ProhibitsCommand extends BaseDependencyCommand
{
    /**
     * Configure command metadata.
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('prohibits')
            ->setAliases(array('why-not'))
            ->setDescription('Shows which packages prevent the given package from being installed.')
            ->setHelp(
                <<<EOT
Displays detailed information about why a package cannot be installed.

<info>php composer.phar prohibits composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not-
EOT
            )
        ;
    }

    /**
     * Execute the function.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return parent::doExecute($input, $output, true);
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Installer;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Util\Silencer;

/**
 * @author Jérémy Romey <jeremy@free-agent.fr>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RequireCommand extends InitCommand
{
    private $newlyCreated;
    private $json;
    private $file;
    private $composerBackup;

    protected function configure()
    {
        $this
            ->setName('require')
            ->setDescription('Adds required packages to your composer.json and installs them.')
            ->setDefinition(array(
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
                new InputOption('update-with-all-dependencies', null, InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
                new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
            ))
            ->setHelp(
                <<<EOT
The require command adds required packages to your composer.json and installs them.

If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of
matches to require.

If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.

If you do not want to install the new dependencies immediately you can call it with --no-update

Read more at https://getcomposer.org/doc/03-cli.md#require
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            pcntl_async_signals(true);
            pcntl_signal(SIGINT, array($this, 'revertComposerFile'));
            pcntl_signal(SIGTERM, array($this, 'revertComposerFile'));
            pcntl_signal(SIGHUP, array($this, 'revertComposerFile'));
        }

        $this->file = Factory::getComposerFile();
        $io = $this->getIO();

        $this->newlyCreated = !file_exists($this->file);
        if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) {
            $io->writeError('<error>'.$this->file.' could not be created.</error>');

            return 1;
        }
        // check for readability by reading the file as is_readable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_readable($this->file) && false === Silencer::call('file_get_contents', $this->file)) {
            $io->writeError('<error>'.$this->file.' is not readable.</error>');

            return 1;
        }

        if (filesize($this->file) === 0) {
            file_put_contents($this->file, "{\n}\n");
        }

        $this->json = new JsonFile($this->file);
        $this->composerBackup = file_get_contents($this->json->getPath());

        // check for writability by writing to the file as is_writable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
            $io->writeError('<error>'.$this->file.' is not writable.</error>');

            return 1;
        }

        if ($input->getOption('fixed') === true) {
            $config = $this->json->read();

            $packageType = empty($config['type']) ? 'library' : $config['type'];

            /**
             * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
             */
            if ($packageType !== 'project') {
                $io->writeError('<error>"--fixed" option is allowed for "project" package types only to prevent possible misuses.</error>');

                if (empty($config['type'])) {
                    $io->writeError('<error>If your package is not library, you should explicitly specify "type" parameter in composer.json.</error>');
                }

                return 1;
            }
        }

        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $repos = $composer->getRepositoryManager()->getRepositories();

        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
        // initialize $this->repos as it is used by the parent InitCommand
        $this->repos = new CompositeRepository(array_merge(
            array(new PlatformRepository(array(), $platformOverrides)),
            $repos
        ));

        if ($composer->getPackage()->getPreferStable()) {
            $preferredStability = 'stable';
        } else {
            $preferredStability = $composer->getPackage()->getMinimumStability();
        }

        $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
        try {
            $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'), $input->getOption('fixed'));
        } catch (\Exception $e) {
            if ($this->newlyCreated) {
                throw new \RuntimeException('No composer.json present in the current directory, this may be the cause of the following exception.', 0, $e);
            }

            throw $e;
        }

        $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
        $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
        $requirements = $this->formatRequirements($requirements);

        // validate requirements format
        $versionParser = new VersionParser();
        foreach ($requirements as $package => $constraint) {
            if (strtolower($package) === $composer->getPackage()->getName()) {
                $io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));

                return 1;
            }
            $versionParser->parseConstraints($constraint);
        }

        $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');

        if (!$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) {
            $composerDefinition = $this->json->read();
            foreach ($requirements as $package => $version) {
                $composerDefinition[$requireKey][$package] = $version;
                unset($composerDefinition[$removeKey][$package]);
            }
            $this->json->write($composerDefinition);
        }

        $io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').'</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        try {
            return $this->doUpdate($input, $output, $io, $requirements);
        } catch (\Exception $e) {
            $this->revertComposerFile(false);
            throw $e;
        }
    }

    private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements)
    {
        // Update packages
        $this->resetComposer();
        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($input->getOption('prefer-source'))
            ->setPreferDist($input->getOption('prefer-dist'))
            ->setDevMode($updateDevMode)
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateAllowList(array_keys($requirements))
            ->setAllowListTransitiveDependencies($input->getOption('update-with-dependencies'))
            ->setAllowListAllDependencies($input->getOption('update-with-all-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
        ;

        $status = $install->run();
        if ($status !== 0) {
            $this->revertComposerFile(false);
        }

        return $status;
    }

    private function updateFileCleanly($json, array $new, $requireKey, $removeKey, $sortPackages)
    {
        $contents = file_get_contents($json->getPath());

        $manipulator = new JsonManipulator($contents);

        foreach ($new as $package => $constraint) {
            if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
                return false;
            }
            if (!$manipulator->removeSubNode($removeKey, $package)) {
                return false;
            }
        }

        file_put_contents($json->getPath(), $manipulator->getContents());

        return true;
    }

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        return;
    }

    public function revertComposerFile($hardExit = true)
    {
        $io = $this->getIO();

        if ($this->newlyCreated) {
            $io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
            unlink($this->json->getPath());
        } else {
            $io->writeError("\n".'<error>Installation failed, reverting '.$this->file.' to its original content.</error>');
            file_put_contents($this->json->getPath(), $this->composerBackup);
        }

        if ($hardExit) {
            exit(1);
        }
    }
}
<?php

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

namespace Composer\Command;

use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Config;
use Composer\Config\JsonConfigSource;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Semver\VersionParser;
use Composer\Package\BasePackage;

/**
 * @author Joshua Estes <Joshua.Estes@iostudio.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigCommand extends BaseCommand
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * @var JsonFile
     */
    protected $configFile;

    /**
     * @var JsonConfigSource
     */
    protected $configSource;

    /**
     * @var JsonFile
     */
    protected $authConfigFile;

    /**
     * @var JsonConfigSource
     */
    protected $authConfigSource;

    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('config')
            ->setDescription('Sets config options.')
            ->setDefinition(array(
                new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
                new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'),
                new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'),
                new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
                new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
                new InputArgument('setting-key', null, 'Setting key'),
                new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
            ))
            ->setHelp(
                <<<EOT
This command allows you to edit composer config settings and repositories
in either the local composer.json file or the global config.json file.

Additionally it lets you edit most properties in the local composer.json.

To set a config setting:

    <comment>%command.full_name% bin-dir bin/</comment>

To read a config setting:

    <comment>%command.full_name% bin-dir</comment>
    Outputs: <info>bin</info>

To edit the global config.json file:

    <comment>%command.full_name% --global</comment>

To add a repository:

    <comment>%command.full_name% repositories.foo vcs https://bar.com</comment>

To remove a repository (repo is a short alias for repositories):

    <comment>%command.full_name% --unset repo.foo</comment>

To disable packagist:

    <comment>%command.full_name% repo.packagist false</comment>

You can alter repositories in the global config.json file by passing in the
<info>--global</info> option.

To add or edit suggested packages you can use:

    <comment>%command.full_name% suggest.package reason for the suggestion</comment>

To add or edit extra properties you can use:

    <comment>%command.full_name% extra.property value</comment>

To edit the file in an external editor:

    <comment>%command.full_name% --editor</comment>

To choose your editor you can set the "EDITOR" env variable.

To get a list of configuration values in the file:

    <comment>%command.full_name% --list</comment>

You can always pass more than one option. As an example, if you want to edit the
global config.json file.

    <comment>%command.full_name% --editor --global</comment>

Read more at https://getcomposer.org/doc/03-cli.md#config
EOT
            )
        ;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        parent::initialize($input, $output);

        if ($input->getOption('global') && null !== $input->getOption('file')) {
            throw new \RuntimeException('--file and --global can not be combined');
        }

        $io = $this->getIO();
        $this->config = Factory::createConfig($io);

        // Get the local composer.json, global config.json, or if the user
        // passed in a file to use
        $configFile = $input->getOption('global')
            ? ($this->config->get('home') . '/config.json')
            : ($input->getOption('file') ?: Factory::getComposerFile());

        // Create global composer.json if this was invoked using `composer global config`
        if (
            ($configFile === 'composer.json' || $configFile === './composer.json')
            && !file_exists($configFile)
            && realpath(getcwd()) === realpath($this->config->get('home'))
        ) {
            file_put_contents($configFile, "{\n}\n");
        }

        $this->configFile = new JsonFile($configFile, null, $io);
        $this->configSource = new JsonConfigSource($this->configFile);

        $authConfigFile = $input->getOption('global')
            ? ($this->config->get('home') . '/auth.json')
            : dirname(realpath($configFile)) . '/auth.json';

        $this->authConfigFile = new JsonFile($authConfigFile, null, $io);
        $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);

        // Initialize the global file if it's not there, ignoring any warnings or notices
        if ($input->getOption('global') && !$this->configFile->exists()) {
            touch($this->configFile->getPath());
            $this->configFile->write(array('config' => new \ArrayObject));
            Silencer::call('chmod', $this->configFile->getPath(), 0600);
        }
        if ($input->getOption('global') && !$this->authConfigFile->exists()) {
            touch($this->authConfigFile->getPath());
            $this->authConfigFile->write(array('bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject));
            Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
        }

        if (!$this->configFile->exists()) {
            throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Open file in editor
        if ($input->getOption('editor')) {
            $editor = escapeshellcmd(getenv('EDITOR'));
            if (!$editor) {
                if (Platform::isWindows()) {
                    $editor = 'notepad';
                } else {
                    foreach (array('editor', 'vim', 'vi', 'nano', 'pico', 'ed') as $candidate) {
                        if (exec('which '.$candidate)) {
                            $editor = $candidate;
                            break;
                        }
                    }
                }
            }

            $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
            system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`'));

            return 0;
        }

        if (!$input->getOption('global')) {
            $this->config->merge($this->configFile->read());
            $this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array()));
        }

        // List the configuration of the file settings
        if ($input->getOption('list')) {
            $this->listConfiguration($this->config->all(), $this->config->raw(), $output);

            return 0;
        }

        $settingKey = $input->getArgument('setting-key');
        if (!$settingKey) {
            return 0;
        }

        // If the user enters in a config variable, parse it and save to file
        if (array() !== $input->getArgument('setting-value') && $input->getOption('unset')) {
            throw new \RuntimeException('You can not combine a setting value with --unset');
        }

        // show the value if no value is provided
        if (array() === $input->getArgument('setting-value') && !$input->getOption('unset')) {
            $properties = array('name', 'type', 'description', 'homepage', 'version', 'minimum-stability', 'prefer-stable', 'keywords', 'license', 'extra');
            $rawData = $this->configFile->read();
            $data = $this->config->all();
            if (preg_match('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) {
                if (!isset($matches[1]) || $matches[1] === '') {
                    $value = isset($data['repositories']) ? $data['repositories'] : array();
                } else {
                    if (!isset($data['repositories'][$matches[1]])) {
                        throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined');
                    }

                    $value = $data['repositories'][$matches[1]];
                }
            } elseif (strpos($settingKey, '.')) {
                $bits = explode('.', $settingKey);
                if ($bits[0] === 'extra') {
                    $data = $rawData;
                } else {
                    $data = $data['config'];
                }
                $match = false;
                foreach ($bits as $bit) {
                    $key = isset($key) ? $key.'.'.$bit : $bit;
                    $match = false;
                    if (isset($data[$key])) {
                        $match = true;
                        $data = $data[$key];
                        unset($key);
                    }
                }

                if (!$match) {
                    throw new \RuntimeException($settingKey.' is not defined.');
                }

                $value = $data;
            } elseif (isset($data['config'][$settingKey])) {
                $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
            } elseif (in_array($settingKey, $properties, true) && isset($rawData[$settingKey])) {
                $value = $rawData[$settingKey];
            } else {
                throw new \RuntimeException($settingKey.' is not defined');
            }

            if (is_array($value)) {
                $value = json_encode($value);
            }

            $this->getIO()->write($value, true, IOInterface::QUIET);

            return 0;
        }

        $values = $input->getArgument('setting-value'); // what the user is trying to add/change

        $booleanValidator = function ($val) {
            return in_array($val, array('true', 'false', '1', '0'), true);
        };
        $booleanNormalizer = function ($val) {
            return $val !== 'false' && (bool) $val;
        };

        // handle config values
        $uniqueConfigValues = array(
            'process-timeout' => array('is_numeric', 'intval'),
            'use-include-path' => array($booleanValidator, $booleanNormalizer),
            'use-github-api' => array($booleanValidator, $booleanNormalizer),
            'preferred-install' => array(
                function ($val) {
                    return in_array($val, array('auto', 'source', 'dist'), true);
                },
                function ($val) {
                    return $val;
                },
            ),
            'store-auths' => array(
                function ($val) {
                    return in_array($val, array('true', 'false', 'prompt'), true);
                },
                function ($val) {
                    if ('prompt' === $val) {
                        return 'prompt';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ),
            'notify-on-install' => array($booleanValidator, $booleanNormalizer),
            'vendor-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'bin-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'archive-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'archive-format' => array('is_string', function ($val) {
                return $val;
            }),
            'data-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'cache-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'cache-files-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'cache-repo-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'cache-vcs-dir' => array('is_string', function ($val) {
                return $val;
            }),
            'cache-ttl' => array('is_numeric', 'intval'),
            'cache-files-ttl' => array('is_numeric', 'intval'),
            'cache-files-maxsize' => array(
                function ($val) {
                    return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0;
                },
                function ($val) {
                    return $val;
                },
            ),
            'bin-compat' => array(
                function ($val) {
                    return in_array($val, array('auto', 'full'));
                },
                function ($val) {
                    return $val;
                },
            ),
            'discard-changes' => array(
                function ($val) {
                    return in_array($val, array('stash', 'true', 'false', '1', '0'), true);
                },
                function ($val) {
                    if ('stash' === $val) {
                        return 'stash';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ),
            'autoloader-suffix' => array('is_string', function ($val) {
                return $val === 'null' ? null : $val;
            }),
            'sort-packages' => array($booleanValidator, $booleanNormalizer),
            'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
            'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
            'apcu-autoloader' => array($booleanValidator, $booleanNormalizer),
            'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
            'disable-tls' => array($booleanValidator, $booleanNormalizer),
            'secure-http' => array($booleanValidator, $booleanNormalizer),
            'cafile' => array(
                function ($val) {
                    return file_exists($val) && is_readable($val);
                },
                function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ),
            'capath' => array(
                function ($val) {
                    return is_dir($val) && is_readable($val);
                },
                function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ),
            'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
            'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
            'lock' => array($booleanValidator, $booleanNormalizer),
        );
        $multiConfigValues = array(
            'github-protocols' => array(
                function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    foreach ($vals as $val) {
                        if (!in_array($val, array('git', 'https', 'ssh'))) {
                            return 'valid protocols include: git, https, ssh';
                        }
                    }

                    return true;
                },
                function ($vals) {
                    return $vals;
                },
            ),
            'github-domains' => array(
                function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                function ($vals) {
                    return $vals;
                },
            ),
            'gitlab-domains' => array(
                function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                function ($vals) {
                    return $vals;
                },
            ),
        );

        if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
            if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            }

            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }
        if (isset($uniqueConfigValues[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        if (isset($multiConfigValues[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        // handle preferred-install per-package config
        if (preg_match('/^preferred-install\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            list($validator) = $uniqueConfigValues['preferred-install'];
            if (!$validator($values[0])) {
                throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist');
            }

            $this->configSource->addConfigSetting($settingKey, $values[0]);

            return 0;
        }

        // handle properties
        $uniqueProps = array(
            'name' => array('is_string', function ($val) {
                return $val;
            }),
            'type' => array('is_string', function ($val) {
                return $val;
            }),
            'description' => array('is_string', function ($val) {
                return $val;
            }),
            'homepage' => array('is_string', function ($val) {
                return $val;
            }),
            'version' => array('is_string', function ($val) {
                return $val;
            }),
            'minimum-stability' => array(
                function ($val) {
                    return isset(BasePackage::$stabilities[VersionParser::normalizeStability($val)]);
                },
                function ($val) {
                    return VersionParser::normalizeStability($val);
                },
            ),
            'prefer-stable' => array($booleanValidator, $booleanNormalizer),
        );
        $multiProps = array(
            'keywords' => array(
                function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                function ($vals) {
                    return $vals;
                },
            ),
            'license' => array(
                function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                function ($vals) {
                    return $vals;
                },
            ),
        );

        if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || substr($settingKey, 0, 6) === 'extra.')) {
            throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
        }
        if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }
        if (isset($uniqueProps[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');

            return 0;
        }
        if (isset($multiProps[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');

            return 0;
        }

        // handle repositories
        if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeRepository($matches[1]);

                return 0;
            }

            if (2 === count($values)) {
                $this->configSource->addRepository($matches[1], array(
                    'type' => $values[0],
                    'url' => $values[1],
                ));

                return 0;
            }

            if (1 === count($values)) {
                $value = strtolower($values[0]);
                if (true === $booleanValidator($value)) {
                    if (false === $booleanNormalizer($value)) {
                        $this->configSource->addRepository($matches[1], false);

                        return 0;
                    }
                } else {
                    $value = JsonFile::parseJson($values[0]);
                    $this->configSource->addRepository($matches[1], $value);

                    return 0;
                }
            }

            throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
        }

        // handle extra
        if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, $values[0]);

            return 0;
        }

        // handle suggest
        if (preg_match('/^suggest\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, implode(' ', $values));

            return 0;
        }

        // handle unsetting extra/suggest
        if (in_array($settingKey, array('suggest', 'extra'), true) && $input->getOption('unset')) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }

        // handle platform
        if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            $this->configSource->addConfigSetting($settingKey, $values[0]);

            return 0;
        }

        // handle unsetting platform
        if ($settingKey === 'platform' && $input->getOption('unset')) {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        // handle auth
        if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|bearer)\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);

                return 0;
            }

            if ($matches[1] === 'bitbucket-oauth') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('consumer-key' => $values[0], 'consumer-secret' => $values[1]));
            } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'), true)) {
                if (1 !== count($values)) {
                    throw new \RuntimeException('Too many arguments, expected only one token');
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
            } elseif ($matches[1] === 'http-basic') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
            }

            return 0;
        }

        // handle script
        if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);

            return 0;
        }

        throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
    }

    protected function handleSingleValue($key, array $callbacks, array $values, $method)
    {
        list($validator, $normalizer) = $callbacks;
        if (1 !== count($values)) {
            throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300');
        }

        if (true !== $validation = $validator($values[0])) {
            throw new \RuntimeException(sprintf(
                '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                $values[0]
            ));
        }

        $normalizedValue = $normalizer($values[0]);

        if ($key === 'disable-tls') {
            if (!$normalizedValue && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            } elseif ($normalizedValue && !$this->config->get('disable-tls')) {
                $this->getIO()->writeError('<warning>You are now running Composer with SSL/TLS protection disabled.</warning>');
            }
        }

        return call_user_func(array($this->configSource, $method), $key, $normalizedValue);
    }

    protected function handleMultiValue($key, array $callbacks, array $values, $method)
    {
        list($validator, $normalizer) = $callbacks;
        if (true !== $validation = $validator($values)) {
            throw new \RuntimeException(sprintf(
                '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                json_encode($values)
            ));
        }

        return call_user_func(array($this->configSource, $method), $key, $normalizer($values));
    }

    /**
     * Display the contents of the file in a pretty formatted way
     *
     * @param array           $contents
     * @param array           $rawContents
     * @param OutputInterface $output
     * @param string|null     $k
     */
    protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, $k = null)
    {
        $origK = $k;
        $io = $this->getIO();
        foreach ($contents as $key => $value) {
            if ($k === null && !in_array($key, array('config', 'repositories'))) {
                continue;
            }

            $rawVal = isset($rawContents[$key]) ? $rawContents[$key] : null;

            if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) {
                $k .= preg_replace('{^config\.}', '', $key . '.');
                $this->listConfiguration($value, $rawVal, $output, $k);
                $k = $origK;

                continue;
            }

            if (is_array($value)) {
                $value = array_map(function ($val) {
                    return is_array($val) ? json_encode($val) : $val;
                }, $value);

                $value = '['.implode(', ', $value).']';
            }

            if (is_bool($value)) {
                $value = var_export($value, true);
            }

            if (is_string($rawVal) && $rawVal != $value) {
                $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $rawVal . ' (' . $value . ')</info>', true, IOInterface::QUIET);
            } else {
                $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $value . '</info>', true, IOInterface::QUIET);
            }
        }
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Config;
use Composer\Console\Application;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

/**
 * Base class for Composer commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class BaseCommand extends Command
{
    /**
     * @var Composer|null
     */
    private $composer;

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

    /**
     * @param  bool              $required
     * @param  bool|null         $disablePlugins
     * @throws \RuntimeException
     * @return Composer
     */
    public function getComposer($required = true, $disablePlugins = null)
    {
        if (null === $this->composer) {
            $application = $this->getApplication();
            if ($application instanceof Application) {
                /* @var $application    Application */
                $this->composer = $application->getComposer($required, $disablePlugins);
            } elseif ($required) {
                throw new \RuntimeException(
                    'Could not create a Composer\Composer instance, you must inject '.
                    'one if this command is not used with a Composer\Console\Application instance'
                );
            }
        }

        return $this->composer;
    }

    /**
     * @param Composer $composer
     */
    public function setComposer(Composer $composer)
    {
        $this->composer = $composer;
    }

    /**
     * Removes the cached composer instance
     */
    public function resetComposer()
    {
        $this->composer = null;
        $this->getApplication()->resetComposer();
    }

    /**
     * Whether or not this command is meant to call another command.
     *
     * This is mainly needed to avoid duplicated warnings messages.
     *
     * @return bool
     */
    public function isProxyCommand()
    {
        return false;
    }

    /**
     * @return IOInterface
     */
    public function getIO()
    {
        if (null === $this->io) {
            $application = $this->getApplication();
            if ($application instanceof Application) {
                /* @var $application    Application */
                $this->io = $application->getIO();
            } else {
                $this->io = new NullIO();
            }
        }

        return $this->io;
    }

    /**
     * @param IOInterface $io
     */
    public function setIO(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        // initialize a plugin-enabled Composer instance, either local or global
        $disablePlugins = $input->hasParameterOption('--no-plugins');
        $composer = $this->getComposer(false, $disablePlugins);
        if (null === $composer) {
            $composer = Factory::createGlobal($this->getIO(), $disablePlugins);
        }
        if ($composer) {
            $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName());
            $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent);
        }

        if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) {
            $input->setOption('no-progress', true);
        }

        parent::initialize($input, $output);
    }

    /**
     * Returns preferSource and preferDist values based on the configuration.
     *
     * @param Config         $config
     * @param InputInterface $input
     * @param bool           $keepVcsRequiresPreferSource
     *
     * @return bool[] An array composed of the preferSource and preferDist values
     */
    protected function getPreferredInstallOptions(Config $config, InputInterface $input, $keepVcsRequiresPreferSource = false)
    {
        $preferSource = false;
        $preferDist = false;

        switch ($config->get('preferred-install')) {
            case 'source':
                $preferSource = true;
                break;
            case 'dist':
                $preferDist = true;
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
            $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
            $preferDist = $input->getOption('prefer-dist');
        }

        return array($preferSource, $preferDist);
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class SearchCommand extends BaseCommand
{
    protected $matches;
    protected $lowMatches = array();
    protected $tokens;
    protected $output;
    protected $onlyName;

    protected function configure()
    {
        $this
            ->setName('search')
            ->setDescription('Searches for packages.')
            ->setDefinition(array(
                new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in name'),
                new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'),
                new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
            ))
            ->setHelp(
                <<<EOT
The search command searches for packages by its name
<info>php composer.phar search symfony composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#search
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // init repos
        $platformRepo = new PlatformRepository;
        $io = $this->getIO();
        if (!($composer = $this->getComposer(false))) {
            $composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins'));
        }
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $installedRepo = new CompositeRepository(array($localRepo, $platformRepo));
        $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories()));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $onlyName = $input->getOption('only-name');
        $type = $input->getOption('type') ?: null;

        $flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT;
        $results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags, $type);

        foreach ($results as $result) {
            $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Package\CompletePackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\CompositeRepository;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FundCommand extends BaseCommand
{
    protected function configure()
    {
        $this->setName('fund')
            ->setDescription('Discover how to help fund the maintenance of your dependencies.')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $repo = $composer->getRepositoryManager()->getLocalRepository();
        $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
        $fundings = array();
        foreach ($repo->getPackages() as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $latest = $remoteRepos->findPackage($package->getName(), 'dev-master');
            if ($latest instanceof CompletePackageInterface && $latest->getFunding()) {
                $fundings = $this->insertFundingData($fundings, $latest);
                continue;
            }
            if ($package instanceof CompletePackageInterface && $package->getFunding()) {
                $fundings = $this->insertFundingData($fundings, $package);
            }
        }

        ksort($fundings);

        $io = $this->getIO();

        if ($fundings) {
            $prev = null;

            $io->write('The following packages were found in your dependencies which publish funding information:');

            foreach ($fundings as $vendor => $links) {
                $io->write('');
                $io->write(sprintf("<comment>%s</comment>", $vendor));
                foreach ($links as $url => $packages) {
                    $line = sprintf('  <info>%s</info>', implode(', ', $packages));

                    if ($prev !== $line) {
                        $io->write($line);
                        $prev = $line;
                    }

                    $io->write(sprintf('    %s', $url));
                }
            }

            $io->write("");
            $io->write("Please consider following these links and sponsoring the work of package authors!");
            $io->write("Thank you!");
        } else {
            $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!");
        }

        return 0;
    }

    private function insertFundingData(array $fundings, CompletePackageInterface $package)
    {
        foreach ($package->getFunding() as $fundingOption) {
            list($vendor, $packageName) = explode('/', $package->getPrettyName());
            // ignore malformed funding entries
            if (empty($fundingOption['url'])) {
                continue;
            }
            $url = $fundingOption['url'];
            if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && preg_match('{^https://github.com/([^/]+)$}', $url, $match)) {
                $url = 'https://github.com/sponsors/'.$match[1];
            }
            $fundings[$vendor][$url][] = $packageName;
        }

        return $fundings;
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AboutCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('about')
            ->setDescription('Shows the short information about Composer.')
            ->setHelp(
                <<<EOT
<info>php composer.phar about</info>
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->getIO()->write(
            <<<EOT
<info>Composer - Dependency Manager for PHP</info>
<comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.
See https://getcomposer.org/ for more information.</comment>
EOT
        );

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GlobalCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('global')
            ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME).')
            ->setDefinition(array(
                new InputArgument('command-name', InputArgument::REQUIRED, ''),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ))
            ->setHelp(
                <<<EOT
Use this command as a wrapper to run other Composer commands
within the global context of COMPOSER_HOME.

You can use this to install CLI utilities globally, all you need
is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.

COMPOSER_HOME is c:\Users\<user>\AppData\Roaming\Composer on Windows
and /home/<user>/.composer on unix systems.

If your system uses freedesktop.org standards, then it will first check
XDG_CONFIG_HOME or default to /home/<user>/.config/composer

Note: This path may vary depending on customizations to bin-dir in
composer.json or the environmental variable COMPOSER_BIN_DIR.

Read more at https://getcomposer.org/doc/03-cli.md#global
EOT
            )
        ;
    }

    public function run(InputInterface $input, OutputInterface $output)
    {
        // extract real command name
        $tokens = preg_split('{\s+}', $input->__toString());
        $args = array();
        foreach ($tokens as $token) {
            if ($token && $token[0] !== '-') {
                $args[] = $token;
                if (count($args) >= 2) {
                    break;
                }
            }
        }

        // show help for this command if no command was found
        if (count($args) < 2) {
            return parent::run($input, $output);
        }

        // The COMPOSER env var should not apply to the global execution scope
        if (getenv('COMPOSER')) {
            putenv('COMPOSER');
            unset($_SERVER['COMPOSER']);
        }

        // change to global dir
        $config = Factory::createConfig();
        $home = $config->get('home');

        if (!is_dir($home)) {
            $fs = new Filesystem();
            $fs->ensureDirectoryExists($home);
            if (!is_dir($home)) {
                throw new \RuntimeException('Could not create home directory');
            }
        }

        try {
            chdir($home);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e);
        }
        $this->getIO()->writeError('<info>Changed current directory to '.$home.'</info>');

        // create new input without "global" command prefix
        $input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));
        $this->getApplication()->resetComposer();

        return $this->getApplication()->run($input, $output);
    }

    /**
     * {@inheritDoc}
     */
    public function isProxyCommand()
    {
        return true;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Config;
use Composer\Factory;
use Composer\Installer;
use Composer\Installer\ProjectInstaller;
use Composer\Installer\InstallationManager;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\InstalledFilesystemRepository;
use Composer\Script\ScriptEvents;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem;
use Composer\Package\Version\VersionParser;
use Composer\EventDispatcher\EventDispatcher;

/**
 * Install a package as new project into new directory.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Tobias Munk <schmunk@usrbin.de>
 * @author Nils Adermann <naderman@naderman.de>
 */
class CreateProjectCommand extends BaseCommand
{
    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    protected function configure()
    {
        $this
            ->setName('create-project')
            ->setDescription('Creates new project from a package into given directory.')
            ->setDefinition(array(
                new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'),
                new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED, 'Pick a different repository (as url or json config) to look for the package.'),
                new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'),
                new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the repository option to the composer.json.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'),
                new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
                new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
            ))
            ->setHelp(
                <<<EOT
The <info>create-project</info> command creates a new project from a given
package into a new directory. If executed without params and in a directory
with a composer.json file it installs the packages for the current project.

You can use this command to bootstrap new projects or setup a clean
version-controlled installation for developers of your project.

<info>php composer.phar create-project vendor/project target-directory [version]</info>

You can also specify the version with the package name using = or : as separator.

<info>php composer.phar create-project vendor/project:version target-directory</info>

To install unstable packages, either specify the version you want, or use the
--stability=dev (where dev can be one of RC, beta, alpha or dev).

To setup a developer workable version you should create the project using the source
controlled code by appending the <info>'--prefer-source'</info> flag.

To install a package from another repository than the default one you
can pass the <info>'--repository=https://myrepository.org'</info> flag.

Read more at https://getcomposer.org/doc/03-cli.md#create-project
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();
        $io = $this->getIO();

        list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input, true);

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }
        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        return $this->installProject(
            $io,
            $config,
            $input,
            $input->getArgument('package'),
            $input->getArgument('directory'),
            $input->getArgument('version'),
            $input->getOption('stability'),
            $preferSource,
            $preferDist,
            !$input->getOption('no-dev'),
            $input->getOption('repository') ?: $input->getOption('repository-url'),
            $input->getOption('no-plugins'),
            $input->getOption('no-scripts'),
            $input->getOption('no-progress'),
            $input->getOption('no-install'),
            $input->getOption('ignore-platform-reqs'),
            !$input->getOption('no-secure-http'),
            $input->getOption('add-repository')
        );
    }

    public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true, $addRepository = false)
    {
        $oldCwd = getcwd();

        // we need to manually load the configuration to pass the auth credentials to the io interface!
        $io->loadConfiguration($config);

        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io);

        if ($packageName !== null) {
            $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $noProgress, $ignorePlatformReqs, $secureHttp);
        } else {
            $installedFromVcs = false;
        }

        $composer = Factory::create($io, null, $disablePlugins);

        // add the repository to the composer.json and use it for the install run later
        if ($repository !== null && $addRepository) {
            if ($composer->getLocker()->isLocked()) {
                $io->writeError('<error>Adding a repository when creating a project that provides a composer.lock file is not supported</error>');

                return false;
            }

            $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repository, true);
            $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories();
            $name = RepositoryFactory::generateRepositoryName(0, $repoConfig, $composerJsonRepositoriesConfig);
            $configSource = new JsonConfigSource(new JsonFile('composer.json'));
            $configSource->addRepository($name, $repoConfig);

            $composer = Factory::create($io, null, $disablePlugins);
        }

        $composer->getDownloadManager()->setOutputProgress(!$noProgress);

        $fs = new Filesystem();

        if ($noScripts === false) {
            // dispatch event
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages);
        }

        // use the new config including the newly installed project
        $config = $composer->getConfig();
        list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input);

        // install dependencies of the created project
        if ($noInstall === false) {
            $installer = Installer::create($io, $composer);
            $installer->setPreferSource($preferSource)
                ->setPreferDist($preferDist)
                ->setDevMode($installDevPackages)
                ->setRunScripts(!$noScripts)
                ->setIgnorePlatformRequirements($ignorePlatformReqs)
                ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
                ->setApcuAutoloader($config->get('apcu-autoloader'));

            if ($disablePlugins) {
                $installer->disablePlugins();
            }

            $status = $installer->run();
            if (0 !== $status) {
                return $status;
            }
        }

        $hasVcs = $installedFromVcs;
        if (
            !$input->getOption('keep-vcs')
            && $installedFromVcs
            && (
                $input->getOption('remove-vcs')
                || !$io->isInteractive()
                || $io->askConfirmation('<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>Y,n</comment>]? ', true)
            )
        ) {
            $finder = new Finder();
            $finder->depth(0)->directories()->in(getcwd())->ignoreVCS(false)->ignoreDotFiles(false);
            foreach (array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_') as $vcsName) {
                $finder->name($vcsName);
            }

            try {
                $dirs = iterator_to_array($finder);
                unset($finder);
                foreach ($dirs as $dir) {
                    if (!$fs->removeDirectory($dir)) {
                        throw new \RuntimeException('Could not remove '.$dir);
                    }
                }
            } catch (\Exception $e) {
                $io->writeError('<error>An error occurred while removing the VCS metadata: '.$e->getMessage().'</error>');
            }

            $hasVcs = false;
        }

        // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone
        if (!$hasVcs) {
            $package = $composer->getPackage();
            $configSource = new JsonConfigSource(new JsonFile('composer.json'));
            foreach (BasePackage::$supportedLinkTypes as $type => $meta) {
                foreach ($package->{'get'.$meta['method']}() as $link) {
                    if ($link->getPrettyConstraint() === 'self.version') {
                        $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion());
                    }
                }
            }
        }

        if ($noScripts === false) {
            // dispatch event
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages);
        }

        chdir($oldCwd);
        $vendorComposerDir = $config->get('vendor-dir').'/composer';
        if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) {
            Silencer::call('rmdir', $vendorComposerDir);
            $vendorDir = $config->get('vendor-dir');
            if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) {
                Silencer::call('rmdir', $vendorDir);
            }
        }

        return 0;
    }

    protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true)
    {
        if (!$secureHttp) {
            $config->merge(array('config' => array('secure-http' => false)));
        }

        $composer = Factory::create($io, $config->all(), $disablePlugins);
        $config = $composer->getConfig();
        $rm = $composer->getRepositoryManager();

        if (null === $repository) {
            $sourceRepo = new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm));
        } else {
            $sourceRepo = RepositoryFactory::fromString($io, $config, $repository, true, $rm);
        }

        $parser = new VersionParser();
        $requirements = $parser->parseNameVersionPairs(array($packageName));
        $name = strtolower($requirements[0]['name']);
        if (!$packageVersion && isset($requirements[0]['version'])) {
            $packageVersion = $requirements[0]['version'];
        }

        // if no directory was specified, use the 2nd part of the package name
        if (null === $directory) {
            $parts = explode("/", $name, 2);
            $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);
        }

        $fs = new Filesystem();
        if (!$fs->isAbsolutePath($directory)) {
            $directory = getcwd() . DIRECTORY_SEPARATOR . $directory;
        }

        $io->writeError('<info>Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(getcwd(), $directory, true) . '"</info>');

        if (file_exists($directory)) {
            if (!is_dir($directory)) {
                throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.');
            } elseif (!$fs->isDirEmpty($directory)) {
                throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.');
            }
        }

        if (null === $stability) {
            if (preg_match('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $packageVersion, $match)) {
                $stability = $match[1];
            } else {
                $stability = VersionParser::parseStability($packageVersion);
            }
        }

        $stability = VersionParser::normalizeStability($stability);

        if (!isset(BasePackage::$stabilities[$stability])) {
            throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
        }

        $pool = new Pool($stability);
        $pool->addRepository($sourceRepo);

        $phpVersion = null;
        $prettyPhpVersion = null;
        if (!$ignorePlatformReqs) {
            $platformOverrides = $config->get('platform') ?: array();
            // initialize $this->repos as it is used by the parent InitCommand
            $platform = new PlatformRepository(array(), $platformOverrides);
            $phpPackage = $platform->findPackage('php', '*');
            $phpVersion = $phpPackage->getVersion();
            $prettyPhpVersion = $phpPackage->getPrettyVersion();
        }

        // find the latest version if there are multiple
        $versionSelector = new VersionSelector($pool);
        $package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability);

        if (!$package) {
            $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability");
            if ($phpVersion && $versionSelector->findBestCandidate($name, $packageVersion, null, $stability)) {
                throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version '.$prettyPhpVersion.'.');
            }

            throw new \InvalidArgumentException($errorMessage .'.');
        }

        // handler Ctrl+C for unix-like systems
        if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            @mkdir($directory, 0777, true);
            if ($realDir = realpath($directory)) {
                pcntl_async_signals(true);
                pcntl_signal(SIGINT, function () use ($realDir) {
                    $fs = new Filesystem();
                    $fs->removeDirectory($realDir);
                    exit(130);
                });
            }
        }

        $io->writeError('<info>Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')</info>');

        if ($disablePlugins) {
            $io->writeError('<info>Plugins have been disabled.</info>');
        }

        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        $dm = $composer->getDownloadManager();
        $dm->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setOutputProgress(!$noProgress);

        $projectInstaller = new ProjectInstaller($directory, $dm);
        $im = $composer->getInstallationManager();
        $im->addInstaller($projectInstaller);
        $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
        $im->notifyInstalls($io);

        // collect suggestions
        $this->suggestedPackagesReporter->addSuggestionsFromPackage($package);

        $installedFromVcs = 'source' === $package->getInstallationSource();

        $io->writeError('<info>Created project in ' . $directory . '</info>');
        chdir($directory);

        $_SERVER['COMPOSER_ROOT_VERSION'] = $package->getPrettyVersion();
        putenv('COMPOSER_ROOT_VERSION='.$_SERVER['COMPOSER_ROOT_VERSION']);

        return $installedFromVcs;
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class OutdatedCommand extends ShowCommand
{
    protected function configure()
    {
        $this
            ->setName('outdated')
            ->setDescription('Shows a list of installed packages that have updates available, including their latest version.')
            ->setDefinition(array(
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
            ))
            ->setHelp(
                <<<EOT
The outdated command is just a proxy for `composer show -l`

The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such:

- <info>green</info> (=): Dependency is in the latest version and is up to date.
- <comment>yellow</comment> (~): Dependency has a new version available that includes backwards
  compatibility breaks according to semver, so upgrade when you can but it
  may involve work.
- <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.

Read more at https://getcomposer.org/doc/03-cli.md#outdated
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $args = array(
            'command' => 'show',
            '--latest' => true,
        );
        if (!$input->getOption('all')) {
            $args['--outdated'] = true;
        }
        if ($input->getOption('direct')) {
            $args['--direct'] = true;
        }
        if ($input->getArgument('package')) {
            $args['package'] = $input->getArgument('package');
        }
        if ($input->getOption('strict')) {
            $args['--strict'] = true;
        }
        if ($input->getOption('minor-only')) {
            $args['--minor-only'] = true;
        }
        $args['--format'] = $input->getOption('format');
        $args['--ignore'] = $input->getOption('ignore');

        $input = new ArrayInput($args);

        return $this->getApplication()->run($input, $output);
    }

    /**
     * {@inheritDoc}
     */
    public function isProxyCommand()
    {
        return true;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Cache;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author David Neilsen <petah.p@gmail.com>
 */
class ClearCacheCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('clear-cache')
            ->setAliases(array('clearcache', 'cc'))
            ->setDescription('Clears composer\'s internal package cache.')
            ->setHelp(
                <<<EOT
The <info>clear-cache</info> deletes all cached packages from composer's
cache directory.

Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();
        $io = $this->getIO();

        $cachePaths = array(
            'cache-vcs-dir' => $config->get('cache-vcs-dir'),
            'cache-repo-dir' => $config->get('cache-repo-dir'),
            'cache-files-dir' => $config->get('cache-files-dir'),
            'cache-dir' => $config->get('cache-dir'),
        );

        foreach ($cachePaths as $key => $cachePath) {
            $cachePath = realpath($cachePath);
            if (!$cachePath) {
                $io->writeError("<info>Cache directory does not exist ($key): $cachePath</info>");

                continue;
            }
            $cache = new Cache($io, $cachePath);
            if (!$cache->isEnabled()) {
                $io->writeError("<info>Cache is not enabled ($key): $cachePath</info>");

                continue;
            }

            $io->writeError("<info>Clearing cache ($key): $cachePath</info>");
            $cache->clear();
        }

        $io->writeError('<info>All caches cleared.</info>');

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class UpdateCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('update')
            ->setAliases(array('u', 'upgrade'))
            ->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.')
            ->setDefinition(array(
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also dependencies of allowed packages to the allow list, except those defined in root package.'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of allowed packages to the allow list, including those defined in root package.'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
                new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
                new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
            ))
            ->setHelp(
                <<<EOT
The <info>update</info> command reads the composer.json file from the
current directory, processes it, and updates, removes or installs all the
dependencies.

<info>php composer.phar update</info>

To limit the update operation to a few packages, you can list the package(s)
you want to update as such:

<info>php composer.phar update vendor/package1 foo/mypackage [...]</info>

You may also use an asterisk (*) pattern to limit the update operation to package(s)
from a specific vendor:

<info>php composer.phar update vendor/package1 foo/* [...]</info>

To select packages names interactively with auto-completion use <info>-i</info>.

Read more at https://getcomposer.org/doc/03-cli.md#update-u
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();
        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }

        $composer = $this->getComposer(true, $input->getOption('no-plugins'));

        $packages = $input->getArgument('packages');

        if ($input->getOption('interactive')) {
            $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
        }

        if ($input->getOption('root-reqs')) {
            $require = array_keys($composer->getPackage()->getRequires());
            if (!$input->getOption('no-dev')) {
                $requireDev = array_keys($composer->getPackage()->getDevRequires());
                $require = array_merge($require, $requireDev);
            }

            if (!empty($packages)) {
                $packages = array_intersect($packages, $require);
            } else {
                $packages = $require;
            }
        }

        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateAllowList($input->getOption('lock') ? array('lock') : $packages)
            ->setAllowListTransitiveDependencies($input->getOption('with-dependencies'))
            ->setAllowListAllDependencies($input->getOption('with-all-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }

    private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages)
    {
        if (!$input->isInteractive()) {
            throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.');
        }

        $requires = array_merge(
            $composer->getPackage()->getRequires(),
            $composer->getPackage()->getDevRequires()
        );
        $autocompleterValues = array();
        foreach ($requires as $require) {
            $target = $require->getTarget();
            $autocompleterValues[strtolower($target)] = $target;
        }

        $installedPackages = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
        foreach ($installedPackages as $package) {
            $autocompleterValues[$package->getName()] = $package->getPrettyName();
        }

        $helper = $this->getHelper('question');
        $question = new Question('<comment>Enter package name: </comment>', null);

        $io->writeError('<info>Press enter without value to end submission</info>');

        do {
            $autocompleterValues = array_diff($autocompleterValues, $packages);
            $question->setAutocompleterValues($autocompleterValues);
            $addedPackage = $helper->ask($input, $output, $question);

            if (!is_string($addedPackage) || empty($addedPackage)) {
                break;
            }

            $addedPackage = strtolower($addedPackage);
            if (!in_array($addedPackage, $packages)) {
                $packages[] = $addedPackage;
            }
        } while (true);

        $packages = array_filter($packages);
        if (!$packages) {
            throw new \InvalidArgumentException('You must enter minimum one package.');
        }

        $table = new Table($output);
        $table->setHeaders(array('Selected packages'));
        foreach ($packages as $package) {
            $table->addRow(array($package));
        }
        $table->render();

        if ($io->askConfirmation(sprintf(
            'Would you like to continue and update the above package%s [<comment>yes</comment>]? ',
            1 === count($packages) ? '' : 's'
        ), true)) {
            return $packages;
        }

        throw new \RuntimeException('Installation aborted.');
    }
}
<?php

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

namespace Composer\Command;

use Composer\Config\JsonConfigSource;
use Composer\Installer;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Json\JsonFile;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\BasePackage;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RemoveCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('remove')
            ->setDescription('Removes a package from the require or require-dev.')
            ->setDefinition(array(
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies. (Deprecrated, is now default behavior)'),
                new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
            ))
            ->setHelp(
                <<<EOT
The <info>remove</info> command removes a package from the current
list of installed packages

<info>php composer.phar remove</info>

Read more at https://getcomposer.org/doc/03-cli.md#remove
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $packages = $input->getArgument('packages');
        $packages = array_map('strtolower', $packages);

        $file = Factory::getComposerFile();

        $jsonFile = new JsonFile($file);
        $composer = $jsonFile->read();
        $composerBackup = file_get_contents($jsonFile->getPath());

        $json = new JsonConfigSource($jsonFile);

        $type = $input->getOption('dev') ? 'require-dev' : 'require';
        $altType = !$input->getOption('dev') ? 'require-dev' : 'require';
        $io = $this->getIO();

        if ($input->getOption('update-with-dependencies')) {
            $io->writeError('<warning>You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.</warning>');
        }

        // make sure name checks are done case insensitively
        foreach (array('require', 'require-dev') as $linkType) {
            if (isset($composer[$linkType])) {
                foreach ($composer[$linkType] as $name => $version) {
                    $composer[$linkType][strtolower($name)] = $name;
                }
            }
        }

        foreach ($packages as $package) {
            if (isset($composer[$type][$package])) {
                $json->removeLink($type, $composer[$type][$package]);
            } elseif (isset($composer[$altType][$package])) {
                $io->writeError('<warning>' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                if ($io->isInteractive()) {
                    if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
                        $json->removeLink($altType, $composer[$altType][$package]);
                    }
                }
            } elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
                foreach ($matches as $matchedPackage) {
                    $json->removeLink($type, $matchedPackage);
                }
            } elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) {
                foreach ($matches as $matchedPackage) {
                    $io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                    if ($io->isInteractive()) {
                        if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
                            $json->removeLink($altType, $matchedPackage);
                        }
                    }
                }
            } else {
                $io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
            }
        }

        if ($input->getOption('no-update')) {
            return 0;
        }

        // Update packages
        $this->resetComposer();
        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateAllowList($packages)
            ->setAllowListTransitiveDependencies(!$input->getOption('no-update-with-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setRunScripts(!$input->getOption('no-scripts'))
        ;

        $status = $install->run();
        if ($status !== 0) {
            $io->writeError("\n".'<error>Removal failed, reverting '.$file.' to its original content.</error>');
            file_put_contents($jsonFile->getPath(), $composerBackup);
        }

        return $status;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DumpAutoloadCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('dump-autoload')
            ->setAliases(array('dumpautoload'))
            ->setDescription('Dumps the autoloader.')
            ->setDefinition(array(
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'),
                new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
            ))
            ->setHelp(
                <<<EOT
<info>php composer.phar dump-autoload</info>

Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $installationManager = $composer->getInstallationManager();
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $package = $composer->getPackage();
        $config = $composer->getConfig();

        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');

        if ($authoritative) {
            $this->getIO()->write('<info>Generating optimized autoload files (authoritative)</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generating optimized autoload files</info>');
        } else {
            $this->getIO()->write('<info>Generating autoload files</info>');
        }

        $generator = $composer->getAutoloadGenerator();
        $generator->setDevMode(!$input->getOption('no-dev'));
        $generator->setClassMapAuthoritative($authoritative);
        $generator->setApcu($apcu);
        $generator->setRunScripts(!$input->getOption('no-scripts'));
        $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);

        if ($authoritative) {
            $this->getIO()->write('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
        } else {
            $this->getIO()->write('<info>Generated autoload files</info>');
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class HomeCommand extends BaseCommand
{
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('browse')
            ->setAliases(array('home'))
            ->setDescription('Opens the package\'s repository URL or homepage in your browser.')
            ->setDefinition(array(
                new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.'),
                new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
                new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
            ))
            ->setHelp(
                <<<EOT
The home command opens or shows a package's repository URL or
homepage in your default browser.

To open the homepage by default, use -H or --homepage.
To show instead of open the repository or homepage URL, use -s or --show.

Read more at https://getcomposer.org/doc/03-cli.md#browse-home
EOT
            );
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $repos = $this->initializeRepos();
        $io = $this->getIO();
        $return = 0;

        $packages = $input->getArgument('packages');
        if (!$packages) {
            $io->writeError('No package specified, opening homepage for the root package');
            $packages = array($this->getComposer()->getPackage()->getName());
        }

        foreach ($packages as $packageName) {
            $handled = false;
            $packageExists = false;
            foreach ($repos as $repo) {
                foreach ($repo->findPackages($packageName) as $package) {
                    $packageExists = true;
                    if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) {
                        $handled = true;
                        break 2;
                    }
                }
            }

            if (!$packageExists) {
                $return = 1;
                $io->writeError('<warning>Package '.$packageName.' not found</warning>');
            }

            if (!$handled) {
                $return = 1;
                $io->writeError('<warning>'.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.'</warning>');
            }
        }

        return $return;
    }

    private function handlePackage(CompletePackageInterface $package, $showHomepage, $showOnly)
    {
        $support = $package->getSupport();
        $url = isset($support['source']) ? $support['source'] : $package->getSourceUrl();
        if (!$url || $showHomepage) {
            $url = $package->getHomepage();
        }

        if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        if ($showOnly) {
            $this->getIO()->write(sprintf('<info>%s</info>', $url));
        } else {
            $this->openBrowser($url);
        }

        return true;
    }

    /**
     * opens a url in your system default browser
     *
     * @param string $url
     */
    private function openBrowser($url)
    {
        $url = ProcessExecutor::escape($url);

        $process = new ProcessExecutor($this->getIO());
        if (Platform::isWindows()) {
            return $process->execute('start "web" explorer ' . $url, $output);
        }

        $linux = $process->execute('which xdg-open', $output);
        $osx = $process->execute('which open', $output);

        if (0 === $linux) {
            $process->execute('xdg-open ' . $url, $output);
        } elseif (0 === $osx) {
            $process->execute('open ' . $url, $output);
        } else {
            $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
        }
    }

    /**
     * Initializes repositories
     *
     * Returns an array of repos in order they should be checked in
     *
     * @return RepositoryInterface[]
     */
    private function initializeRepos()
    {
        $composer = $this->getComposer(false);

        if ($composer) {
            return array_merge(
                array(new ArrayRepository(array($composer->getPackage()))), // root package
                array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
                $composer->getRepositoryManager()->getRepositories() // remotes
            );
        }

        return RepositoryFactory::defaultRepos($this->getIO());
    }
}
<?php

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

namespace Composer\Command;

use Composer\DependencyResolver\Pool;
use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

/**
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InitCommand extends BaseCommand
{
    /** @var CompositeRepository */
    protected $repos;

    /** @var array */
    private $gitConfig;

    /** @var Pool[] */
    private $pools;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('init')
            ->setDescription('Creates a basic composer.json file in current directory.')
            ->setDefinition(array(
                new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'),
                new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'),
                new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'),
                // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
                new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'),
                new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'),
                new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'),
                new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'),
            ))
            ->setHelp(
                <<<EOT
The <info>init</info> command creates a basic composer.json file
in the current directory.

<info>php composer.phar init</info>

Read more at https://getcomposer.org/doc/03-cli.md#init
EOT
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();

        $allowList = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
        $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowList)));

        if (isset($options['author'])) {
            $options['authors'] = $this->formatAuthors($options['author']);
            unset($options['author']);
        }

        $repositories = $input->getOption('repository');
        if ($repositories) {
            $config = Factory::createConfig($io);
            foreach ($repositories as $repo) {
                $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo);
            }
        }

        if (isset($options['stability'])) {
            $options['minimum-stability'] = $options['stability'];
            unset($options['stability']);
        }

        $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass;
        if (array() === $options['require']) {
            $options['require'] = new \stdClass;
        }

        if (isset($options['require-dev'])) {
            $options['require-dev'] = $this->formatRequirements($options['require-dev']);
            if (array() === $options['require-dev']) {
                $options['require-dev'] = new \stdClass;
            }
        }

        $file = new JsonFile(Factory::getComposerFile());
        $json = $file->encode($options);

        if ($input->isInteractive()) {
            $io->writeError(array('', $json, ''));
            if (!$io->askConfirmation('Do you confirm generation [<comment>yes</comment>]? ', true)) {
                $io->writeError('<error>Command aborted</error>');

                return 1;
            }
        }

        $file->write($options);

        if ($input->isInteractive() && is_dir('.git')) {
            $ignoreFile = realpath('.gitignore');

            if (false === $ignoreFile) {
                $ignoreFile = realpath('.') . '/.gitignore';
            }

            if (!$this->hasVendorIgnore($ignoreFile)) {
                $question = 'Would you like the <info>vendor</info> directory added to your <info>.gitignore</info> [<comment>yes</comment>]? ';

                if ($io->askConfirmation($question, true)) {
                    $this->addVendorIgnore($ignoreFile);
                }
            }
        }

        $question = 'Would you like to install dependencies now [<comment>yes</comment>]? ';
        if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
            $this->installDependencies($output);
        }

        return 0;
    }

    /**
     * {@inheritdoc}
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $git = $this->getGitConfig();
        $io = $this->getIO();
        $formatter = $this->getHelperSet()->get('formatter');

        // initialize repos if configured
        $repositories = $input->getOption('repository');
        if ($repositories) {
            $config = Factory::createConfig($io);
            $repos = array(new PlatformRepository);
            $createDefaultPackagistRepo = true;
            foreach ($repositories as $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $config, $repo);
                if (
                    (isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false))
                    || (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
                ) {
                    $createDefaultPackagistRepo = false;
                    continue;
                }
                $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig);
            }

            if ($createDefaultPackagistRepo) {
                $repos[] = RepositoryFactory::createRepo($io, $config, array(
                    'type' => 'composer',
                    'url' => 'https://repo.packagist.org',
                ));
            }

            $this->repos = new CompositeRepository($repos);
            unset($repos, $config, $repositories);
        }

        $io->writeError(array(
            '',
            $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true),
            '',
        ));

        // namespace
        $io->writeError(array(
            '',
            'This command will guide you through creating your composer.json config.',
            '',
        ));

        $cwd = realpath(".");

        if (!$name = $input->getOption('name')) {
            $name = basename($cwd);
            $name = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
            $name = strtolower($name);
            if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) {
                $name = $_SERVER['COMPOSER_DEFAULT_VENDOR'] . '/' . $name;
            } elseif (isset($git['github.user'])) {
                $name = $git['github.user'] . '/' . $name;
            } elseif (!empty($_SERVER['USERNAME'])) {
                $name = $_SERVER['USERNAME'] . '/' . $name;
            } elseif (!empty($_SERVER['USER'])) {
                $name = $_SERVER['USER'] . '/' . $name;
            } elseif (get_current_user()) {
                $name = get_current_user() . '/' . $name;
            } else {
                // package names must be in the format foo/bar
                $name .= '/' . $name;
            }
            $name = strtolower($name);
        } else {
            if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $name)) {
                throw new \InvalidArgumentException(
                    'The package name '.$name.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
                );
            }
        }

        $name = $io->askAndValidate(
            'Package name (<vendor>/<name>) [<comment>'.$name.'</comment>]: ',
            function ($value) use ($name) {
                if (null === $value) {
                    return $name;
                }

                if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $value)) {
                    throw new \InvalidArgumentException(
                        'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
                    );
                }

                return $value;
            },
            null,
            $name
        );
        $input->setOption('name', $name);

        $description = $input->getOption('description') ?: false;
        $description = $io->ask(
            'Description [<comment>'.$description.'</comment>]: ',
            $description
        );
        $input->setOption('description', $description);

        if (null === $author = $input->getOption('author')) {
            if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) {
                $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR'];
            } elseif (isset($git['user.name'])) {
                $author_name = $git['user.name'];
            }

            if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) {
                $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL'];
            } elseif (isset($git['user.email'])) {
                $author_email = $git['user.email'];
            }

            if (isset($author_name) && isset($author_email)) {
                $author = sprintf('%s <%s>', $author_name, $author_email);
            }
        }

        $self = $this;
        $author = $io->askAndValidate(
            'Author [<comment>'.$author.'</comment>, n to skip]: ',
            function ($value) use ($self, $author) {
                if ($value === 'n' || $value === 'no') {
                    return;
                }
                $value = $value ?: $author;
                $author = $self->parseAuthorString($value);

                return sprintf('%s <%s>', $author['name'], $author['email']);
            },
            null,
            $author
        );
        $input->setOption('author', $author);

        $minimumStability = $input->getOption('stability') ?: null;
        $minimumStability = $io->askAndValidate(
            'Minimum Stability [<comment>'.$minimumStability.'</comment>]: ',
            function ($value) use ($minimumStability) {
                if (null === $value) {
                    return $minimumStability;
                }

                if (!isset(BasePackage::$stabilities[$value])) {
                    throw new \InvalidArgumentException(
                        'Invalid minimum stability "'.$value.'". Must be empty or one of: '.
                        implode(', ', array_keys(BasePackage::$stabilities))
                    );
                }

                return $value;
            },
            null,
            $minimumStability
        );
        $input->setOption('stability', $minimumStability);

        $type = $input->getOption('type') ?: false;
        $type = $io->ask(
            'Package Type (e.g. library, project, metapackage, composer-plugin) [<comment>'.$type.'</comment>]: ',
            $type
        );
        $input->setOption('type', $type);

        if (null === $license = $input->getOption('license')) {
            if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) {
                $license = $_SERVER['COMPOSER_DEFAULT_LICENSE'];
            }
        }

        $license = $io->ask(
            'License [<comment>'.$license.'</comment>]: ',
            $license
        );
        $input->setOption('license', $license);

        $io->writeError(array('', 'Define your dependencies.', ''));

        // prepare to resolve dependencies
        $repos = $this->getRepos();
        $preferredStability = $minimumStability ?: 'stable';
        $phpVersion = $repos->findPackage('php', '*')->getPrettyVersion();

        $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
        $require = $input->getOption('require');
        $requirements = array();
        if ($require || $io->askConfirmation($question, true)) {
            $requirements = $this->determineRequirements($input, $output, $require, $phpVersion, $preferredStability);
        }
        $input->setOption('require', $requirements);

        $question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ';
        $requireDev = $input->getOption('require-dev');
        $devRequirements = array();
        if ($requireDev || $io->askConfirmation($question, true)) {
            $devRequirements = $this->determineRequirements($input, $output, $requireDev, $phpVersion, $preferredStability);
        }
        $input->setOption('require-dev', $devRequirements);
    }

    /**
     * @private
     * @param  string $author
     * @return array
     */
    public function parseAuthorString($author)
    {
        if (preg_match('/^(?P<name>[- .,\p{L}\p{N}\p{Mn}\'’"()]+) <(?P<email>.+?)>$/u', $author, $match)) {
            if ($this->isValidEmail($match['email'])) {
                return array(
                    'name' => trim($match['name']),
                    'email' => $match['email'],
                );
            }
        }

        throw new \InvalidArgumentException(
            'Invalid author string.  Must be in the format: '.
            'John Smith <john@example.com>'
        );
    }

    protected function findPackages($name)
    {
        return $this->getRepos()->search($name);
    }

    protected function getRepos()
    {
        if (!$this->repos) {
            $this->repos = new CompositeRepository(array_merge(
                array(new PlatformRepository),
                RepositoryFactory::defaultRepos($this->getIO())
            ));
        }

        return $this->repos;
    }

    final protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true, $fixed = false)
    {
        if ($requires) {
            $requires = $this->normalizeRequirements($requires);
            $result = array();
            $io = $this->getIO();

            foreach ($requires as $requirement) {
                if (!isset($requirement['version'])) {
                    // determine the best version automatically
                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, null, null, $fixed);
                    $requirement['version'] = $version;

                    // replace package name from packagist.org
                    $requirement['name'] = $name;

                    $io->writeError(sprintf(
                        'Using version <info>%s</info> for <info>%s</info>',
                        $requirement['version'],
                        $requirement['name']
                    ));
                } else {
                    // check that the specified version/constraint exists before we proceed
                    list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);

                    // replace package name from packagist.org
                    $requirement['name'] = $name;
                }

                $result[] = $requirement['name'] . ' ' . $requirement['version'];
            }

            return $result;
        }

        $versionParser = new VersionParser();

        // Collect existing packages
        $composer = $this->getComposer(false);
        $installedRepo = $composer ? $composer->getRepositoryManager()->getLocalRepository() : null;
        $existingPackages = array();
        if ($installedRepo) {
            foreach ($installedRepo->getPackages() as $package) {
                $existingPackages[] = $package->getName();
            }
        }
        unset($composer, $installedRepo);

        $io = $this->getIO();
        while (null !== $package = $io->ask('Search for a package: ')) {
            $matches = $this->findPackages($package);

            if (count($matches)) {
                // Remove existing packages from search results.
                foreach ($matches as $position => $foundPackage) {
                    if (in_array($foundPackage['name'], $existingPackages, true)) {
                        unset($matches[$position]);
                    }
                }
                $matches = array_values($matches);

                $exactMatch = null;
                $choices = array();
                foreach ($matches as $position => $foundPackage) {
                    $abandoned = '';
                    if (isset($foundPackage['abandoned'])) {
                        if (is_string($foundPackage['abandoned'])) {
                            $replacement = sprintf('Use %s instead', $foundPackage['abandoned']);
                        } else {
                            $replacement = 'No replacement was suggested';
                        }
                        $abandoned = sprintf('<warning>Abandoned. %s.</warning>', $replacement);
                    }

                    $choices[] = sprintf(' <info>%5s</info> %s %s', "[$position]", $foundPackage['name'], $abandoned);
                    if ($foundPackage['name'] === $package) {
                        $exactMatch = true;
                        break;
                    }
                }

                // no match, prompt which to pick
                if (!$exactMatch) {
                    $io->writeError(array(
                        '',
                        sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
                        '',
                    ));

                    $io->writeError($choices);
                    $io->writeError('');

                    $validator = function ($selection) use ($matches, $versionParser) {
                        if ('' === $selection) {
                            return false;
                        }

                        if (is_numeric($selection) && isset($matches[(int) $selection])) {
                            $package = $matches[(int) $selection];

                            return $package['name'];
                        }

                        if (preg_match('{^\s*(?P<name>[\S/]+)(?:\s+(?P<version>\S+))?\s*$}', $selection, $packageMatches)) {
                            if (isset($packageMatches['version'])) {
                                // parsing `acme/example ~2.3`

                                // validate version constraint
                                $versionParser->parseConstraints($packageMatches['version']);

                                return $packageMatches['name'].' '.$packageMatches['version'];
                            }

                            // parsing `acme/example`
                            return $packageMatches['name'];
                        }

                        throw new \Exception('Not a valid selection');
                    };

                    $package = $io->askAndValidate(
                        'Enter package # to add, or the complete package name if it is not listed: ',
                        $validator,
                        3,
                        false
                    );
                }

                // no constraint yet, determine the best version automatically
                if (false !== $package && false === strpos($package, ' ')) {
                    $validator = function ($input) {
                        $input = trim($input);

                        return $input ?: false;
                    };

                    $constraint = $io->askAndValidate(
                        'Enter the version constraint to require (or leave blank to use the latest version): ',
                        $validator,
                        3,
                        false
                    );

                    if (false === $constraint) {
                        list($name, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $phpVersion, $preferredStability);

                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $constraint,
                            $package
                        ));
                    }

                    $package .= ' '.$constraint;
                }

                if (false !== $package) {
                    $requires[] = $package;
                    $existingPackages[] = substr($package, 0, strpos($package, ' '));
                }
            }
        }

        return $requires;
    }

    protected function formatAuthors($author)
    {
        return array($this->parseAuthorString($author));
    }

    protected function formatRequirements(array $requirements)
    {
        $requires = array();
        $requirements = $this->normalizeRequirements($requirements);
        foreach ($requirements as $requirement) {
            $requires[$requirement['name']] = $requirement['version'];
        }

        return $requires;
    }

    protected function getGitConfig()
    {
        if (null !== $this->gitConfig) {
            return $this->gitConfig;
        }

        $finder = new ExecutableFinder();
        $gitBin = $finder->find('git');

        // TODO in v3 always call with an array
        if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
            $cmd = new Process(array($gitBin, 'config', '-l'));
        } else {
            $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin)));
        }
        $cmd->run();

        if ($cmd->isSuccessful()) {
            $this->gitConfig = array();
            preg_match_all('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches, PREG_SET_ORDER);
            foreach ($matches as $match) {
                $this->gitConfig[$match[1]] = $match[2];
            }

            return $this->gitConfig;
        }

        return $this->gitConfig = array();
    }

    /**
     * Checks the local .gitignore file for the Composer vendor directory.
     *
     * Tested patterns include:
     *  "/$vendor"
     *  "$vendor"
     *  "$vendor/"
     *  "/$vendor/"
     *  "/$vendor/*"
     *  "$vendor/*"
     *
     * @param string $ignoreFile
     * @param string $vendor
     *
     * @return bool
     */
    protected function hasVendorIgnore($ignoreFile, $vendor = 'vendor')
    {
        if (!file_exists($ignoreFile)) {
            return false;
        }

        $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor));

        $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES);
        foreach ($lines as $line) {
            if (preg_match($pattern, $line)) {
                return true;
            }
        }

        return false;
    }

    protected function normalizeRequirements(array $requirements)
    {
        $parser = new VersionParser();

        return $parser->parseNameVersionPairs($requirements);
    }

    protected function addVendorIgnore($ignoreFile, $vendor = '/vendor/')
    {
        $contents = "";
        if (file_exists($ignoreFile)) {
            $contents = file_get_contents($ignoreFile);

            if ("\n" !== substr($contents, 0, -1)) {
                $contents .= "\n";
            }
        }

        file_put_contents($ignoreFile, $contents . $vendor. "\n");
    }

    protected function isValidEmail($email)
    {
        // assume it's valid if we can't validate it
        if (!function_exists('filter_var')) {
            return true;
        }

        // php <5.3.3 has a very broken email validator, so bypass checks
        if (PHP_VERSION_ID < 50303) {
            return true;
        }

        return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    private function getPool(InputInterface $input, $minimumStability = null)
    {
        $key = $minimumStability ?: 'default';

        if (!isset($this->pools[$key])) {
            $this->pools[$key] = $pool = new Pool($minimumStability ?: $this->getMinimumStability($input));
            $pool->addRepository($this->getRepos());
        }

        return $this->pools[$key];
    }

    private function getMinimumStability(InputInterface $input)
    {
        if ($input->hasOption('stability')) {
            return VersionParser::normalizeStability($input->getOption('stability') ?: 'stable');
        }

        $file = Factory::getComposerFile();
        if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
            if (!empty($composer['minimum-stability'])) {
                return VersionParser::normalizeStability($composer['minimum-stability']);
            }
        }

        return 'stable';
    }

    /**
     * Given a package name, this determines the best version to use in the require key.
     *
     * This returns a version with the ~ operator prefixed when possible.
     *
     * @param  InputInterface            $input
     * @param  string                    $name
     * @param  string|null               $phpVersion
     * @param  string                    $preferredStability
     * @param  string|null               $requiredVersion
     * @param  string                    $minimumStability
     * @param  bool                      $fixed
     * @throws \InvalidArgumentException
     * @return array                     name version
     */
    private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
    {
        // find the latest version allowed in this pool
        $versionSelector = new VersionSelector($this->getPool($input, $minimumStability));
        $ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');

        // ignore phpVersion if platform requirements are ignored
        if ($ignorePlatformReqs) {
            $phpVersion = null;
        }

        $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);

        if (!$package) {
            // platform packages can not be found in the pool in versions other than the local platform's has
            // so if platform reqs are ignored we just take the user's word for it
            if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
                return array($name, $requiredVersion ?: '*');
            }

            // Check whether the PHP version was the problem
            if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
                throw new \InvalidArgumentException(sprintf(
                    'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)',
                    $name,
                    $requiredVersion,
                    $phpVersion
                ));
            }
            // Check whether the required version was the problem
            if ($requiredVersion && $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability)) {
                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in a version matching %s',
                    $name,
                    $requiredVersion
                ));
            }
            // Check whether the PHP version was the problem
            if ($phpVersion && $versionSelector->findBestCandidate($name)) {
                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in any version matching your PHP version (%s)',
                    $name,
                    $phpVersion
                ));
            }

            // Check for similar names/typos
            $similar = $this->findSimilar($name);
            if ($similar) {
                // Check whether the minimum stability was the problem but the package exists
                if ($requiredVersion === null && in_array($name, $similar, true)) {
                    throw new \InvalidArgumentException(sprintf(
                        'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
                        $name,
                        $this->getMinimumStability($input)
                    ));
                }

                throw new \InvalidArgumentException(sprintf(
                    "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n    %s",
                    $name,
                    implode("\n    ", $similar)
                ));
            }

            throw new \InvalidArgumentException(sprintf(
                'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
                $name,
                $this->getMinimumStability($input)
            ));
        }

        return array(
            $package->getPrettyName(),
            $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
        );
    }

    private function findSimilar($package)
    {
        try {
            $results = $this->repos->search($package);
        } catch (\Exception $e) {
            // ignore search errors
            return array();
        }
        $similarPackages = array();

        $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();

        foreach ($results as $result) {
            if ($installedRepo->findPackage($result['name'], '*')) {
                // Ignore installed package
                continue;
            }
            $similarPackages[$result['name']] = levenshtein($package, $result['name']);
        }
        asort($similarPackages);

        return array_keys(array_slice($similarPackages, 0, 5));
    }

    private function installDependencies($output)
    {
        try {
            $installCommand = $this->getApplication()->find('install');
            $installCommand->run(new ArrayInput(array()), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not install dependencies. Run `composer install` to see more information.');
        }

    }

    private function hasDependencies($options)
    {
        $requires = (array) $options['require'];
        $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : array();

        return !empty($requires) || !empty($devRequires);
    }
}
<?php

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

namespace Composer\Command;

use Composer\Script\Event as ScriptEvent;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;

/**
 * @author Fabien Potencier <fabien.potencier@gmail.com>
 */
class RunScriptCommand extends BaseCommand
{
    /**
     * @var array Array with command events
     */
    protected $scriptEvents = array(
        ScriptEvents::PRE_INSTALL_CMD,
        ScriptEvents::POST_INSTALL_CMD,
        ScriptEvents::PRE_UPDATE_CMD,
        ScriptEvents::POST_UPDATE_CMD,
        ScriptEvents::PRE_STATUS_CMD,
        ScriptEvents::POST_STATUS_CMD,
        ScriptEvents::POST_ROOT_PACKAGE_INSTALL,
        ScriptEvents::POST_CREATE_PROJECT_CMD,
        ScriptEvents::PRE_ARCHIVE_CMD,
        ScriptEvents::POST_ARCHIVE_CMD,
        ScriptEvents::PRE_AUTOLOAD_DUMP,
        ScriptEvents::POST_AUTOLOAD_DUMP,
    );

    protected function configure()
    {
        $this
            ->setName('run-script')
            ->setAliases(array('run'))
            ->setDescription('Runs the scripts defined in composer.json.')
            ->setDefinition(array(
                new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
                new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'),
            ))
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if ($input->getOption('list')) {
            return $this->listScripts($output);
        } elseif (!$input->getArgument('script')) {
            throw new \RuntimeException('Missing required argument "script"');
        }

        $script = $input->getArgument('script');
        if (!in_array($script, $this->scriptEvents)) {
            if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script));
            }
        }

        $composer = $this->getComposer();
        $devMode = $input->getOption('dev') || !$input->getOption('no-dev');
        $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode);
        $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event);
        if (!$hasListeners) {
            throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script));
        }

        $args = $input->getArgument('args');

        if (null !== $timeout = $input->getOption('timeout')) {
            if (!ctype_digit($timeout)) {
                throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever');
            }
            // Override global timeout set before in Composer by environment or config
            ProcessExecutor::setTimeout((int) $timeout);
        }

        return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args);
    }

    protected function listScripts(OutputInterface $output)
    {
        $scripts = $this->getComposer()->getPackage()->getScripts();

        if (!count($scripts)) {
            return 0;
        }

        $io = $this->getIO();
        $io->writeError('<info>scripts:</info>');
        $table = array();
        foreach ($scripts as $name => $script) {
            $description = '';
            try {
                $cmd = $this->getApplication()->find($name);
                if ($cmd instanceof ScriptAliasCommand) {
                    $description = $cmd->getDescription();
                }
            } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) {
                // ignore scripts that have no command associated, like native Composer script listeners
            }
            $table[] = array('  '.$name, $description);
        }

        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        if (method_exists($rendererStyle, 'setVerticalBorderChars')) {
            $rendererStyle->setVerticalBorderChars('');
        } else {
            $rendererStyle->setVerticalBorderChar('');
        }
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\IOInterface;
use Composer\Downloader\FilesystemException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Kevin Ran <kran@adobe.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class SelfUpdateCommand extends BaseCommand
{
    const HOMEPAGE = 'getcomposer.org';
    const OLD_INSTALL_EXT = '-old.phar';

    protected function configure()
    {
        $this
            ->setName('self-update')
            ->setAliases(array('selfupdate'))
            ->setDescription('Updates composer.phar to the latest version.')
            ->setDefinition(array(
                new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
                new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
                new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'),
                new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'),
                new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'),
                new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'),
                new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'),
                new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'),
                new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'),
            ))
            ->setHelp(
                <<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
versions of composer and if found, installs the latest.

<info>php composer.phar self-update</info>

Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate-
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();

        if ($config->get('disable-tls') === true) {
            $baseUrl = 'http://' . self::HOMEPAGE;
        } else {
            $baseUrl = 'https://' . self::HOMEPAGE;
        }

        $io = $this->getIO();
        $remoteFilesystem = Factory::createRemoteFilesystem($io, $config);

        $versionsUtil = new Versions($config, $remoteFilesystem);

        // switch channel if requested
        $requestedChannel = null;
        foreach (Versions::$channels as $channel) {
            if ($input->getOption($channel)) {
                $requestedChannel = $channel;
                $versionsUtil->setChannel($channel);
                break;
            }
        }

        if ($input->getOption('set-channel-only')) {
            return 0;
        }

        $cacheDir = $config->get('cache-dir');
        $rollbackDir = $config->get('data-dir');
        $home = $config->get('home');
        $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];

        if ($input->getOption('update-keys')) {
            return $this->fetchKeys($io, $config);
        }

        // ensure composer.phar location is accessible
        if (!file_exists($localFilename)) {
            throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible');
        }

        // check if current dir is writable and if not try the cache dir from settings
        $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;

        // check for permissions in local filesystem before start connection process
        if (!is_writable($tmpDir)) {
            throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
        }

        // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $composeUser = posix_getpwuid(posix_geteuid());
            $homeOwner = posix_getpwuid(fileowner($home));
            if (isset($composeUser['name']) && isset($homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) {
                $io->writeError('<warning>You are running composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
            }
        }

        if ($input->getOption('rollback')) {
            return $this->rollback($output, $rollbackDir, $localFilename);
        }

        $latest = $versionsUtil->getLatest();
        $latestStable = $versionsUtil->getLatest('stable');
        try {
            $latestPreview = $versionsUtil->getLatest('preview');
        } catch (\UnexpectedValueException $e) {
            $latestPreview = $latestStable;
        }
        $latestVersion = $latest['version'];
        $updateVersion = $input->getArgument('version') ?: $latestVersion;
        $currentMajorVersion = preg_replace('{^(\d+).*}', '$1', Composer::getVersion());
        $updateMajorVersion = preg_replace('{^(\d+).*}', '$1', $updateVersion);
        $previewMajorVersion = preg_replace('{^(\d+).*}', '$1', $latestPreview['version']);

        if ($versionsUtil->getChannel() === 'stable' && !$input->getArgument('version')) {
            // if requesting stable channel and no specific version, avoid automatically upgrading to the next major
            // simply output a warning that the next major stable is available and let users upgrade to it manually
            if ($currentMajorVersion < $updateMajorVersion) {
                $skippedVersion = $updateVersion;

                $versionsUtil->setChannel($currentMajorVersion);

                $latest = $versionsUtil->getLatest();
                $latestStable = $versionsUtil->getLatest('stable');
                $latestVersion = $latest['version'];
                $updateVersion = $latestVersion;

                $io->writeError('<warning>A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.'</warning>');
            } elseif ($currentMajorVersion < $previewMajorVersion) {
                // promote next major version if available in preview
                $io->writeError('<warning>A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>');
            }
        }

        if ($requestedChannel && is_numeric($requestedChannel) && substr($latestStable['version'], 0, 1) !== $requestedChannel) {
            $io->writeError('<warning>Warning: You forced the install of '.$latestVersion.' via --'.$requestedChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
        }

        if (preg_match('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) {
            $io->writeError('<error>You can not update to a specific SHA-1 as those phars are not available for download</error>');

            return 1;
        }

        $channelString = $versionsUtil->getChannel();
        if (is_numeric($channelString)) {
            $channelString .= '.x';
        }

        if (Composer::VERSION === $updateVersion) {
            $io->writeError(
                sprintf(
                    '<info>You are already using composer version %s (%s channel).</info>',
                    $updateVersion,
                    $channelString
                )
            );

            // remove all backups except for the most recent, if any
            if ($input->getOption('clean-backups')) {
                $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir));
            }

            return 0;
        }

        $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp.phar';
        $backupFile = sprintf(
            '%s/%s-%s%s',
            $rollbackDir,
            strtr(Composer::RELEASE_DATE, ' :', '_-'),
            preg_replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION),
            self::OLD_INSTALL_EXT
        );

        $updatingToTag = !preg_match('{^[0-9a-f]{40}$}', $updateVersion);

        $io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $channelString));
        $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
        $signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false);
        $io->writeError('   ', false);
        $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
        $io->writeError('');

        if (!file_exists($tempFilename) || !$signature) {
            $io->writeError('<error>The download of the new composer version failed for an unexpected reason</error>');

            return 1;
        }

        // verify phar signature
        if (!extension_loaded('openssl') && $config->get('disable-tls')) {
            $io->writeError('<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>');
        } else {
            if (!extension_loaded('openssl')) {
                throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
            }

            $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub');
            if (!file_exists($sigFile)) {
                file_put_contents(
                    $home.'/keys.dev.pub',
                    <<<DEVPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
-----END PUBLIC KEY-----
DEVPUBKEY
                );

                file_put_contents(
                    $home.'/keys.tags.pub',
                    <<<TAGSPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
-----END PUBLIC KEY-----
TAGSPUBKEY
                );
            }

            $pubkeyid = openssl_pkey_get_public($sigFile);
            $algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
            if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) {
                throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
            }
            $signature = json_decode($signature, true);
            $signature = base64_decode($signature['sha384']);
            $verified = 1 === openssl_verify(file_get_contents($tempFilename), $signature, $pubkeyid, $algo);

            // PHP 8 automatically frees the key instance and deprecates the function
            if (PHP_VERSION_ID < 80000) {
                openssl_free_key($pubkeyid);
            }

            if (!$verified) {
                throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
            }
        }

        // remove saved installations of composer
        if ($input->getOption('clean-backups')) {
            $this->cleanBackups($rollbackDir);
        }

        if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
            @unlink($tempFilename);

            return 1;
        }

        if (file_exists($backupFile)) {
            $io->writeError(sprintf(
                'Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>',
                Composer::VERSION
            ));
        } else {
            $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
        }

        return 0;
    }

    protected function fetchKeys(IOInterface $io, Config $config)
    {
        if (!$io->isInteractive()) {
            throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively');
        }

        $io->write('Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys');

        $validator = function ($value) {
            if (!preg_match('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) {
                throw new \UnexpectedValueException('Invalid input');
            }

            return trim($value)."\n";
        };

        $devKey = '';
        while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) {
            $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('')) {
                $devKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $tagsKey = '';
        while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) {
            $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('')) {
                $tagsKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $io->write('Public keys stored in '.$config->get('home'));
    }

    protected function rollback(OutputInterface $output, $rollbackDir, $localFilename)
    {
        $rollbackVersion = $this->getLastBackupVersion($rollbackDir);
        if (!$rollbackVersion) {
            throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
        }

        $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;

        if (!is_file($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found');
        }
        if (!is_readable($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read');
        }

        $io = $this->getIO();
        $io->writeError(sprintf("Rolling back to version <info>%s</info>.", $rollbackVersion));
        if (!$this->setLocalPhar($localFilename, $oldFile)) {
            return 1;
        }

        return 0;
    }

    /**
     * Checks if the downloaded/rollback phar is valid then moves it
     *
     * @param  string $localFilename The composer.phar location
     * @param  string $newFilename The downloaded or backup phar
     * @param  string $backupTarget The filename to use for the backup
     * @throws \FilesystemException If the file cannot be moved
     * @return bool Whether the phar is valid and has been moved
     */
    protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
    {
        $io = $this->getIO();
        @chmod($newFilename, fileperms($localFilename));

        // check phar validity
        if (!$this->validatePhar($newFilename, $error)) {
            $io->writeError('<error>The '.($backupTarget ? 'update' : 'backup').' file is corrupted ('.$error.')</error>');

            if ($backupTarget) {
                $io->writeError('<error>Please re-run the self-update command to try again.</error>');
            }

            return false;
        }

        // copy current file into backups dir
        if ($backupTarget) {
            @copy($localFilename, $backupTarget);
        }

        try {
            rename($newFilename, $localFilename);

            return true;
        } catch (\Exception $e) {
            // see if we can run this operation as an Admin on Windows
            if (!is_writable(dirname($localFilename))
                && $io->isInteractive()
                && $this->isWindowsNonAdminUser()) {
                return $this->tryAsWindowsAdmin($localFilename, $newFilename);
            }

            $action = 'Composer '.($backupTarget ? 'update' : 'rollback');
            throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage());
        }
    }

    protected function cleanBackups($rollbackDir, $except = null)
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $io = $this->getIO();
        $fs = new Filesystem;

        foreach ($finder as $file) {
            if ($except && $file->getBasename(self::OLD_INSTALL_EXT) === $except) {
                continue;
            }
            $file = (string) $file;
            $io->writeError('<info>Removing: '.$file.'</info>');
            $fs->remove($file);
        }
    }

    protected function getLastBackupVersion($rollbackDir)
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $finder->sortByName();
        $files = iterator_to_array($finder);

        if (count($files)) {
            return basename(end($files), self::OLD_INSTALL_EXT);
        }

        return false;
    }

    protected function getOldInstallationFinder($rollbackDir)
    {
        $finder = Finder::create()
            ->depth(0)
            ->files()
            ->name('*' . self::OLD_INSTALL_EXT)
            ->in($rollbackDir);

        return $finder;
    }

    /**
     * Validates the downloaded/backup phar file
     *
     * @param string $pharFile The downloaded or backup phar
     * @param null|string $error Set by method on failure
     *
     * Code taken from getcomposer.org/installer. Any changes should be made
     * there and replicated here
     *
     * @return bool If the operation succeeded
     * @throws \Exception
     */
    protected function validatePhar($pharFile, &$error)
    {
        if (ini_get('phar.readonly')) {
            return true;
        }

        try {
            // Test the phar validity
            $phar = new \Phar($pharFile);
            // Free the variable to unlock the file
            unset($phar);
            $result = true;
        } catch (\Exception $e) {
            if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
                throw $e;
            }
            $error = $e->getMessage();
            $result = false;
        }

        return $result;
    }

    /**
     * Returns true if this is a non-admin Windows user account
     *
     * @return bool
     */
    protected function isWindowsNonAdminUser()
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // fltmc.exe manages filter drivers and errors without admin privileges
        exec('fltmc.exe filters', $output, $exitCode);

        return $exitCode !== 0;
    }

    /**
     * Invokes a UAC prompt to update composer.phar as an admin
     *
     * Uses a .vbs script to elevate and run the cmd.exe move command.
     *
     * @param string $localFilename The composer.phar location
     * @param string $newFilename The downloaded or backup phar
     * @return bool Whether composer.phar has been updated
     */
    protected function tryAsWindowsAdmin($localFilename, $newFilename)
    {
        $io = $this->getIO();

        $io->writeError('<error>Unable to write "'.$localFilename.'". Access is denied.</error>');
        $helpMessage = 'Please run the self-update command as an Administrator.';
        $question = 'Complete this operation with Administrator privileges [<comment>Y,n</comment>]? ';

        if (!$io->askConfirmation($question, false)) {
            $io->writeError('<warning>Operation cancelled. '.$helpMessage.'</warning>');

            return false;
        }

        $tmpFile = tempnam(sys_get_temp_dir(), '');
        $script = $tmpFile.'.vbs';
        rename($tmpFile, $script);

        $checksum = hash_file('sha256', $newFilename);

        // cmd's internal move is fussy about backslashes
        $source = str_replace('/', '\\', $newFilename);
        $destination = str_replace('/', '\\', $localFilename);

        $vbs = <<<EOT
Set UAC = CreateObject("Shell.Application")
UAC.ShellExecute "cmd.exe", "/c move /y ""$source"" ""$destination""", "", "runas", 0
Wscript.Sleep(300)
EOT;

        file_put_contents($script, $vbs);
        exec('"'.$script.'"');
        @unlink($script);

        // see if the file was moved
        if ($result = (hash_file('sha256', $localFilename) === $checksum)) {
            $io->writeError('<info>Operation succeeded.</info>');
        } else {
            $io->writeError('<error>Operation failed (file not written). '.$helpMessage.'</error>');
        };

        return $result;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\ArrayRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositoryInterface;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Semver;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\Platform;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jérémy Romey <jeremyFreeAgent>
 * @author Mihai Plasoianu <mihai@plasoianu.de>
 */
class ShowCommand extends BaseCommand
{
    /** @var VersionParser */
    protected $versionParser;
    protected $colors;

    /** @var Pool */
    private $pool;

    protected function configure()
    {
        $this
            ->setName('show')
            ->setAliases(array('info'))
            ->setDescription('Shows information about packages.')
            ->setDefinition(array(
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'),
                new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'),
                new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'),
                new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'),
                new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
                new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
                new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
                new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
                new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'),
                new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
            ))
            ->setHelp(
                <<<EOT
The show command displays detailed information about a package, or
lists all packages available.

Read more at https://getcomposer.org/doc/03-cli.md#show
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->versionParser = new VersionParser;
        if ($input->getOption('tree')) {
            $this->initStyles($output);
        }

        $composer = $this->getComposer(false);
        $io = $this->getIO();

        if ($input->getOption('installed')) {
            $io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>');
        }

        if ($input->getOption('outdated')) {
            $input->setOption('latest', true);
        } elseif ($input->getOption('ignore')) {
            $io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>');
        }

        if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) {
            $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)');

            return 1;
        }

        if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) {
            $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('latest')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('path')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');

            return 1;
        }

        $format = $input->getOption('format');
        if (!in_array($format, array('text', 'json'))) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        // init repos
        $platformOverrides = array();
        if ($composer) {
            $platformOverrides = $composer->getConfig()->get('platform') ?: array();
        }
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $phpVersion = $platformRepo->findPackage('php', '*')->getVersion();

        if ($input->getOption('self')) {
            $package = $this->getComposer()->getPackage();
            if ($input->getOption('name-only')) {
                $io->write($package->getName());
                return 0;
            }
            $repos = $installedRepo = new ArrayRepository(array($package));
        } elseif ($input->getOption('platform')) {
            $repos = $installedRepo = $platformRepo;
        } elseif ($input->getOption('available')) {
            $installedRepo = $platformRepo;
            if ($composer) {
                $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
            } else {
                $defaultRepos = RepositoryFactory::defaultRepos($io);
                $repos = new CompositeRepository($defaultRepos);
                $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            }
        } elseif ($input->getOption('all') && $composer) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $installedRepo = new CompositeRepository(array($localRepo, $platformRepo));
            $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories()));
        } elseif ($input->getOption('all')) {
            $defaultRepos = RepositoryFactory::defaultRepos($io);
            $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            $installedRepo = $platformRepo;
            $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
        } else {
            $repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
            $rootPkg = $this->getComposer()->getPackage();
            if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
                $io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
            }
        }

        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
        }

        if ($input->getOption('latest') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "latest" option');
            $input->setOption('latest', false);
        }

        $packageFilter = $input->getArgument('package');

        // show single package or single version
        if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) {
            if (empty($package)) {
                list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));

                if (empty($package)) {
                    $options = $input->getOptions();
                    if (!isset($options['working-dir']) || !file_exists('composer.json')) {
                        if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $input->getArgument('package')) && !$input->getOption('platform')) {
                            throw new \InvalidArgumentException('Package ' . $packageFilter . ' not found, try using --platform (-p) to show platform packages.');
                        }
                        throw new \InvalidArgumentException('Package ' . $packageFilter . ' not found');
                    }

                    $io->writeError('Package ' . $packageFilter . ' not found in ' . $options['working-dir'] . '/composer.json');

                    return 1;
                }
            } else {
                $versions = array($package->getPrettyVersion() => $package->getVersion());
            }

            $exitCode = 0;
            if ($input->getOption('tree')) {
                $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos);

                if ('json' === $format) {
                    $io->write(JsonFile::encode(array('installed' => array($arrayTree))));
                } else {
                    $this->displayPackageTree(array($arrayTree));
                }
            } else {
                $latestPackage = null;
                if ($input->getOption('latest')) {
                    $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion);
                }
                if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) {
                    $exitCode = 1;
                }
                if ($input->getOption('path')) {
                    $io->write($package->getName(), false);
                    $io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"));

                    return $exitCode;
                }

                if ('json' === $format) {
                    $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null);
                } else {
                    $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
                }
            }

            return $exitCode;
        }

        // show tree view if requested
        if ($input->getOption('tree')) {
            $rootRequires = $this->getRootRequires();
            $packages = $installedRepo->getPackages();
            usort($packages, 'strcmp');
            $arrayTree = array();
            foreach ($packages as $package) {
                if (in_array($package->getName(), $rootRequires, true)) {
                    $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos);
                }
            }

            if ('json' === $format) {
                $io->write(JsonFile::encode(array('installed' => $arrayTree)));
            } else {
                $this->displayPackageTree($arrayTree);
            }

            return 0;
        }

        if ($repos instanceof CompositeRepository) {
            $repos = $repos->getRepositories();
        } elseif (!is_array($repos)) {
            $repos = array($repos);
        }

        // list packages
        $packages = array();
        if (null !== $packageFilter) {
            $packageFilter = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i';
        }

        $packageListFilter = array();
        if ($input->getOption('direct')) {
            $packageListFilter = $this->getRootRequires();
        }

        if (class_exists('Symfony\Component\Console\Terminal')) {
            $terminal = new Terminal();
            $width = $terminal->getWidth();
        } else {
            // For versions of Symfony console before 3.2
            list($width) = $this->getApplication()->getTerminalDimensions();
        }
        if (null === $width) {
            // In case the width is not detected, we're probably running the command
            // outside of a real terminal, use space without a limit
            $width = PHP_INT_MAX;
        }
        if (Platform::isWindows()) {
            $width--;
        } else {
            $width = max(80, $width);
        }

        if ($input->getOption('path') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "path" option');
            $input->setOption('path', false);
        }

        foreach ($repos as $repo) {
            if ($repo === $platformRepo) {
                $type = 'platform';
            } elseif (
                $repo === $installedRepo
                || ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
            ) {
                $type = 'installed';
            } else {
                $type = 'available';
            }
            if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
                foreach ($repo->getProviderNames() as $name) {
                    if (!$packageFilter || preg_match($packageFilter, $name)) {
                        $packages[$type][$name] = $name;
                    }
                }
            } else {
                foreach ($repo->getPackages() as $package) {
                    if (!isset($packages[$type][$package->getName()])
                        || !is_object($packages[$type][$package->getName()])
                        || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
                    ) {
                        if (!$packageFilter || preg_match($packageFilter, $package->getName())) {
                            if (!$packageListFilter || in_array($package->getName(), $packageListFilter, true)) {
                                $packages[$type][$package->getName()] = $package;
                            }
                        }
                    }
                }
            }
        }

        $showAllTypes = $input->getOption('all');
        $showLatest = $input->getOption('latest');
        $showMinorOnly = $input->getOption('minor-only');
        $ignoredPackages = array_map('strtolower', $input->getOption('ignore'));
        $indent = $showAllTypes ? '  ' : '';
        $latestPackages = array();
        $exitCode = 0;
        $viewData = array();
        $viewMetaData = array();
        foreach (array('platform' => true, 'available' => false, 'installed' => true) as $type => $showVersion) {
            if (isset($packages[$type])) {
                ksort($packages[$type]);

                $nameLength = $versionLength = $latestLength = 0;

                if ($showLatest && $showVersion) {
                    foreach ($packages[$type] as $package) {
                        if (is_object($package)) {
                            $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly);
                            if ($latestPackage === false) {
                                continue;
                            }

                            $latestPackages[$package->getPrettyName()] = $latestPackage;
                        }
                    }
                }

                $writePath = !$input->getOption('name-only') && $input->getOption('path');
                $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
                $writeLatest = $writeVersion && $showLatest;
                $writeDescription = !$input->getOption('name-only') && !$input->getOption('path');

                $hasOutdatedPackages = false;

                $viewData[$type] = array();
                foreach ($packages[$type] as $package) {
                    $packageViewData = array();
                    if (is_object($package)) {
                        $latestPackage = null;
                        if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
                            $latestPackage = $latestPackages[$package->getPrettyName()];
                        }

                        // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
                        $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned();
                        $packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true);
                        if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
                            continue;
                        } elseif ($input->getOption('outdated') || $input->getOption('strict')) {
                            $hasOutdatedPackages = true;
                        }

                        $packageViewData['name'] = $package->getPrettyName();
                        $nameLength = max($nameLength, strlen($package->getPrettyName()));
                        if ($writeVersion) {
                            $packageViewData['version'] = $package->getFullPrettyVersion();
                            $versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
                        }
                        if ($writeLatest && $latestPackage) {
                            $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
                            $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
                            $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion()));
                        }
                        if ($writeDescription) {
                            $packageViewData['description'] = $package->getDescription();
                        }
                        if ($writePath) {
                            $packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
                        }

                        if ($latestPackage && $latestPackage->isAbandoned()) {
                            $replacement = is_string($latestPackage->getReplacementPackage())
                                ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
                                : 'No replacement was suggested';
                            $packageWarning = sprintf(
                                'Package %s is abandoned, you should avoid using it. %s.',
                                $package->getPrettyName(),
                                $replacement
                            );
                            $packageViewData['warning'] = $packageWarning;
                        }
                    } else {
                        $packageViewData['name'] = $package;
                        $nameLength = max($nameLength, strlen($package));
                    }
                    $viewData[$type][] = $packageViewData;
                }
                $viewMetaData[$type] = array(
                    'nameLength' => $nameLength,
                    'versionLength' => $versionLength,
                    'latestLength' => $latestLength,
                );
                if ($input->getOption('strict') && $hasOutdatedPackages) {
                    $exitCode = 1;
                    break;
                }
            }
        }

        if ('json' === $format) {
            $io->write(JsonFile::encode($viewData));
        } else {
            foreach ($viewData as $type => $packages) {
                $nameLength = $viewMetaData[$type]['nameLength'];
                $versionLength = $viewMetaData[$type]['versionLength'];
                $latestLength = $viewMetaData[$type]['latestLength'];

                $writeVersion = $nameLength + $versionLength + 3 <= $width;
                $writeLatest = $nameLength + $versionLength + $latestLength + 3 <= $width;
                $writeDescription = $nameLength + $versionLength + $latestLength + 24 <= $width;

                if ($writeLatest && !$io->isDecorated()) {
                    $latestLength += 2;
                }

                if ($showAllTypes) {
                    if ('available' === $type) {
                        $io->write('<comment>' . $type . '</comment>:');
                    } else {
                        $io->write('<info>' . $type . '</info>:');
                    }
                }

                foreach ($packages as $package) {
                    $io->write($indent . str_pad($package['name'], $nameLength, ' '), false);
                    if (isset($package['version']) && $writeVersion) {
                        $io->write(' ' . str_pad($package['version'], $versionLength, ' '), false);
                    }
                    if (isset($package['latest']) && $writeLatest) {
                        $latestVersion = $package['latest'];
                        $updateStatus = $package['latest-status'];
                        $style = $this->updateStatusToVersionStyle($updateStatus);
                        if (!$io->isDecorated()) {
                            $latestVersion = str_replace(array('up-to-date', 'semver-safe-update', 'update-possible'), array('=', '!', '~'), $updateStatus) . ' ' . $latestVersion;
                        }
                        $io->write(' <' . $style . '>' . str_pad($latestVersion, $latestLength, ' ') . '</' . $style . '>', false);
                    }
                    if (isset($package['description']) && $writeDescription) {
                        $description = strtok($package['description'], "\r\n");
                        $remaining = $width - $nameLength - $versionLength - 4;
                        if ($writeLatest) {
                            $remaining -= $latestLength;
                        }
                        if (strlen($description) > $remaining) {
                            $description = substr($description, 0, $remaining - 3) . '...';
                        }
                        $io->write(' ' . $description, false);
                    }
                    if (isset($package['path'])) {
                        $io->write(' ' . $package['path'], false);
                    }
                    $io->write('');
                    if (isset($package['warning'])) {
                        $io->write('<warning>' . $package['warning'] . '</warning>');
                    }
                }

                if ($showAllTypes) {
                    $io->write('');
                }
            }
        }

        return $exitCode;
    }

    protected function getRootRequires()
    {
        $rootPackage = $this->getComposer()->getPackage();

        return array_map(
            'strtolower',
            array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))
        );
    }

    protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
    {
        return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
    }

    /**
     * finds a package by name and version if provided
     *
     * @param  RepositoryInterface        $installedRepo
     * @param  RepositoryInterface        $repos
     * @param  string                     $name
     * @param  ConstraintInterface|string $version
     * @throws \InvalidArgumentException
     * @return array                      array(CompletePackageInterface, array of versions)
     */
    protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null)
    {
        $name = strtolower($name);
        $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;

        $policy = new DefaultPolicy();
        $pool = new Pool('dev');
        $pool->addRepository($repos);

        $matchedPackage = null;
        $versions = array();
        $matches = $pool->whatProvides($name, $constraint);
        foreach ($matches as $index => $package) {
            // skip providers/replacers
            if ($package->getName() !== $name) {
                unset($matches[$index]);
                continue;
            }

            // select an exact match if it is in the installed repo and no specific version was required
            if (null === $version && $installedRepo->hasPackage($package)) {
                $matchedPackage = $package;
            }

            $versions[$package->getPrettyVersion()] = $package->getVersion();
            $matches[$index] = $package->getId();
        }

        // select preferred package according to policy rules
        if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) {
            $matchedPackage = $pool->literalToPackage($preferred[0]);
        }

        return array($matchedPackage, $versions);
    }

    /**
     * Prints package info.
     *
     * @param CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     * @param PackageInterface|null    $latestPackage
     */
    protected function printPackageInfo(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
    {
        $io = $this->getIO();

        $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null);
        $this->printLinks($package, 'requires');
        $this->printLinks($package, 'devRequires', 'requires (dev)');

        if ($package->getSuggests()) {
            $io->write("\n<info>suggests</info>");
            foreach ($package->getSuggests() as $suggested => $reason) {
                $io->write($suggested . ' <comment>' . $reason . '</comment>');
            }
        }

        $this->printLinks($package, 'provides');
        $this->printLinks($package, 'conflicts');
        $this->printLinks($package, 'replaces');
    }

    /**
     * Prints package metadata.
     *
     * @param CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     * @param PackageInterface|null    $latestPackage
     */
    protected function printMeta(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
    {
        $io = $this->getIO();
        $io->write('<info>name</info>     : ' . $package->getPrettyName());
        $io->write('<info>descrip.</info> : ' . $package->getDescription());
        $io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: array()));
        $this->printVersions($package, $versions, $installedRepo);
        if ($latestPackage) {
            $style = $this->getVersionStyle($latestPackage, $package);
            $io->write('<info>latest</info>   : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>');
        } else {
            $latestPackage = $package;
        }
        $io->write('<info>type</info>     : ' . $package->getType());
        $this->printLicenses($package);
        $io->write('<info>homepage</info> : ' . $package->getHomepage());
        $io->write('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
        $io->write('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
        if ($installedRepo->hasPackage($package)) {
            $io->write('<info>path</info>     : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package))));
        }
        $io->write('<info>names</info>    : ' . implode(', ', $package->getNames()));

        if ($latestPackage->isAbandoned()) {
            $replacement = ($latestPackage->getReplacementPackage() !== null)
                ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.'
                : null;

            $io->writeError(
                sprintf('<warning>Attention: This package is abandoned and no longer maintained.%s</warning>', $replacement)
            );
        }

        if ($package->getSupport()) {
            $io->write("\n<info>support</info>");
            foreach ($package->getSupport() as $type => $value) {
                $io->write('<comment>' . $type . '</comment> : '.$value);
            }
        }

        if ($package->getAutoload()) {
            $io->write("\n<info>autoload</info>");
            foreach ($package->getAutoload() as $type => $autoloads) {
                $io->write('<comment>' . $type . '</comment>');

                if ($type === 'psr-0') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'psr-4') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'classmap') {
                    $io->write(implode(', ', $autoloads));
                }
            }
            if ($package->getIncludePaths()) {
                $io->write('<comment>include-path</comment>');
                $io->write(implode(', ', $package->getIncludePaths()));
            }
        }
    }

    /**
     * Prints all available versions of this package and highlights the installed one if any.
     *
     * @param CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     */
    protected function printVersions(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo)
    {
        uasort($versions, 'version_compare');
        $versions = array_keys(array_reverse($versions));

        // highlight installed version
        if ($installedRepo->hasPackage($package)) {
            $installedVersion = $package->getPrettyVersion();
            $key = array_search($installedVersion, $versions);
            if (false !== $key) {
                $versions[$key] = '<info>* ' . $installedVersion . '</info>';
            }
        }

        $versions = implode(', ', $versions);

        $this->getIO()->write('<info>versions</info> : ' . $versions);
    }

    /**
     * print link objects
     *
     * @param CompletePackageInterface $package
     * @param string                   $linkType
     * @param string                   $title
     */
    protected function printLinks(CompletePackageInterface $package, $linkType, $title = null)
    {
        $title = $title ?: $linkType;
        $io = $this->getIO();
        if ($links = $package->{'get'.ucfirst($linkType)}()) {
            $io->write("\n<info>" . $title . "</info>");

            foreach ($links as $link) {
                $io->write($link->getTarget() . ' <comment>' . $link->getPrettyConstraint() . '</comment>');
            }
        }
    }

    /**
     * Prints the licenses of a package with metadata
     *
     * @param CompletePackageInterface $package
     */
    protected function printLicenses(CompletePackageInterface $package)
    {
        $spdxLicenses = new SpdxLicenses();

        $licenses = $package->getLicense();
        $io = $this->getIO();

        foreach ($licenses as $licenseId) {
            $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

            if (!$license) {
                $out = $licenseId;
            } else {
                // is license OSI approved?
                if ($license[1] === true) {
                    $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]);
                } else {
                    $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]);
                }
            }

            $io->write('<info>license</info>  : ' . $out);
        }
    }

    /**
     * Prints package info in JSON format.
     *
     * @param CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     * @param PackageInterface|null    $latestPackage
     */
    protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
    {
        $json = array(
            'name' => $package->getPrettyName(),
            'description' => $package->getDescription(),
            'keywords' => $package->getKeywords() ?: array(),
            'type' => $package->getType(),
            'homepage' => $package->getHomepage(),
            'names' => $package->getNames()
        );

        $json = $this->appendVersions($json, $versions);
        $json = $this->appendLicenses($json, $package);

        if ($latestPackage) {
            $json['latest'] = $latestPackage->getPrettyVersion();
        } else {
            $latestPackage = $package;
        }

        if ($package->getSourceType()) {
            $json['source'] = array(
                'type' => $package->getSourceType(),
                'url' => $package->getSourceUrl(),
                'reference' => $package->getSourceReference()
            );
        }

        if ($package->getDistType()) {
            $json['dist'] = array(
                'type' => $package->getDistType(),
                'url' => $package->getDistUrl(),
                'reference' => $package->getDistReference()
            );
        }

        if ($installedRepo->hasPackage($package)) {
            $json['path'] = realpath($this->getComposer()->getInstallationManager()->getInstallPath($package));
            if ($json['path'] === false) {
                unset($json['path']);
            }
        }

        if ($latestPackage->isAbandoned()) {
            $json['replacement'] = $latestPackage->getReplacementPackage();
        }

        if ($package->getSuggests()) {
            $json['suggests'] = $package->getSuggests();
        }

        if ($package->getSupport()) {
            $json['support'] = $package->getSupport();
        }

        $json = $this->appendAutoload($json, $package);

        if ($package->getIncludePaths()) {
            $json['include_path'] = $package->getIncludePaths();
        }

        $json = $this->appendLinks($json, $package);

        $this->getIO()->write(JsonFile::encode($json));
    }

    private function appendVersions($json, array $versions)
    {
        uasort($versions, 'version_compare');
        $versions = array_keys(array_reverse($versions));
        $json['versions'] = $versions;

        return $json;
    }

    private function appendLicenses($json, CompletePackageInterface $package)
    {
        if ($licenses = $package->getLicense()) {
            $spdxLicenses = new SpdxLicenses();

            $json['licenses'] = array_map(function ($licenseId) use ($spdxLicenses) {
                $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

                if (!$license) {
                    return $licenseId;
                }

                return array(
                    'name' => $license[0],
                    'osi' => $licenseId,
                    'url' => $license[2]
                );
            }, $licenses);
        }

        return $json;
    }

    private function appendAutoload($json, CompletePackageInterface $package)
    {
        if ($package->getAutoload()) {
            $autoload = array();

            foreach ($package->getAutoload() as $type => $autoloads) {
                if ($type === 'psr-0' || $type === 'psr-4') {
                    $psr = array();

                    foreach ($autoloads as $name => $path) {
                        if (!$path) {
                            $path = '.';
                        }

                        $psr[$name ?: '*'] = $path;
                    }

                    $autoload[$type] = $psr;
                } elseif ($type === 'classmap') {
                    $autoload['classmap'] = $autoloads;
                }
            }

            $json['autoload'] = $autoload;
        }

        return $json;
    }

    private function appendLinks($json, CompletePackageInterface $package)
    {
        foreach (array('requires', 'devRequires', 'provides', 'conflicts', 'replaces') as $linkType) {
            $json = $this->appendLink($json, $package, $linkType);
        }

        return $json;
    }

    private function appendLink($json, CompletePackageInterface $package, $linkType)
    {
        $links = $package->{'get' . ucfirst($linkType)}();

        if ($links) {
            $json[$linkType] = array();

            foreach ($links as $link) {
                $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $json;
    }

    /**
     * Init styles for tree
     *
     * @param OutputInterface $output
     */
    protected function initStyles(OutputInterface $output)
    {
        $this->colors = array(
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        );

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Display the tree
     *
     * @param array $arrayTree
     */
    protected function displayPackageTree(array $arrayTree)
    {
        $io = $this->getIO();
        foreach ($arrayTree as $package) {
            $io->write(sprintf('<info>%s</info>', $package['name']), false);
            $io->write(' ' . $package['version'], false);
            $io->write(' ' . strtok($package['description'], "\r\n"));

            if (isset($package['requires'])) {
                $requires = $package['requires'];
                $treeBar = '├';
                $j = 0;
                $total = count($requires);
                foreach ($requires as $require) {
                    $requireName = $require['name'];
                    $j++;
                    if ($j === $total) {
                        $treeBar = '└';
                    }
                    $level = 1;
                    $color = $this->colors[$level];
                    $info = sprintf(
                        '%s──<%s>%s</%s> %s',
                        $treeBar,
                        $color,
                        $requireName,
                        $color,
                        $require['version']
                    );
                    $this->writeTreeLine($info);

                    $treeBar = str_replace('└', ' ', $treeBar);
                    $packagesInTree = array($package['name'], $requireName);

                    $this->displayTree($require, $packagesInTree, $treeBar, $level + 1);
                }
            }
        }
    }

    /**
     * Generate the package tree
     *
     * @param  PackageInterface $package
     * @param  RepositoryInterface     $installedRepo
     * @param  RepositoryInterface     $distantRepos
     * @return array
     */
    protected function generatePackageTree(
        PackageInterface $package,
        RepositoryInterface $installedRepo,
        RepositoryInterface $distantRepos
    ) {
        $requires = $package->getRequires();
        ksort($requires);
        $children = array();
        foreach ($requires as $requireName => $require) {
            $packagesInTree = array($package->getName(), $requireName);

            $treeChildDesc = array(
                'name' => $requireName,
                'version' => $require->getPrettyConstraint(),
            );

            $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree);

            if ($deepChildren) {
                $treeChildDesc['requires'] = $deepChildren;
            }

            $children[] = $treeChildDesc;
        }
        $tree = array(
            'name' => $package->getPrettyName(),
            'version' => $package->getPrettyVersion(),
            'description' => $package->getDescription(),
        );

        if ($children) {
            $tree['requires'] = $children;
        }

        return $tree;
    }

    /**
     * Display a package tree
     *
     * @param PackageInterface|string $package
     * @param array                   $packagesInTree
     * @param string                  $previousTreeBar
     * @param int                     $level
     */
    protected function displayTree(
        $package,
        array $packagesInTree,
        $previousTreeBar = '├',
        $level = 1
    ) {
        $previousTreeBar = str_replace('├', '│', $previousTreeBar);
        if (isset($package['requires'])) {
            $requires = $package['requires'];
            $treeBar = $previousTreeBar . '  ├';
            $i = 0;
            $total = count($requires);
            foreach ($requires as $require) {
                $currentTree = $packagesInTree;
                $i++;
                if ($i === $total) {
                    $treeBar = $previousTreeBar . '  └';
                }
                $colorIdent = $level % count($this->colors);
                $color = $this->colors[$colorIdent];

                $circularWarn = in_array(
                    $require['name'],
                    $currentTree,
                    true
                ) ? '(circular dependency aborted here)' : '';
                $info = rtrim(sprintf(
                    '%s──<%s>%s</%s> %s %s',
                    $treeBar,
                    $color,
                    $require['name'],
                    $color,
                    $require['version'],
                    $circularWarn
                ));
                $this->writeTreeLine($info);

                $treeBar = str_replace('└', ' ', $treeBar);

                $currentTree[] = $require['name'];
                $this->displayTree($require, $currentTree, $treeBar, $level + 1);
            }
        }
    }

    /**
     * Display a package tree
     *
     * @param  string                  $name
     * @param  PackageInterface|string $package
     * @param  RepositoryInterface     $installedRepo
     * @param  RepositoryInterface     $distantRepos
     * @param  array                   $packagesInTree
     * @return array
     */
    protected function addTree(
        $name,
        $package,
        RepositoryInterface $installedRepo,
        RepositoryInterface $distantRepos,
        array $packagesInTree
    ) {
        $children = array();
        list($package, $versions) = $this->getPackage(
            $installedRepo,
            $distantRepos,
            $name,
            $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()
        );
        if (is_object($package)) {
            $requires = $package->getRequires();
            ksort($requires);
            foreach ($requires as $requireName => $require) {
                $currentTree = $packagesInTree;

                $treeChildDesc = array(
                    'name' => $requireName,
                    'version' => $require->getPrettyConstraint(),
                );

                if (!in_array($requireName, $currentTree, true)) {
                    $currentTree[] = $requireName;
                    $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree);
                    if ($deepChildren) {
                        $treeChildDesc['requires'] = $deepChildren;
                    }
                }

                $children[] = $treeChildDesc;
            }
        }

        return $children;
    }

    private function updateStatusToVersionStyle($updateStatus)
    {
        // 'up-to-date' is printed green
        // 'semver-safe-update' is printed red
        // 'update-possible' is printed yellow
        return str_replace(array('up-to-date', 'semver-safe-update', 'update-possible'), array('info', 'highlight', 'comment'), $updateStatus);
    }

    private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package)
    {
        if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
            return 'up-to-date';
        }

        $constraint = $package->getVersion();
        if (0 !== strpos($constraint, 'dev-')) {
            $constraint = '^'.$constraint;
        }
        if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
            // it needs an immediate semver-compliant upgrade
            return 'semver-safe-update';
        }

        // it needs an upgrade but has potential BC breaks so is not urgent
        return 'update-possible';
    }

    private function writeTreeLine($line)
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(array('└', '├', '──', '│'), array('`-', '|-', '-', '|'), $line);
        }

        $io->write($line);
    }

    /**
     * Given a package, this finds the latest package matching it
     *
     * @param PackageInterface $package
     * @param Composer         $composer
     * @param string           $phpVersion
     * @param bool             $minorOnly
     *
     * @return PackageInterface|null
     */
    private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false)
    {
        // find the latest version allowed in this pool
        $name = $package->getName();
        $versionSelector = new VersionSelector($this->getPool($composer));
        $stability = $composer->getPackage()->getMinimumStability();
        $flags = $composer->getPackage()->getStabilityFlags();
        if (isset($flags[$name])) {
            $stability = array_search($flags[$name], BasePackage::$stabilities, true);
        }

        $bestStability = $stability;
        if ($composer->getPackage()->getPreferStable()) {
            $bestStability = $package->getStability();
        }

        $targetVersion = null;
        if (0 === strpos($package->getVersion(), 'dev-')) {
            $targetVersion = $package->getVersion();
        }

        if ($targetVersion === null && $minorOnly) {
            $targetVersion = '^' . $package->getVersion();
        }

        return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability);
    }

    private function getPool(Composer $composer)
    {
        if (!$this->pool) {
            $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
            $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
        }

        return $this->pool;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ValidateCommand
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidateCommand extends BaseCommand
{
    /**
     * configure
     */
    protected function configure()
    {
        $this
            ->setName('validate')
            ->setDescription('Validates a composer.json and composer.lock.')
            ->setDefinition(array(
                new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'),
                new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'),
                new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'),
                new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'),
                new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'),
            ))
            ->setHelp(
                <<<EOT
The validate command validates a given composer.json and composer.lock

Exit codes in case of errors are:
1 validation warning(s), only when --strict is given
2 validation error(s)
3 file unreadable or missing

Read more at https://getcomposer.org/doc/03-cli.md#validate
EOT
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $file = $input->getArgument('file') ?: Factory::getComposerFile();
        $io = $this->getIO();

        if (!file_exists($file)) {
            $io->writeError('<error>' . $file . ' not found.</error>');

            return 3;
        }
        if (!is_readable($file)) {
            $io->writeError('<error>' . $file . ' is not readable.</error>');

            return 3;
        }

        $validator = new ConfigValidator($io);
        $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL;
        $checkPublish = !$input->getOption('no-check-publish');
        $checkLock = !$input->getOption('no-check-lock');
        $isStrict = $input->getOption('strict');
        list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);

        $lockErrors = array();
        $composer = Factory::create($io, $file, $input->hasParameterOption('--no-plugins'));
        $locker = $composer->getLocker();
        if ($locker->isLocked() && !$locker->isFresh()) {
            $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update <package name>`.';
        }

        $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true);

        // $errors include publish and lock errors when exists
        $exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0);

        if ($input->getOption('with-dependencies')) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            foreach ($localRepo->getPackages() as $package) {
                $path = $composer->getInstallationManager()->getInstallPath($package);
                $file = $path . '/composer.json';
                if (is_dir($path) && file_exists($file)) {
                    list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);

                    $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors);

                    // $errors include publish errors when exists
                    $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0);
                    $exitCode = max($depCode, $exitCode);
                }
            }
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output);
        $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
        $exitCode = max($eventCode, $exitCode);

        return $exitCode;
    }

    private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false)
    {
        $doPrintSchemaUrl = false;

        if ($errors) {
            $io->writeError('<error>' . $name . ' is invalid, the following errors/warnings were found:</error>');
        } elseif ($publishErrors) {
            $io->writeError('<info>' . $name . ' is valid for simple usage with composer but has</info>');
            $io->writeError('<info>strict errors that make it unable to be published as a package:</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } elseif ($warnings) {
            $io->writeError('<info>' . $name . ' is valid, but with a few warnings</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } else {
            $io->write('<info>' . $name . ' is valid</info>');
            // if ($lockErrors) then they will be displayed below
        }

        if ($doPrintSchemaUrl) {
            $io->writeError('<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>');
        }

        // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined
        $extraWarnings = array();

        // If checking publish errors, display them as errors, otherwise just show them as warnings
        if ($checkPublish) {
            $errors = array_merge($errors, $publishErrors);
        } else {
            $extraWarnings = array_merge($extraWarnings, $publishErrors);
        }

        // If checking lock errors, display them as errors, otherwise just show them as warnings
        if ($checkLock) {
            $errors = array_merge($errors, $lockErrors);
        } else {
            $extraWarnings = array_merge($extraWarnings, $lockErrors);
        }

        $messages = array(
            'error' => $errors,
            'warning' => array_merge($warnings, $extraWarnings),
        );

        foreach ($messages as $style => $msgs) {
            foreach ($msgs as $msg) {
                $io->writeError('<' . $style . '>' . $msg . '</' . $style . '>');
            }
        }
    }
}
<?php

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

namespace Composer\Console;

use Symfony\Component\Console\Formatter\OutputFormatter;

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

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

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

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

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

    private function formatHtml($matches)
    {
        $out = '<span style="';
        foreach (explode(';', $matches[1]) as $code) {
            if (isset(self::$availableForegroundColors[$code])) {
                $out .= 'color:'.self::$availableForegroundColors[$code].';';
            } elseif (isset(self::$availableBackgroundColors[$code])) {
                $out .= 'background-color:'.self::$availableBackgroundColors[$code].';';
            } elseif (isset(self::$availableOptions[$code])) {
                switch (self::$availableOptions[$code]) {
                    case 'bold':
                        $out .= 'font-weight:bold;';
                        break;

                    case 'underscore':
                        $out .= 'text-decoration:underline;';
                        break;
                }
            }
        }

        return $out.'">'.$matches[2].'</span>';
    }
}
<?php

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

namespace Composer\Console;

use Composer\IO\NullIO;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Command;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO;
use Composer\Json\JsonValidationException;
use Composer\Util\ErrorHandler;
use Composer\EventDispatcher\ScriptExecutionException;
use Composer\Exception\NoSslException;

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

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

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

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

    /**
     * @var string Store the initial working directory at startup time
     */
    private $initialWorkingDirectory = '';

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

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

        if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
            date_default_timezone_set(Silencer::call('date_default_timezone_get'));
        }

        if (!$shutdownRegistered) {
            $shutdownRegistered = true;

            register_shutdown_function(function () {
                $lastError = error_get_last();

                if ($lastError && $lastError['message'] &&
                   (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ ||
                    strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) {
                    echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.';
                }
            });
        }

        $this->io = new NullIO();

        $this->initialWorkingDirectory = getcwd();

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

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

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

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

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

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

        // Register error handler again to pass it the IO instance
        ErrorHandler::register($io);

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

        // switch working dir
        if ($newWorkDir = $this->getNewWorkingDir($input)) {
            $oldWorkingDir = getcwd();
            chdir($newWorkDir);
            $this->initialWorkingDirectory = $newWorkDir;
            $io->writeError('Changed CWD to ' . getcwd(), true, IOInterface::DEBUG);
        }

        // determine command name to be executed without including plugin commands
        $commandName = '';
        if ($name = $this->getCommandName($input)) {
            try {
                $commandName = $this->find($name)->getName();
            } catch (CommandNotFoundException $e) {
              // we'll check command validity again later after plugins are loaded
              $commandName = false;
            } catch (\InvalidArgumentException $e) {
            }
        }

        // prompt user for dir change if no composer.json is present in current dir
        if ($io->isInteractive() && !$newWorkDir && !in_array($commandName, array('', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'), true) && !file_exists(Factory::getComposerFile())) {
            $dir = dirname(getcwd());
            $home = realpath(getenv('HOME') ?: getenv('USERPROFILE') ?: '/');

            // abort when we reach the home dir or top of the filesystem
            while (dirname($dir) !== $dir && $dir !== $home) {
                if (file_exists($dir.'/'.Factory::getComposerFile())) {
                    if ($io->askConfirmation('<info>No composer.json in current directory, do you want to use the one at '.$dir.'?</info> [<comment>Y,n</comment>]? ', true)) {
                        $oldWorkingDir = getcwd();
                        chdir($dir);
                    }
                    break;
                }
                $dir = dirname($dir);
            }
        }

        if (!$this->disablePluginsByDefault && !$this->hasPluginCommands && 'global' !== $commandName) {
            try {
                foreach ($this->getPluginCommands() as $command) {
                    if ($this->has($command->getName())) {
                        $io->writeError('<warning>Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped</warning>');
                    } else {
                        $this->add($command);
                    }
                }
            } catch (NoSslException $e) {
                // suppress these as they are not relevant at this point
            }

            $this->hasPluginCommands = true;
        }

        // determine command name to be executed incl plugin commands, and check if it's a proxy command
        $isProxyCommand = false;
        if ($name = $this->getCommandName($input)) {
            try {
                $command = $this->find($name);
                $commandName = $command->getName();
                $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand());
            } catch (\InvalidArgumentException $e) {
            }
        }

        if (!$isProxyCommand) {
            $io->writeError(sprintf(
                'Running %s (%s) with %s on %s',
                Composer::getVersion(),
                Composer::RELEASE_DATE,
                defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
                function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
            ), true, IOInterface::DEBUG);

            if (PHP_VERSION_ID < 50302) {
                $io->writeError('<warning>Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.</warning>');
            }

            if (extension_loaded('xdebug') && !getenv('COMPOSER_DISABLE_XDEBUG_WARN')) {
                $io->writeError('<warning>You are running composer with Xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug</warning>');
            }

            if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) {
                $io->writeError(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
            }

            if (
                !Platform::isWindows()
                && function_exists('exec')
                && !getenv('COMPOSER_ALLOW_SUPERUSER')
                && (ini_get('open_basedir') || !file_exists('/.dockerenv'))
            ) {
                if (function_exists('posix_getuid') && posix_getuid() === 0) {
                    if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
                        $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');
                    }
                    if ($uid = (int) getenv('SUDO_UID')) {
                        // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on
                        // ref. https://github.com/composer/composer/issues/5119
                        Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1");
                    }
                }
                // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations
                Silencer::call('exec', 'sudo -K > /dev/null 2>&1');
            }

            // Check system temp folder for usability as it can cause weird runtime issues otherwise
            Silencer::call(function () use ($io) {
                $tempfile = sys_get_temp_dir() . '/temp-' . md5(microtime());
                if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) == __FILE__) && unlink($tempfile) && !file_exists($tempfile))) {
                    $io->writeError(sprintf('<error>PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini</error>', sys_get_temp_dir()));
                }
            });

            // add non-standard scripts as own commands
            $file = Factory::getComposerFile();
            if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
                if (isset($composer['scripts']) && is_array($composer['scripts'])) {
                    foreach ($composer['scripts'] as $script => $dummy) {
                        if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                            if ($this->has($script)) {
                                $io->writeError('<warning>A script named '.$script.' would override a Composer command and has been skipped</warning>');
                            } else {
                                $description = null;

                                if (isset($composer['scripts-descriptions'][$script])) {
                                    $description = $composer['scripts-descriptions'][$script];
                                }

                                $this->add(new Command\ScriptAliasCommand($script, $description));
                            }
                        }
                    }
                }
            }
        }

        try {
            if ($input->hasParameterOption('--profile')) {
                $startTime = microtime(true);
                $this->io->enableDebugging($startTime);
            }

            $result = parent::doRun($input, $output);

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

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

            restore_error_handler();

            return $result;
        } catch (ScriptExecutionException $e) {
            return $e->getCode();
        } catch (\Exception $e) {
            $this->hintCommonErrors($e);
            restore_error_handler();
            throw $e;
        }
    }

    /**
     * @param  InputInterface    $input
     * @throws \RuntimeException
     * @return string
     */
    private function getNewWorkingDir(InputInterface $input)
    {
        $workingDir = $input->getParameterOption(array('--working-dir', '-d'));
        if (false !== $workingDir && !is_dir($workingDir)) {
            throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.');
        }

        return $workingDir;
    }

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

        Silencer::suppress();
        try {
            $composer = $this->getComposer(false, true);
            if ($composer) {
                $config = $composer->getConfig();

                $minSpaceFree = 1024 * 1024;
                if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
                ) {
                    $io->writeError('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>', true, IOInterface::QUIET);
                }
            }
        } catch (\Exception $e) {
        }
        Silencer::restore();

        if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
            $io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET);
        }

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

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

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

        return $this->composer;
    }

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

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

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

    /**
     * Initializes all the composer commands.
     */
    protected function getDefaultCommands()
    {
        $commands = array_merge(parent::getDefaultCommands(), array(
            new Command\AboutCommand(),
            new Command\ConfigCommand(),
            new Command\DependsCommand(),
            new Command\ProhibitsCommand(),
            new Command\InitCommand(),
            new Command\InstallCommand(),
            new Command\CreateProjectCommand(),
            new Command\UpdateCommand(),
            new Command\SearchCommand(),
            new Command\ValidateCommand(),
            new Command\ShowCommand(),
            new Command\SuggestsCommand(),
            new Command\RequireCommand(),
            new Command\DumpAutoloadCommand(),
            new Command\StatusCommand(),
            new Command\ArchiveCommand(),
            new Command\DiagnoseCommand(),
            new Command\RunScriptCommand(),
            new Command\LicensesCommand(),
            new Command\GlobalCommand(),
            new Command\ClearCacheCommand(),
            new Command\RemoveCommand(),
            new Command\HomeCommand(),
            new Command\ExecCommand(),
            new Command\OutdatedCommand(),
            new Command\CheckPlatformReqsCommand(),
            new Command\FundCommand(),
        ));

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

        return $commands;
    }

    /**
     * {@inheritDoc}
     */
    public function getLongVersion()
    {
        if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
            return sprintf(
                '<info>%s</info> version <comment>%s (%s)</comment> %s',
                $this->getName(),
                Composer::BRANCH_ALIAS_VERSION,
                $this->getVersion(),
                Composer::RELEASE_DATE
            );
        }

        return parent::getLongVersion() . ' ' . Composer::RELEASE_DATE;
    }

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

        return $definition;
    }

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

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

        if (null !== $composer) {
            $pm = $composer->getPluginManager();
            foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', array('composer' => $composer, 'io' => $this->io)) as $capability) {
                $newCommands = $capability->getCommands();
                if (!is_array($newCommands)) {
                    throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands');
                }
                foreach ($newCommands as $command) {
                    if (!$command instanceof Command\BaseCommand) {
                        throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects');
                    }
                }
                $commands = array_merge($commands, $newCommands);
            }
        }

        return $commands;
    }

    /**
     * Get the working directoy at startup time
     *
     * @return string
     */
    public function getInitialWorkingDirectory()
    {
        return $this->initialWorkingDirectory;
    }
}
<?php

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

namespace Composer;

use Composer\Autoload\AutoloadGenerator;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\Installer\InstallerEvents;
use Composer\Installer\NoopInstaller;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
use Composer\Repository\WritableRepositoryInterface;
use Composer\Script\ScriptEvents;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Installer
{
    /**
     * @var IOInterface
     */
    protected $io;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var RootPackageInterface
     */
    protected $package;

    /**
     * @var DownloadManager
     */
    protected $downloadManager;

    /**
     * @var RepositoryManager
     */
    protected $repositoryManager;

    /**
     * @var Locker
     */
    protected $locker;

    /**
     * @var InstallationManager
     */
    protected $installationManager;

    /**
     * @var EventDispatcher
     */
    protected $eventDispatcher;

    /**
     * @var AutoloadGenerator
     */
    protected $autoloadGenerator;

    protected $preferSource = false;
    protected $preferDist = false;
    protected $optimizeAutoloader = false;
    protected $classMapAuthoritative = false;
    protected $apcuAutoloader = false;
    protected $devMode = false;
    protected $dryRun = false;
    protected $verbose = false;
    protected $update = false;
    protected $dumpAutoloader = true;
    protected $runScripts = true;
    protected $ignorePlatformReqs = false;
    protected $preferStable = false;
    protected $preferLowest = false;
    protected $skipSuggest = false;
    protected $writeLock;
    protected $executeOperations = true;

    /**
     * Array of package names/globs flagged for update
     *
     * @var array|null
     */
    protected $updateWhitelist = null; // TODO 2.0 rename to updateAllowList
    protected $whitelistDependencies = false; // TODO 2.0 rename to allowListTransitiveDependencies
    protected $whitelistAllDependencies = false; // TODO 2.0 rename to allowListAllDependencies

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    /**
     * @var RepositoryInterface
     */
    protected $additionalInstalledRepository;

    /**
     * Constructor
     *
     * @param IOInterface          $io
     * @param Config               $config
     * @param RootPackageInterface $package
     * @param DownloadManager      $downloadManager
     * @param RepositoryManager    $repositoryManager
     * @param Locker               $locker
     * @param InstallationManager  $installationManager
     * @param EventDispatcher      $eventDispatcher
     * @param AutoloadGenerator    $autoloadGenerator
     */
    public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
    {
        $this->io = $io;
        $this->config = $config;
        $this->package = $package;
        $this->downloadManager = $downloadManager;
        $this->repositoryManager = $repositoryManager;
        $this->locker = $locker;
        $this->installationManager = $installationManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->autoloadGenerator = $autoloadGenerator;

        $this->writeLock = $config->get('lock');
    }

    /**
     * Run installation (or update)
     *
     * @throws \Exception
     * @return int        0 on success or a positive error code on failure
     */
    public function run()
    {
        // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands
        // of PHP objects, the GC can spend quite some time walking the tree of references looking
        // for stuff to collect while there is nothing to collect. This slows things down dramatically
        // and turning it off results in much better performance. Do not try this at home however.
        gc_collect_cycles();
        gc_disable();

        // Force update if there is no lock file present
        if (!$this->update && !$this->locker->isLocked()) {
            $this->update = true;
        }

        if ($this->dryRun) {
            $this->verbose = true;
            $this->runScripts = false;
            $this->executeOperations = false;
            $this->writeLock = false;
            $this->dumpAutoloader = false;
            $this->installationManager->addInstaller(new NoopInstaller);
            $this->mockLocalRepositories($this->repositoryManager);
        }

        if ($this->runScripts) {
            $_SERVER['COMPOSER_DEV_MODE'] = $this->devMode ? '1' : '0';
            putenv('COMPOSER_DEV_MODE='.$_SERVER['COMPOSER_DEV_MODE']);

            // dispatch pre event
            $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        $this->downloadManager->setPreferSource($this->preferSource);
        $this->downloadManager->setPreferDist($this->preferDist);

        // create installed repo, this contains all local packages + platform packages (php & extensions)
        $localRepo = $this->repositoryManager->getLocalRepository();
        if ($this->update) {
            $platformOverrides = $this->config->get('platform') ?: array();
        } else {
            $platformOverrides = $this->locker->getPlatformOverrides();
        }
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);

        $aliases = $this->getRootAliases();
        $this->aliasPlatformPackages($platformRepo, $aliases);

        if (!$this->suggestedPackagesReporter) {
            $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
        }

        try {
            list($res, $devPackages) = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases);
            if ($res !== 0) {
                return $res;
            }
        } catch (\Exception $e) {
            if ($this->executeOperations && $this->config->get('notify-on-install')) {
                $this->installationManager->notifyInstalls($this->io);
            }

            throw $e;
        }
        if ($this->executeOperations && $this->config->get('notify-on-install')) {
            $this->installationManager->notifyInstalls($this->io);
        }

        // output suggestions if we're in dev mode
        if ($this->devMode && !$this->skipSuggest) {
            $this->suggestedPackagesReporter->output($installedRepo);
        }

        # Find abandoned packages and warn user
        foreach ($localRepo->getPackages() as $package) {
            if (!$package instanceof CompletePackage || !$package->isAbandoned()) {
                continue;
            }

            $replacement = is_string($package->getReplacementPackage())
                ? 'Use ' . $package->getReplacementPackage() . ' instead'
                : 'No replacement was suggested';

            $this->io->writeError(
                sprintf(
                    "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
                    $package->getPrettyName(),
                    $replacement
                )
            );
        }

        // write lock
        if ($this->update && $this->writeLock) {
            $localRepo->reload();

            $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
            $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());

            $updatedLock = $this->locker->setLockData(
                array_diff($localRepo->getCanonicalPackages(), $devPackages),
                $devPackages,
                $platformReqs,
                $platformDevReqs,
                $aliases,
                $this->package->getMinimumStability(),
                $this->package->getStabilityFlags(),
                $this->preferStable || $this->package->getPreferStable(),
                $this->preferLowest,
                $this->config->get('platform') ?: array()
            );
            if ($updatedLock) {
                $this->io->writeError('<info>Writing lock file</info>');
            }
        }

        if ($this->dumpAutoloader) {
            // write autoloader
            if ($this->optimizeAutoloader) {
                $this->io->writeError('<info>Generating optimized autoload files</info>');
            } else {
                $this->io->writeError('<info>Generating autoload files</info>');
            }

            $this->autoloadGenerator->setDevMode($this->devMode);
            $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
            $this->autoloadGenerator->setApcu($this->apcuAutoloader);
            $this->autoloadGenerator->setRunScripts($this->runScripts);
            $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
        }

        if ($this->executeOperations) {
            // force binaries re-generation in case they are missing
            foreach ($localRepo->getPackages() as $package) {
                $this->installationManager->ensureBinariesPresence($package);
            }
        }

        $fundingCount = 0;
        foreach ($localRepo->getPackages() as $package) {
            if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
                $fundingCount++;
            }
        }
        if ($fundingCount) {
            $this->io->writeError(array(
                sprintf(
                    "<info>%d package%s you are using %s looking for funding.</info>",
                    $fundingCount,
                    1 === $fundingCount ? '' : 's',
                    1 === $fundingCount ? 'is' : 'are'
                ),
                '<info>Use the `composer fund` command to find out more!</info>',
            ));
        }

        if ($this->runScripts) {
            // dispatch post event
            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        // re-enable GC except on HHVM which triggers a warning here
        if (!defined('HHVM_VERSION')) {
            gc_enable();
        }

        return 0;
    }

    /**
     * @param  RepositoryInterface $localRepo
     * @param  RepositoryInterface $installedRepo
     * @param  PlatformRepository  $platformRepo
     * @param  array               $aliases
     * @return array               [int, PackageInterfaces[]|null] with the exit code and an array of dev packages on update, or null on install
     */
    protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases)
    {
        // init vars
        $lockedRepository = null;
        $repositories = null;

        // initialize locked repo if we are installing from lock or in a partial update
        // and a lock file is present as we need to force install non-allowed lock file
        // packages in that case
        if (!$this->update || (!empty($this->updateWhitelist) && $this->locker->isLocked())) {
            try {
                $lockedRepository = $this->locker->getLockedRepository($this->devMode);
            } catch (\RuntimeException $e) {
                // if there are dev requires, then we really can not install
                if ($this->package->getDevRequires()) {
                    throw $e;
                }
                // no require-dev in composer.json and the lock file was created with no dev info, so skip them
                $lockedRepository = $this->locker->getLockedRepository();
            }
        }

        $this->allowListUpdateDependencies(
            $lockedRepository ?: $localRepo,
            $this->package->getRequires(),
            $this->package->getDevRequires()
        );

        $this->io->writeError('<info>Loading composer repositories with package information</info>');

        // creating repository pool
        $policy = $this->createPolicy();
        $pool = $this->createPool($this->update ? null : $lockedRepository);
        $pool->addRepository($installedRepo, $aliases);
        if ($this->update) {
            $repositories = $this->repositoryManager->getRepositories();
            foreach ($repositories as $repository) {
                $pool->addRepository($repository, $aliases);
            }
        }
        // Add the locked repository after the others in case we are doing a
        // partial update so missing packages can be found there still.
        // For installs from lock it's the only one added so it is first
        if ($lockedRepository) {
            $pool->addRepository($lockedRepository, $aliases);
        }

        // creating requirements request
        $request = $this->createRequest($this->package, $platformRepo);

        if ($this->update) {
            // remove unstable packages from the localRepo if they don't match the current stability settings
            $removedUnstablePackages = array();
            foreach ($localRepo->getPackages() as $package) {
                if (
                    !$pool->isPackageAcceptable($package->getNames(), $package->getStability())
                    && $this->installationManager->isPackageInstalled($localRepo, $package)
                ) {
                    $removedUnstablePackages[$package->getName()] = true;
                    $request->remove($package->getName(), new Constraint('=', $package->getVersion()));
                }
            }

            $this->io->writeError('<info>Updating dependencies'.($this->devMode ? ' (including require-dev)' : '').'</info>');

            $request->updateAll();

            $links = array_merge($this->package->getRequires(), $this->package->getDevRequires());

            foreach ($links as $link) {
                $request->install($link->getTarget(), $link->getConstraint());
            }

            // if the updateWhitelist is enabled, packages not in it are also fixed
            // to the version specified in the lock, or their currently installed version
            if ($this->updateWhitelist) {
                $currentPackages = $this->getCurrentPackages($installedRepo);

                // collect packages to fixate from root requirements as well as installed packages
                $candidates = array();
                foreach ($links as $link) {
                    $candidates[$link->getTarget()] = true;
                    $rootRequires[$link->getTarget()] = $link;
                }
                foreach ($currentPackages as $package) {
                    $candidates[$package->getName()] = true;
                }

                // fix them to the version in lock (or currently installed) if they are not updateable
                foreach ($candidates as $candidate => $dummy) {
                    foreach ($currentPackages as $curPackage) {
                        if ($curPackage->getName() === $candidate) {
                            if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) {
                                $constraint = new Constraint('=', $curPackage->getVersion());
                                $description = $this->locker->isLocked() ? '(locked at' : '(installed at';
                                $requiredAt = isset($rootRequires[$candidate]) ? ', required as ' . $rootRequires[$candidate]->getPrettyConstraint() : '';
                                $constraint->setPrettyString($description . ' ' . $curPackage->getPrettyVersion() . $requiredAt . ')');
                                $request->install($curPackage->getName(), $constraint);
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            $this->io->writeError('<info>Installing dependencies'.($this->devMode ? ' (including require-dev)' : '').' from lock file</info>');

            if (!$this->locker->isFresh()) {
                $this->io->writeError('<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.</warning>', true, IOInterface::QUIET);
            }

            foreach ($lockedRepository->getPackages() as $package) {
                $version = $package->getVersion();
                if (isset($aliases[$package->getName()][$version])) {
                    $version = $aliases[$package->getName()][$version]['alias_normalized'];
                }
                $constraint = new Constraint('=', $version);
                $constraint->setPrettyString($package->getPrettyVersion());
                $request->install($package->getName(), $constraint);
            }

            foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) {
                $request->install($link->getTarget(), $link->getConstraint());
            }
        }

        // force dev packages to have the latest links if we update or install from a (potentially new) lock
        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links');

        // solve dependencies
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);
        $solver = new Solver($policy, $pool, $installedRepo, $this->io);
        try {
            $operations = $solver->solve($request, $this->ignorePlatformReqs);
            $ruleSetSize = $solver->getRuleSetSize();
            $solver = null;
        } catch (SolverProblemsException $e) {
            $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
            $this->io->writeError($e->getMessage());
            if ($this->update && !$this->devMode) {
                $this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
            }

            return array(max(1, $e->getCode()), array());
        }

        // force dev packages to be updated if we update or install from a (potentially new) lock
        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations);

        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations);

        $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);

        // execute operations
        if (!$operations) {
            $this->io->writeError('Nothing to install or update');
        }

        $operations = $this->movePluginsToFront($operations);
        $operations = $this->moveUninstallsToFront($operations);

        // extract dev packages and mark them to be skipped if it's a --no-dev install or update
        // we also force them to be uninstalled if they are present in the local repo
        if ($this->update) {
            $devPackages = $this->extractDevPackages($operations, $localRepo, $platformRepo, $aliases);
            if (!$this->devMode) {
                $operations = $this->filterDevPackageOperations($devPackages, $operations, $localRepo);
            }
        } else {
            $devPackages = null;
        }

        if ($operations) {
            $installs = $updates = $uninstalls = array();
            foreach ($operations as $operation) {
                if ($operation instanceof InstallOperation) {
                    $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UpdateOperation) {
                    $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UninstallOperation) {
                    $uninstalls[] = $operation->getPackage()->getPrettyName();
                }
            }

            $this->io->writeError(sprintf(
                "<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
                count($installs),
                1 === count($installs) ? '' : 's',
                count($updates),
                1 === count($updates) ? '' : 's',
                count($uninstalls),
                1 === count($uninstalls) ? '' : 's'
            ));
            if ($installs) {
                $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE);
            }
            if ($updates) {
                $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE);
            }
            if ($uninstalls) {
                $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE);
            }
        }

        foreach ($operations as $operation) {
            // collect suggestions
            $jobType = $operation->getJobType();
            if ('install' === $jobType) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());
            }

            // updating, force dev packages' references if they're in root package refs
            if ($this->update) {
                $package = null;
                if ('update' === $jobType) {
                    $package = $operation->getTargetPackage();
                } elseif ('install' === $jobType) {
                    $package = $operation->getPackage();
                }
                if ($package && $package->isDev()) {
                    $references = $this->package->getReferences();
                    if (isset($references[$package->getName()])) {
                        $this->updateInstallReferences($package, $references[$package->getName()]);
                    }
                }
                if ('update' === $jobType) {
                    $targetPackage = $operation->getTargetPackage();
                    if ($targetPackage->isDev()) {
                        $initialPackage = $operation->getInitialPackage();
                        if ($targetPackage->getVersion() === $initialPackage->getVersion()
                            && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference())
                            && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference())
                        ) {
                            $this->io->writeError('  - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG);
                            $this->io->writeError('', true, IOInterface::DEBUG);

                            continue;
                        }
                    }
                }
            }

            $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($jobType);
            if (defined($event) && $this->runScripts) {
                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);
            }

            // output non-alias ops when not executing operations (i.e. dry run), output alias ops in debug verbosity
            if (!$this->executeOperations && false === strpos($operation->getJobType(), 'Alias')) {
                $this->io->writeError('  - ' . $operation);
            } elseif ($this->io->isDebug() && false !== strpos($operation->getJobType(), 'Alias')) {
                $this->io->writeError('  - ' . $operation);
            }

            $this->installationManager->execute($localRepo, $operation);

            // output reasons why the operation was ran, only for install/update operations
            if ($this->verbose && $this->io->isVeryVerbose() && in_array($jobType, array('install', 'update'))) {
                $reason = $operation->getReason();
                if ($reason instanceof Rule) {
                    switch ($reason->getReason()) {
                        case Rule::RULE_JOB_INSTALL:
                            $this->io->writeError('    REASON: Required by the root package: '.$reason->getPrettyString($pool));
                            $this->io->writeError('');
                            break;
                        case Rule::RULE_PACKAGE_REQUIRES:
                            $this->io->writeError('    REASON: '.$reason->getPrettyString($pool));
                            $this->io->writeError('');
                            break;
                    }
                }
            }

            if ($this->executeOperations || $this->writeLock) {
                $localRepo->write();
            }

            $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType);
            if (defined($event) && $this->runScripts) {
                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);
            }
        }

        if ($this->executeOperations) {
            // force source/dist urls to be updated for all packages
            $this->processPackageUrls($pool, $policy, $localRepo, $repositories);
            $localRepo->write();
        }

        // see https://github.com/composer/composer/issues/2764
        if ($operations) {
            $vendorDir = $this->config->get('vendor-dir');
            if (is_dir($vendorDir)) {
                // suppress errors as this fails sometimes on OSX for no apparent reason
                // see https://github.com/composer/composer/issues/4070#issuecomment-129792748
                @touch($vendorDir);
            }
        }

        return array(0, $devPackages);
    }

    /**
     * Extracts the dev packages out of the localRepo
     *
     * This works by faking the operations so we can see what the dev packages
     * would be at the end of the operation execution. This lets us then remove
     * the dev packages from the list of operations accordingly if we are in a
     * --no-dev install or update.
     *
     * @return array
     */
    private function extractDevPackages(array $operations, RepositoryInterface $localRepo, PlatformRepository $platformRepo, array $aliases)
    {
        if (!$this->package->getDevRequires()) {
            return array();
        }

        // fake-apply all operations to this clone of the local repo so we see the complete set of package we would end up with
        $tempLocalRepo = clone $localRepo;
        foreach ($operations as $operation) {
            switch ($operation->getJobType()) {
                case 'install':
                case 'markAliasInstalled':
                    if (!$tempLocalRepo->hasPackage($operation->getPackage())) {
                        $tempLocalRepo->addPackage(clone $operation->getPackage());
                    }
                    break;

                case 'uninstall':
                case 'markAliasUninstalled':
                    $tempLocalRepo->removePackage($operation->getPackage());
                    break;

                case 'update':
                    $tempLocalRepo->removePackage($operation->getInitialPackage());
                    if (!$tempLocalRepo->hasPackage($operation->getTargetPackage())) {
                        $tempLocalRepo->addPackage(clone $operation->getTargetPackage());
                    }
                    break;

                default:
                    throw new \LogicException('Unknown type: '.$operation->getJobType());
            }
        }

        // we have to reload the local repo to handle aliases properly
        // but as it is not persisted on disk we use a loader/dumper
        // to reload it in memory
        $localRepo = new InstalledArrayRepository(array());
        $loader = new ArrayLoader(null, true);
        $dumper = new ArrayDumper();
        foreach ($tempLocalRepo->getCanonicalPackages() as $pkg) {
            $localRepo->addPackage($loader->load($dumper->dump($pkg)));
        }
        unset($tempLocalRepo, $loader, $dumper);

        $policy = $this->createPolicy();
        $pool = $this->createPool();
        $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);
        $pool->addRepository($installedRepo, $aliases);

        // creating requirements request without dev requirements
        $request = $this->createRequest($this->package, $platformRepo);
        $request->updateAll();
        foreach ($this->package->getRequires() as $link) {
            $request->install($link->getTarget(), $link->getConstraint());
        }

        // solve deps to see which get removed
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request);
        $solver = new Solver($policy, $pool, $installedRepo, $this->io);
        $ops = $solver->solve($request, $this->ignorePlatformReqs);
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);

        $devPackages = array();
        foreach ($ops as $op) {
            if ($op->getJobType() === 'uninstall') {
                $devPackages[] = $op->getPackage();
            }
        }

        return $devPackages;
    }

    /**
     * @return OperationInterface[] filtered operations, dev packages are uninstalled and all operations on them ignored
     */
    private function filterDevPackageOperations(array $devPackages, array $operations, RepositoryInterface $localRepo)
    {
        $finalOps = array();
        $packagesToSkip = array();
        foreach ($devPackages as $pkg) {
            $packagesToSkip[$pkg->getName()] = true;
            if ($installedDevPkg = $localRepo->findPackage($pkg->getName(), '*')) {
                if ($installedDevPkg instanceof AliasPackage) {
                    $finalOps[] = new MarkAliasUninstalledOperation($installedDevPkg, 'non-dev install removing it');
                    $installedDevPkg = $installedDevPkg->getAliasOf();
                }
                $finalOps[] = new UninstallOperation($installedDevPkg, 'non-dev install removing it');
            }
        }

        // skip operations applied on dev packages
        foreach ($operations as $op) {
            $package = $op->getJobType() === 'update' ? $op->getTargetPackage() : $op->getPackage();
            if (isset($packagesToSkip[$package->getName()])) {
                continue;
            }

            $finalOps[] = $op;
        }

        return $finalOps;
    }

    /**
     * Workaround: if your packages depend on plugins, we must be sure
     * that those are installed / updated first; else it would lead to packages
     * being installed multiple times in different folders, when running Composer
     * twice.
     *
     * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147,
     * it at least fixes the symptoms and makes usage of composer possible (again)
     * in such scenarios.
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function movePluginsToFront(array $operations)
    {
        $pluginsNoDeps = array();
        $pluginsWithDeps = array();
        $pluginRequires = array();

        foreach (array_reverse($operations, true) as $idx => $op) {
            if ($op instanceof InstallOperation) {
                $package = $op->getPackage();
            } elseif ($op instanceof UpdateOperation) {
                $package = $op->getTargetPackage();
            } else {
                continue;
            }

            // is this package a plugin?
            $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';

            // is this a plugin or a dependency of a plugin?
            if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) {
                // get the package's requires, but filter out any platform requirements or 'composer-plugin-api'
                $requires = array_filter(array_keys($package->getRequires()), function ($req) {
                    return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req);
                });

                // is this a plugin with no meaningful dependencies?
                if ($isPlugin && !count($requires)) {
                    // plugins with no dependencies go to the very front
                    array_unshift($pluginsNoDeps, $op);
                } else {
                    // capture the requirements for this package so those packages will be moved up as well
                    $pluginRequires = array_merge($pluginRequires, $requires);
                    // move the operation to the front
                    array_unshift($pluginsWithDeps, $op);
                }

                unset($operations[$idx]);
            }
        }

        return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations);
    }

    /**
     * Removals of packages should be executed before installations in
     * case two packages resolve to the same path (due to custom installers)
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function moveUninstallsToFront(array $operations)
    {
        $uninstOps = array();
        foreach ($operations as $idx => $op) {
            if ($op instanceof UninstallOperation) {
                $uninstOps[] = $op;
                unset($operations[$idx]);
            }
        }

        return array_merge($uninstOps, $operations);
    }

    /**
     * @return RepositoryInterface
     */
    private function createInstalledRepo(RepositoryInterface $localRepo, PlatformRepository $platformRepo)
    {
        // clone root package to have one in the installed repo that does not require anything
        // we don't want it to be uninstallable, but its requirements should not conflict
        // with the lock file for example
        $installedRootPackage = clone $this->package;
        $installedRootPackage->setRequires(array());
        $installedRootPackage->setDevRequires(array());

        $repos = array(
            $localRepo,
            new InstalledArrayRepository(array($installedRootPackage)),
            $platformRepo,
        );
        $installedRepo = new CompositeRepository($repos);
        if ($this->additionalInstalledRepository) {
            $installedRepo->addRepository($this->additionalInstalledRepository);
        }

        return $installedRepo;
    }

    /**
     * @param  RepositoryInterface|null $lockedRepository
     * @return Pool
     */
    private function createPool(RepositoryInterface $lockedRepository = null)
    {
        if ($this->update) {
            $minimumStability = $this->package->getMinimumStability();
            $stabilityFlags = $this->package->getStabilityFlags();

            $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires());
        } else {
            $minimumStability = $this->locker->getMinimumStability();
            $stabilityFlags = $this->locker->getStabilityFlags();

            $requires = array();
            foreach ($lockedRepository->getPackages() as $package) {
                $constraint = new Constraint('=', $package->getVersion());
                $constraint->setPrettyString($package->getPrettyVersion());
                $requires[$package->getName()] = $constraint;
            }
        }

        $rootConstraints = array();
        foreach ($requires as $req => $constraint) {
            // skip platform requirements from the root package to avoid filtering out existing platform packages
            if ($this->ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) {
                continue;
            }
            if ($constraint instanceof Link) {
                $rootConstraints[$req] = $constraint->getConstraint();
            } else {
                $rootConstraints[$req] = $constraint;
            }
        }

        return new Pool($minimumStability, $stabilityFlags, $rootConstraints);
    }

    /**
     * @return DefaultPolicy
     */
    private function createPolicy()
    {
        $preferStable = null;
        $preferLowest = null;
        if (!$this->update) {
            $preferStable = $this->locker->getPreferStable();
            $preferLowest = $this->locker->getPreferLowest();
        }
        // old lock file without prefer stable/lowest will return null
        // so in this case we use the composer.json info
        if (null === $preferStable) {
            $preferStable = $this->preferStable || $this->package->getPreferStable();
        }
        if (null === $preferLowest) {
            $preferLowest = $this->preferLowest;
        }

        return new DefaultPolicy($preferStable, $preferLowest);
    }

    /**
     * @param  RootPackageInterface $rootPackage
     * @param  PlatformRepository   $platformRepo
     * @return Request
     */
    private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
    {
        $request = new Request();

        $constraint = new Constraint('=', $rootPackage->getVersion());
        $constraint->setPrettyString($rootPackage->getPrettyVersion());
        $request->install($rootPackage->getName(), $constraint);

        $fixedPackages = $platformRepo->getPackages();
        if ($this->additionalInstalledRepository) {
            $additionalFixedPackages = $this->additionalInstalledRepository->getPackages();
            $fixedPackages = array_merge($fixedPackages, $additionalFixedPackages);
        }

        // fix the version of all platform packages + additionally installed packages
        // to prevent the solver trying to remove or update those
        $provided = $rootPackage->getProvides();
        foreach ($fixedPackages as $package) {
            $constraint = new Constraint('=', $package->getVersion());
            $constraint->setPrettyString($package->getPrettyVersion());

            // skip platform packages that are provided by the root package
            if ($package->getRepository() !== $platformRepo
                || !isset($provided[$package->getName()])
                || !$provided[$package->getName()]->getConstraint()->matches($constraint)
            ) {
                $request->fix($package->getName(), $constraint);
            }
        }

        return $request;
    }

    /**
     * @param  WritableRepositoryInterface $localRepo
     * @param  Pool                        $pool
     * @param  PolicyInterface             $policy
     * @param  array                       $repositories
     * @param  RepositoryInterface         $installedRepo
     * @param  RepositoryInterface         $lockedRepository
     * @param  string                      $task
     * @param  array|null                  $operations
     * @return array
     */
    private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null)
    {
        if ($task === 'force-updates' && null === $operations) {
            throw new \InvalidArgumentException('Missing operations argument');
        }
        if ($task === 'force-links') {
            $operations = array();
        }

        if ($this->update && $this->updateWhitelist) {
            $currentPackages = $this->getCurrentPackages($installedRepo);
        }

        foreach ($localRepo->getCanonicalPackages() as $package) {
            // skip non-dev packages
            if (!$package->isDev()) {
                continue;
            }

            // skip packages that will be updated/uninstalled
            foreach ($operations as $operation) {
                if (('update' === $operation->getJobType() && $operation->getInitialPackage()->equals($package))
                    || ('uninstall' === $operation->getJobType() && $operation->getPackage()->equals($package))
                ) {
                    continue 2;
                }
            }

            if ($this->update) {
                // skip package if the allow list is enabled and it is not in it
                if ($this->updateWhitelist && !$this->isUpdateable($package)) {
                    // check if non-updateable packages are out of date compared to the lock file to ensure we don't corrupt it
                    foreach ($currentPackages as $curPackage) {
                        if ($curPackage->isDev() && $curPackage->getName() === $package->getName() && $curPackage->getVersion() === $package->getVersion()) {
                            if ($task === 'force-links') {
                                $package->setRequires($curPackage->getRequires());
                                $package->setConflicts($curPackage->getConflicts());
                                $package->setProvides($curPackage->getProvides());
                                $package->setReplaces($curPackage->getReplaces());
                            } elseif ($task === 'force-updates') {
                                if (($curPackage->getSourceReference() && $curPackage->getSourceReference() !== $package->getSourceReference())
                                    || ($curPackage->getDistReference() && $curPackage->getDistReference() !== $package->getDistReference())
                                ) {
                                    $operations[] = new UpdateOperation($package, $curPackage);
                                }
                            }

                            break;
                        }
                    }

                    continue;
                }

                // find similar packages (name/version) in all repositories
                $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()));
                foreach ($matches as $index => $match) {
                    // skip local packages
                    if (!in_array($match->getRepository(), $repositories, true)) {
                        unset($matches[$index]);
                        continue;
                    }

                    // skip providers/replacers
                    if ($match->getName() !== $package->getName()) {
                        unset($matches[$index]);
                        continue;
                    }

                    $matches[$index] = $match->getId();
                }

                // select preferred package according to policy rules
                if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) {
                    $newPackage = $pool->literalToPackage($matches[0]);

                    if ($task === 'force-links' && $newPackage) {
                        $package->setRequires($newPackage->getRequires());
                        $package->setConflicts($newPackage->getConflicts());
                        $package->setProvides($newPackage->getProvides());
                        $package->setReplaces($newPackage->getReplaces());
                    }

                    if (
                        $task === 'force-updates'
                        && $newPackage
                        && (
                            ($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference())
                            || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference())
                        )
                    ) {
                        $operations[] = new UpdateOperation($package, $newPackage);

                        continue;
                    }
                }

                if ($task === 'force-updates') {
                    // force installed package to update to referenced version in root package if it does not match the installed version
                    $references = $this->package->getReferences();

                    if (isset($references[$package->getName()]) && $references[$package->getName()] !== $package->getSourceReference()) {
                        // changing the source ref to update to will be handled in the operations loop
                        $operations[] = new UpdateOperation($package, clone $package);
                    }
                }
            } else {
                // force update to locked version if it does not match the installed version
                foreach ($lockedRepository->findPackages($package->getName()) as $lockedPackage) {
                    if ($lockedPackage->isDev() && $lockedPackage->getVersion() === $package->getVersion()) {
                        if ($task === 'force-links') {
                            $package->setRequires($lockedPackage->getRequires());
                            $package->setConflicts($lockedPackage->getConflicts());
                            $package->setProvides($lockedPackage->getProvides());
                            $package->setReplaces($lockedPackage->getReplaces());
                        } elseif ($task === 'force-updates') {
                            if (($lockedPackage->getSourceReference() && $lockedPackage->getSourceReference() !== $package->getSourceReference())
                                || ($lockedPackage->getDistReference() && $lockedPackage->getDistReference() !== $package->getDistReference())
                            ) {
                                $operations[] = new UpdateOperation($package, $lockedPackage);
                            }
                        }

                        break;
                    }
                }
            }
        }

        return $operations;
    }

    /**
     * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback
     * @param  RepositoryInterface $installedRepo
     * @return array
     */
    private function getCurrentPackages($installedRepo)
    {
        if ($this->locker->isLocked()) {
            try {
                return $this->locker->getLockedRepository(true)->getPackages();
            } catch (\RuntimeException $e) {
                // fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
                return $this->locker->getLockedRepository()->getPackages();
            }
        }

        return $installedRepo->getPackages();
    }

    /**
     * @return array
     */
    private function getRootAliases()
    {
        if ($this->update) {
            $aliases = $this->package->getAliases();
        } else {
            $aliases = $this->locker->getAliases();
        }

        $normalizedAliases = array();

        foreach ($aliases as $alias) {
            $normalizedAliases[$alias['package']][$alias['version']] = array(
                'alias' => $alias['alias'],
                'alias_normalized' => $alias['alias_normalized'],
            );
        }

        return $normalizedAliases;
    }

    /**
     * @param Pool                        $pool
     * @param PolicyInterface             $policy
     * @param WritableRepositoryInterface $localRepo
     * @param array                       $repositories
     */
    private function processPackageUrls($pool, $policy, $localRepo, $repositories)
    {
        if (!$this->update) {
            return;
        }

        $rootRefs = $this->package->getReferences();

        foreach ($localRepo->getCanonicalPackages() as $package) {
            // find similar packages (name/version) in all repositories
            $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()));
            foreach ($matches as $index => $match) {
                // skip local packages
                if (!in_array($match->getRepository(), $repositories, true)) {
                    unset($matches[$index]);
                    continue;
                }

                // skip providers/replacers
                if ($match->getName() !== $package->getName()) {
                    unset($matches[$index]);
                    continue;
                }

                $matches[$index] = $match->getId();
            }

            // select preferred package according to policy rules
            if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) {
                $newPackage = $pool->literalToPackage($matches[0]);

                // update the dist and source URLs
                $sourceUrl = $package->getSourceUrl();
                $newSourceUrl = $newPackage->getSourceUrl();
                $newReference = $newPackage->getSourceReference();

                if ($package->isDev() && isset($rootRefs[$package->getName()]) && $package->getSourceReference() === $rootRefs[$package->getName()]) {
                    $newReference = $rootRefs[$package->getName()];
                }

                $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum());

                if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) {
                    $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned());
                }

                $package->setDistMirrors($newPackage->getDistMirrors());
                $package->setSourceMirrors($newPackage->getSourceMirrors());
                $package->setTransportOptions($newPackage->getTransportOptions());
            }
        }
    }

    private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl, $distType, $distShaSum)
    {
        $oldSourceRef = $package->getSourceReference();

        if ($package->getSourceUrl() !== $sourceUrl) {
            $package->setSourceType($sourceType);
            $package->setSourceUrl($sourceUrl);
            $package->setSourceReference($sourceReference);
        }

        // only update dist url for github/bitbucket/gitlab dists as they use a combination of dist url + dist reference to install
        // but for other urls this is ambiguous and could result in bad outcomes
        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) {
            $package->setDistUrl($distUrl);
            $package->setDistType($distType);
            $package->setDistSha1Checksum($distShaSum);
            $this->updateInstallReferences($package, $sourceReference);
        }

        if ($this->updateWhitelist && !$this->isUpdateable($package)) {
            $this->updateInstallReferences($package, $oldSourceRef);
        }
    }

    private function updateInstallReferences(PackageInterface $package, $reference)
    {
        if (!$reference) {
            return;
        }

        $package->setSourceReference($reference);

        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) {
            $package->setDistReference($reference);
            $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl()));
        } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it
            $package->setDistReference($reference);
        }
    }

    /**
     * @param PlatformRepository $platformRepo
     * @param array              $aliases
     */
    private function aliasPlatformPackages(PlatformRepository $platformRepo, $aliases)
    {
        foreach ($aliases as $package => $versions) {
            foreach ($versions as $version => $alias) {
                $packages = $platformRepo->findPackages($package, $version);
                foreach ($packages as $package) {
                    $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    $aliasPackage->setRootPackageAlias(true);
                    $platformRepo->addPackage($aliasPackage);
                }
            }
        }
    }

    /**
     * @param  PackageInterface $package
     * @return bool
     */
    private function isUpdateable(PackageInterface $package)
    {
        if (!$this->updateWhitelist) {
            throw new \LogicException('isUpdateable should only be called when an allow list is present');
        }

        foreach ($this->updateWhitelist as $pattern => $void) {
            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
            if (preg_match($patternRegexp, $package->getName())) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  array $links
     * @return array
     */
    private function extractPlatformRequirements($links)
    {
        $platformReqs = array();
        foreach ($links as $link) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                $platformReqs[$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $platformReqs;
    }

    /**
     * Adds all dependencies of the update allow list to the allow list, too.
     *
     * Packages which are listed as requirements in the root package will be
     * skipped including their dependencies, unless they are listed in the
     * update allow list themselves or $whitelistAllDependencies is true.
     *
     * @param RepositoryInterface $localOrLockRepo Use the locked repo if available, otherwise installed repo will do
     *                                             As we want the most accurate package list to work with, and installed
     *                                             repo might be empty but locked repo will always be current.
     * @param array               $rootRequires    An array of links to packages in require of the root package
     * @param array               $rootDevRequires An array of links to packages in require-dev of the root package
     */
    private function allowListUpdateDependencies($localOrLockRepo, array $rootRequires, array $rootDevRequires)
    {
        if (!$this->updateWhitelist) {
            return;
        }

        $rootRequires = array_merge($rootRequires, $rootDevRequires);

        $skipPackages = array();
        if (!$this->whitelistAllDependencies) {
            foreach ($rootRequires as $require) {
                $skipPackages[$require->getTarget()] = true;
            }
        }

        $pool = new Pool('dev');
        $pool->addRepository($localOrLockRepo);

        $seen = array();

        $rootRequiredPackageNames = array_keys($rootRequires);

        foreach ($this->updateWhitelist as $packageName => $void) {
            $packageQueue = new \SplQueue;
            $nameMatchesRequiredPackage = false;

            $depPackages = $pool->whatProvides($packageName);
            $matchesByPattern = array();
            // check if the name is a glob pattern that did not match directly
            if (empty($depPackages)) {
                // add any installed package matching the allow listed name/pattern
                $allowListPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$');
                foreach ($localOrLockRepo->search($allowListPatternSearchRegexp) as $installedPackage) {
                    $matchesByPattern[] = $pool->whatProvides($installedPackage['name']);
                }

                // add root requirements which match the allow listed name/pattern
                $allowListPatternRegexp = BasePackage::packageNameToRegexp($packageName);
                foreach ($rootRequiredPackageNames as $rootRequiredPackageName) {
                    if (preg_match($allowListPatternRegexp, $rootRequiredPackageName)) {
                        $nameMatchesRequiredPackage = true;
                        break;
                    }
                }
            }

            if (!empty($matchesByPattern)) {
                $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern));
            }

            if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) {
                $this->io->writeError('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
            }

            foreach ($depPackages as $depPackage) {
                $packageQueue->enqueue($depPackage);
            }

            while (!$packageQueue->isEmpty()) {
                $package = $packageQueue->dequeue();
                if (isset($seen[$package->getId()])) {
                    continue;
                }

                $seen[$package->getId()] = true;
                $this->updateWhitelist[$package->getName()] = true;

                if (!$this->whitelistDependencies && !$this->whitelistAllDependencies) {
                    continue;
                }

                $requires = $package->getRequires();

                foreach ($requires as $require) {
                    $requirePackages = $pool->whatProvides($require->getTarget());

                    foreach ($requirePackages as $requirePackage) {
                        if (isset($this->updateWhitelist[$requirePackage->getName()])) {
                            continue;
                        }

                        if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) {
                            $this->io->writeError('<warning>Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly allowed. Ignoring.</warning>');
                            continue;
                        }

                        $packageQueue->enqueue($requirePackage);
                    }
                }
            }
        }
    }

    /**
     * Replace local repositories with InstalledArrayRepository instances
     *
     * This is to prevent any accidental modification of the existing repos on disk
     *
     * @param RepositoryManager $rm
     */
    private function mockLocalRepositories(RepositoryManager $rm)
    {
        $packages = array();
        foreach ($rm->getLocalRepository()->getPackages() as $package) {
            $packages[(string) $package] = clone $package;
        }
        foreach ($packages as $key => $package) {
            if ($package instanceof AliasPackage) {
                $alias = (string) $package->getAliasOf();
                $packages[$key] = new AliasPackage($packages[$alias], $package->getVersion(), $package->getPrettyVersion());
            }
        }
        $rm->setLocalRepository(
            new InstalledArrayRepository($packages)
        );
    }

    /**
     * Create Installer
     *
     * @param  IOInterface $io
     * @param  Composer    $composer
     * @return Installer
     */
    public static function create(IOInterface $io, Composer $composer)
    {
        return new static(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator()
        );
    }

    /**
     * @param  RepositoryInterface $additionalInstalledRepository
     * @return $this
     */
    public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository)
    {
        $this->additionalInstalledRepository = $additionalInstalledRepository;

        return $this;
    }

    /**
     * Whether to run in drymode or not
     *
     * @param  bool      $dryRun
     * @return Installer
     */
    public function setDryRun($dryRun = true)
    {
        $this->dryRun = (bool) $dryRun;

        return $this;
    }

    /**
     * Checks, if this is a dry run (simulation mode).
     *
     * @return bool
     */
    public function isDryRun()
    {
        return $this->dryRun;
    }

    /**
     * prefer source installation
     *
     * @param  bool      $preferSource
     * @return Installer
     */
    public function setPreferSource($preferSource = true)
    {
        $this->preferSource = (bool) $preferSource;

        return $this;
    }

    /**
     * prefer dist installation
     *
     * @param  bool      $preferDist
     * @return Installer
     */
    public function setPreferDist($preferDist = true)
    {
        $this->preferDist = (bool) $preferDist;

        return $this;
    }

    /**
     * Whether or not generated autoloader are optimized
     *
     * @param  bool      $optimizeAutoloader
     * @return Installer
     */
    public function setOptimizeAutoloader($optimizeAutoloader = false)
    {
        $this->optimizeAutoloader = (bool) $optimizeAutoloader;
        if (!$this->optimizeAutoloader) {
            // Force classMapAuthoritative off when not optimizing the
            // autoloader
            $this->setClassMapAuthoritative(false);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers the class map
     * authoritative.
     *
     * @param  bool      $classMapAuthoritative
     * @return Installer
     */
    public function setClassMapAuthoritative($classMapAuthoritative = false)
    {
        $this->classMapAuthoritative = (bool) $classMapAuthoritative;
        if ($this->classMapAuthoritative) {
            // Force optimizeAutoloader when classmap is authoritative
            $this->setOptimizeAutoloader(true);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers APCu caching.
     *
     * @param  bool      $apcuAutoloader
     * @return Installer
     */
    public function setApcuAutoloader($apcuAutoloader = false)
    {
        $this->apcuAutoloader = (bool) $apcuAutoloader;

        return $this;
    }

    /**
     * update packages
     *
     * @param  bool      $update
     * @return Installer
     */
    public function setUpdate($update = true)
    {
        $this->update = (bool) $update;

        return $this;
    }

    /**
     * enables dev packages
     *
     * @param  bool      $devMode
     * @return Installer
     */
    public function setDevMode($devMode = true)
    {
        $this->devMode = (bool) $devMode;

        return $this;
    }

    /**
     * set whether to run autoloader or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $dumpAutoloader
     * @return Installer
     */
    public function setDumpAutoloader($dumpAutoloader = true)
    {
        $this->dumpAutoloader = (bool) $dumpAutoloader;

        return $this;
    }

    /**
     * set whether to run scripts or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $runScripts
     * @return Installer
     */
    public function setRunScripts($runScripts = true)
    {
        $this->runScripts = (bool) $runScripts;

        return $this;
    }

    /**
     * set the config instance
     *
     * @param  Config    $config
     * @return Installer
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;

        return $this;
    }

    /**
     * run in verbose mode
     *
     * @param  bool      $verbose
     * @return Installer
     */
    public function setVerbose($verbose = true)
    {
        $this->verbose = (bool) $verbose;

        return $this;
    }

    /**
     * Checks, if running in verbose mode.
     *
     * @return bool
     */
    public function isVerbose()
    {
        return $this->verbose;
    }

    /**
     * set ignore Platform Package requirements
     *
     * @param  bool      $ignorePlatformReqs
     * @return Installer
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs = false)
    {
        $this->ignorePlatformReqs = (bool) $ignorePlatformReqs;

        return $this;
    }

    /**
     * restrict the update operation to a few packages, all other packages
     * that are already installed will be kept at their current version
     *
     * @deprecated use setUpdateAllowList instead
     *
     * @param  array     $packages
     * @return Installer
     */
    public function setUpdateWhitelist(array $packages)
    {
        $this->updateWhitelist = array_flip(array_map('strtolower', $packages));

        return $this;
    }

    /**
     * restrict the update operation to a few packages, all other packages
     * that are already installed will be kept at their current version
     *
     * @param  array     $packages
     * @return Installer
     */
    public function setUpdateAllowList(array $packages)
    {
        // call original method for BC
        return $this->setUpdateWhitelist($packages);
    }

    /**
     * @deprecated use setAllowListTransitiveDependencies instead
     */
    public function setWhitelistDependencies($updateDependencies = true)
    {
        return $this->setWhitelistTransitiveDependencies($updateDependencies);
    }

    /**
     * Should dependencies of allowed packages (but not direct dependencies) be updated?
     *
     * This will NOT allow list any dependencies that are also directly defined
     * in the root package.
     *
     * @deprecated use setAllowListTransitiveDependencies instead
     *
     * @param  bool      $updateTransitiveDependencies
     * @return Installer
     */
    public function setWhitelistTransitiveDependencies($updateTransitiveDependencies = true)
    {
        $this->whitelistDependencies = (bool) $updateTransitiveDependencies;

        return $this;
    }

    /**
     * Should dependencies of allowed packages (but not direct dependencies) be updated?
     *
     * This will NOT allow list any dependencies that are also directly defined
     * in the root package.
     *
     * @param  bool      $updateTransitiveDependencies
     * @return Installer
     */
    public function setAllowListTransitiveDependencies($updateTransitiveDependencies = true)
    {
        // call original method for BC
        return $this->setWhitelistTransitiveDependencies($updateTransitiveDependencies);
    }

    /**
     * Should all dependencies of allowed packages be updated recursively?
     *
     * This will allow list any dependencies of the allow listed packages, including
     * those defined in the root package.
     *
     * @deprecated use setAllowListAllDependencies instead
     *
     * @param  bool      $updateAllDependencies
     * @return Installer
     */
    public function setWhitelistAllDependencies($updateAllDependencies = true)
    {
        $this->whitelistAllDependencies = (bool) $updateAllDependencies;

        return $this;
    }

    /**
     * Should all dependencies of allowed packages be updated recursively?
     *
     * This will allow list any dependencies of the allow listed packages, including
     * those defined in the root package.
     *
     * @param  bool      $updateAllDependencies
     * @return Installer
     */
    public function setAllowListAllDependencies($updateAllDependencies = true)
    {
        // call original method for BC
        return $this->setWhitelistAllDependencies($updateAllDependencies);
    }

    /**
     * Should packages be preferred in a stable version when updating?
     *
     * @param  bool      $preferStable
     * @return Installer
     */
    public function setPreferStable($preferStable = true)
    {
        $this->preferStable = (bool) $preferStable;

        return $this;
    }

    /**
     * Should packages be preferred in a lowest version when updating?
     *
     * @param  bool      $preferLowest
     * @return Installer
     */
    public function setPreferLowest($preferLowest = true)
    {
        $this->preferLowest = (bool) $preferLowest;

        return $this;
    }

    /**
     * Should the lock file be updated when updating?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $writeLock
     * @return Installer
     */
    public function setWriteLock($writeLock = true)
    {
        $this->writeLock = (bool) $writeLock;

        return $this;
    }

    /**
     * Should the operations (package install, update and removal) be executed on disk?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $executeOperations
     * @return Installer
     */
    public function setExecuteOperations($executeOperations = true)
    {
        $this->executeOperations = (bool) $executeOperations;

        return $this;
    }

    /**
     * Should suggestions be skipped?
     *
     * @param  bool      $skipSuggest
     * @return Installer
     */
    public function setSkipSuggest($skipSuggest = true)
    {
        $this->skipSuggest = (bool) $skipSuggest;

        return $this;
    }

    /**
     * Disables plugins.
     *
     * Call this if you want to ensure that third-party code never gets
     * executed. The default is to automatically install, and execute
     * custom third-party installers.
     *
     * @return Installer
     */
    public function disablePlugins()
    {
        $this->installationManager->disablePlugins();

        return $this;
    }

    /**
     * @param  SuggestedPackagesReporter $suggestedPackagesReporter
     * @return Installer
     */
    public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter)
    {
        $this->suggestedPackagesReporter = $suggestedPackagesReporter;

        return $this;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;
use Composer\Util\Platform;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Process\ExecutableFinder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PlatformRepository extends ArrayRepository
{
    const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD';

    private $versionParser;

    /**
     * Defines overrides so that the platform can be mocked
     *
     * Should be an array of package name => version number mappings
     *
     * @var array
     */
    private $overrides = array();

    private $process;

    public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null)
    {
        $this->process = $process === null ? (new ProcessExecutor()) : $process;
        foreach ($overrides as $name => $version) {
            $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
        }
        parent::__construct($packages);
    }

    protected function initialize()
    {
        parent::initialize();

        $this->versionParser = new VersionParser();

        // Add each of the override versions as options.
        // Later we might even replace the extensions instead.
        foreach ($this->overrides as $override) {
            // Check that it's a platform package.
            if (!preg_match(self::PLATFORM_PACKAGE_REGEX, $override['name'])) {
                throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
            }

            $this->addOverriddenPackage($override);
        }

        $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion);
        $composerPluginApi->setDescription('The Composer Plugin API');
        $this->addPackage($composerPluginApi);

        $prettyVersion = Composer::RUNTIME_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion);
        $composerRuntimeApi->setDescription('The Composer Runtime API');
        $this->addPackage($composerRuntimeApi);

        try {
            $prettyVersion = PHP_VERSION;
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION);
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $php = new CompletePackage('php', $version, $prettyVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        if (PHP_DEBUG) {
            $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion);
            $phpdebug->setDescription('The PHP interpreter, with debugging symbols');
            $this->addPackage($phpdebug);
        }

        if (defined('PHP_ZTS') && PHP_ZTS) {
            $phpzts = new CompletePackage('php-zts', $version, $prettyVersion);
            $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety');
            $this->addPackage($phpzts);
        }

        if (PHP_INT_SIZE === 8) {
            $php64 = new CompletePackage('php-64bit', $version, $prettyVersion);
            $php64->setDescription('The PHP interpreter, 64bit');
            $this->addPackage($php64);
        }

        // The AF_INET6 constant is only defined if ext-sockets is available but
        // IPv6 support might still be available.
        if (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) {
            $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion);
            $phpIpv6->setDescription('The PHP interpreter, with IPv6 support');
            $this->addPackage($phpIpv6);
        }

        $loadedExtensions = get_loaded_extensions();

        // Extensions scanning
        foreach ($loadedExtensions as $name) {
            if (in_array($name, array('standard', 'Core'))) {
                continue;
            }

            $reflExt = new \ReflectionExtension($name);
            $prettyVersion = $reflExt->getVersion();
            $this->addExtension($name, $prettyVersion);
        }

        // Check for Xdebug in a restarted process
        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) {
            $this->addExtension('xdebug', $prettyVersion);
        }

        // Another quick loop, just for possible libraries
        // Doing it this way to know that functions or constants exist before
        // relying on them.
        foreach ($loadedExtensions as $name) {
            $prettyVersion = null;
            $description = 'The '.$name.' PHP library';
            switch ($name) {
                case 'curl':
                    $curlVersion = curl_version();
                    $prettyVersion = $curlVersion['version'];
                    break;

                case 'iconv':
                    $prettyVersion = ICONV_VERSION;
                    break;

                case 'intl':
                    $name = 'ICU';
                    if (defined('INTL_ICU_VERSION')) {
                        $prettyVersion = INTL_ICU_VERSION;
                    } else {
                        $reflector = new \ReflectionExtension('intl');

                        ob_start();
                        $reflector->info();
                        $output = ob_get_clean();

                        preg_match('/^ICU version => (.*)$/m', $output, $matches);
                        $prettyVersion = $matches[1];
                    }

                    break;

                case 'imagick':
                    $imagick = new \Imagick();
                    $imageMagickVersion = $imagick->getVersion();
                    // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
                    // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
                    preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches);
                    if (isset($matches[2])) {
                        $prettyVersion = "{$matches[1]}.{$matches[2]}";
                    } else {
                        $prettyVersion = $matches[1];
                    }
                    break;

                case 'libxml':
                    $prettyVersion = LIBXML_DOTTED_VERSION;
                    break;

                case 'openssl':
                    $prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) {
                        if (empty($match[2])) {
                            return $match[1];
                        }

                        // OpenSSL versions add another letter when they reach Z.
                        // e.g. OpenSSL 0.9.8zh 3 Dec 2015

                        if (!preg_match('{^z*[a-z]$}', $match[2])) {
                            // 0.9.8abc is garbage
                            return 0;
                        }

                        $len = strlen($match[2]);
                        $patchVersion = ($len - 1) * 26; // All Z
                        $patchVersion += ord($match[2][$len - 1]) - 96;

                        return $match[1].'.'.$patchVersion;
                    }, OPENSSL_VERSION_TEXT);

                    $description = OPENSSL_VERSION_TEXT;
                    break;

                case 'pcre':
                    $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION);
                    break;

                case 'uuid':
                    $prettyVersion = phpversion('uuid');
                    break;

                case 'xsl':
                    $prettyVersion = LIBXSLT_DOTTED_VERSION;
                    break;

                default:
                    // None handled extensions have no special cases, skip
                    continue 2;
            }

            try {
                $version = $this->versionParser->normalize($prettyVersion);
            } catch (\UnexpectedValueException $e) {
                continue;
            }

            $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
            $lib->setDescription($description);
            $this->addPackage($lib);
        }

        $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
        if ($hhvmVersion === null && !Platform::isWindows()) {
            $finder = new ExecutableFinder();
            $hhvm = $finder->find('hhvm');
            if ($hhvm !== null) {
                $exitCode = $this->process->execute(
                    ProcessExecutor::escape($hhvm).
                    ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
                    $hhvmVersion
                );
                if ($exitCode !== 0) {
                    $hhvmVersion = null;
                }
            }
        }
        if ($hhvmVersion) {
            try {
                $prettyVersion = $hhvmVersion;
                $version = $this->versionParser->normalize($prettyVersion);
            } catch (\UnexpectedValueException $e) {
                $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', $hhvmVersion);
                $version = $this->versionParser->normalize($prettyVersion);
            }

            $hhvm = new CompletePackage('hhvm', $version, $prettyVersion);
            $hhvm->setDescription('The HHVM Runtime (64bit)');
            $this->addPackage($hhvm);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function addPackage(PackageInterface $package)
    {
        // Skip if overridden
        if (isset($this->overrides[$package->getName()])) {
            $overrider = $this->findPackage($package->getName(), '*');
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');

            return;
        }

        // Skip if PHP is overridden and we are adding a php-* package
        if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) {
            $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName());
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');

            return;
        }

        parent::addPackage($package);
    }

    private function addOverriddenPackage(array $override, $name = null)
    {
        $version = $this->versionParser->normalize($override['version']);
        $package = new CompletePackage($name ?: $override['name'], $version, $override['version']);
        $package->setDescription('Package overridden via config.platform');
        $package->setExtra(array('config.platform' => true));
        parent::addPackage($package);

        return $package;
    }

    /**
     * Parses the version and adds a new package to the repository
     *
     * @param string      $name
     * @param null|string $prettyVersion
     */
    private function addExtension($name, $prettyVersion)
    {
        $extraDescription = null;

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $extraDescription = ' (actual version: '.$prettyVersion.')';
            if (preg_match('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
                $prettyVersion = $match[1];
            } else {
                $prettyVersion = '0';
            }
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $packageName = $this->buildPackageName($name);
        $ext = new CompletePackage($packageName, $version, $prettyVersion);
        $ext->setDescription('The '.$name.' PHP extension'.$extraDescription);
        $this->addPackage($ext);
    }

    private function buildPackageName($name)
    {
        return 'ext-' . str_replace(' ', '-', $name);
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\RemoteFilesystem;
use Composer\Json\JsonFile;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RepositoryFactory
{
    /**
     * @param  IOInterface $io
     * @param  Config      $config
     * @param  string      $repository
     * @param  bool        $allowFilesystem
     * @return array|mixed
     */
    public static function configFromString(IOInterface $io, Config $config, $repository, $allowFilesystem = false)
    {
        if (0 === strpos($repository, 'http')) {
            $repoConfig = array('type' => 'composer', 'url' => $repository);
        } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) {
            $json = new JsonFile($repository, Factory::createRemoteFilesystem($io, $config));
            $data = $json->read();
            if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
                $repoConfig = array('type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/'));
            } elseif ($allowFilesystem) {
                $repoConfig = array('type' => 'filesystem', 'json' => $json);
            } else {
                throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository.");
            }
        } elseif ('{' === substr($repository, 0, 1)) {
            // assume it is a json object that makes a repo config
            $repoConfig = JsonFile::parseJson($repository);
        } else {
            throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object.");
        }

        return $repoConfig;
    }

    /**
     * @param  IOInterface         $io
     * @param  Config              $config
     * @param  string              $repository
     * @param  bool                $allowFilesystem
     * @return RepositoryInterface
     */
    public static function fromString(IOInterface $io, Config $config, $repository, $allowFilesystem = false, RepositoryManager $rm = null)
    {
        $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem);

        return static::createRepo($io, $config, $repoConfig, $rm);
    }

    /**
     * @param  IOInterface         $io
     * @param  Config              $config
     * @param  array               $repoConfig
     * @return RepositoryInterface
     */
    public static function createRepo(IOInterface $io, Config $config, array $repoConfig, RepositoryManager $rm = null)
    {
        if (!$rm) {
            $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config));
        }
        $repos = static::createRepos($rm, array($repoConfig));

        return reset($repos);
    }

    /**
     * @param  IOInterface|null       $io
     * @param  Config|null            $config
     * @param  RepositoryManager|null $rm
     * @return RepositoryInterface[]
     */
    public static function defaultRepos(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null)
    {
        if (!$config) {
            $config = Factory::createConfig($io);
        }
        if ($io) {
            $io->loadConfiguration($config);
        }
        if (!$rm) {
            if (!$io) {
                throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
            }
            $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config));
        }

        return static::createRepos($rm, $config->getRepositories());
    }

    /**
     * @param  IOInterface       $io
     * @param  Config            $config
     * @param  EventDispatcher   $eventDispatcher
     * @param  RemoteFilesystem  $rfs
     * @return RepositoryManager
     */
    public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        $rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs);
        $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
        $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
        $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
        $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('hg-bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
        $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository');

        return $rm;
    }

    /**
     * @return RepositoryInterface[]
     */
    private static function createRepos(RepositoryManager $rm, array $repoConfigs)
    {
        $repos = array();

        foreach ($repoConfigs as $index => $repo) {
            if (is_string($repo)) {
                throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
            }
            if (!is_array($repo)) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
            }
            if (!isset($repo['type'])) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
            }

            $name = self::generateRepositoryName($index, $repo, $repos);
            if ($repo['type'] === 'filesystem') {
                $repos[$name] = new FilesystemRepository($repo['json']);
            } else {
                $repos[$name] = $rm->createRepository($repo['type'], $repo, $index);
            }
        }

        return $repos;
    }

    public static function generateRepositoryName($index, array $repo, array $existingRepos)
    {
        $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
        while (isset($existingRepos[$name])) {
            $name .= '2';
        }

        return $name;
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Installable repository interface.
 *
 * Just used to tag installed repositories so the base classes can act differently on Alias packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstalledRepositoryInterface extends WritableRepositoryInterface
{
}
<?php

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

namespace Composer\Repository;

use Composer\Downloader\TransportException;
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Package\Loader\LoaderInterface;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Semver\Constraint\Constraint;
use Composer\IO\IOInterface;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    protected $url;
    protected $packageName;
    protected $isVerbose;
    protected $isVeryVerbose;
    protected $io;
    protected $config;
    protected $versionParser;
    protected $type;
    protected $loader;
    protected $repoConfig;
    protected $branchErrorOccurred = false;
    private $drivers;
    /** @var VcsDriverInterface */
    private $driver;
    /** @var VersionCacheInterface */
    private $versionCache;
    private $emptyReferences = array();
    private $versionTransportExceptions = array();

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
    {
        parent::__construct();
        $this->drivers = $drivers ?: array(
            'github' => 'Composer\Repository\Vcs\GitHubDriver',
            'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
            'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'git' => 'Composer\Repository\Vcs\GitDriver',
            'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver',
            'hg' => 'Composer\Repository\Vcs\HgDriver',
            'perforce' => 'Composer\Repository\Vcs\PerforceDriver',
            'fossil' => 'Composer\Repository\Vcs\FossilDriver',
            // svn must be last because identifying a subversion server for sure is practically impossible
            'svn' => 'Composer\Repository\Vcs\SvnDriver',
        );

        $this->url = $repoConfig['url'];
        $this->io = $io;
        $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
        $this->isVerbose = $io->isVerbose();
        $this->isVeryVerbose = $io->isVeryVerbose();
        $this->config = $config;
        $this->repoConfig = $repoConfig;
        $this->versionCache = $versionCache;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setLoader(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    public function getDriver()
    {
        if ($this->driver) {
            return $this->driver;
        }

        if (isset($this->drivers[$this->type])) {
            $class = $this->drivers[$this->type];
            $this->driver = new $class($this->repoConfig, $this->io, $this->config);
            $this->driver->initialize();

            return $this->driver;
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config);
                $this->driver->initialize();

                return $this->driver;
            }
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url, true)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config);
                $this->driver->initialize();

                return $this->driver;
            }
        }
    }

    public function hadInvalidBranches()
    {
        return $this->branchErrorOccurred;
    }

    public function getEmptyReferences()
    {
        return $this->emptyReferences;
    }

    public function getVersionTransportExceptions()
    {
        return $this->versionTransportExceptions;
    }

    protected function initialize()
    {
        parent::initialize();

        $isVerbose = $this->isVerbose;
        $isVeryVerbose = $this->isVeryVerbose;

        $driver = $this->getDriver();
        if (!$driver) {
            throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url);
        }

        $this->versionParser = new VersionParser;
        if (!$this->loader) {
            $this->loader = new ArrayLoader($this->versionParser);
        }

        try {
            if ($driver->hasComposerFile($driver->getRootIdentifier())) {
                $data = $driver->getComposerInformation($driver->getRootIdentifier());
                $this->packageName = !empty($data['name']) ? $data['name'] : null;
            }
        } catch (\Exception $e) {
            if ($isVeryVerbose) {
                $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
            }
        }

        foreach ($driver->getTags() as $tag => $identifier) {
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            // strip the release- prefix from tags if present
            $tag = str_replace('release-', '', $tag);

            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            } elseif ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            if (!$parsedTag = $this->validateTag($tag)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                }
                continue;
            }

            try {
                if (!$data = $driver->getComposerInformation($identifier)) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // manually versioned package
                if (isset($data['version'])) {
                    $data['version_normalized'] = $this->versionParser->normalize($data['version']);
                } else {
                    // auto-versioned package, read value from tag
                    $data['version'] = $tag;
                    $data['version_normalized'] = $parsedTag;
                }

                // make sure tag packages have no -dev flag
                $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
                $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);

                // broken package, version doesn't match tag
                if ($data['version_normalized'] !== $parsedTag) {
                    if ($isVeryVerbose) {
                        if (preg_match('{(^dev-|[.-]?dev$)}i', $parsedTag)) {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes</warning>');
                        } else {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                        }
                    }
                    continue;
                }

                $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName;
                if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally</warning>');
                    }
                    continue;
                }

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                }

                $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
            } catch (\Exception $e) {
                if ($e instanceof TransportException) {
                    $this->versionTransportExceptions['tags'][$tag] = $e;
                    if ($e->getCode() === 404) {
                        $this->emptyReferences[] = $identifier;
                    }
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'</warning>');
                }
                continue;
            }
        }

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        $branches = $driver->getBranches();
        foreach ($branches as $branch => $identifier) {
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if ($branch === 'trunk' && isset($branches['master'])) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally</warning>');
                }
                continue;
            }

            if (!$parsedBranch = $this->validateBranch($branch)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                }
                continue;
            }

            // make sure branch packages have a dev flag
            if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
                $version = 'dev-' . $branch;
            } else {
                $prefix = substr($branch, 0, 1) === 'v' ? 'v' : '';
                $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
            }

            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            } elseif ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            try {
                if (!$data = $driver->getComposerInformation($identifier)) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // branches are always auto-versioned, read value from branch name
                $data['version'] = $version;
                $data['version_normalized'] = $parsedBranch;

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                }

                $packageData = $this->preProcess($driver, $data, $identifier);
                $package = $this->loader->load($packageData);
                if ($this->loader instanceof ValidatingArrayLoader && $this->loader->getWarnings()) {
                    throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData);
                }
                $this->addPackage($package);
            } catch (TransportException $e) {
                $this->versionTransportExceptions['branches'][$branch] = $e;
                if ($e->getCode() === 404) {
                    $this->emptyReferences[] = $identifier;
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)</warning>');
                }
                continue;
            } catch (\Exception $e) {
                if (!$isVeryVerbose) {
                    $this->io->writeError('');
                }
                $this->branchErrorOccurred = true;
                $this->io->writeError('<error>Skipped branch '.$branch.', '.$e->getMessage().'</error>');
                $this->io->writeError('');
                continue;
            }
        }
        $driver->cleanup();

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        if (!$this->getPackages()) {
            throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.');
        }
    }

    protected function preProcess(VcsDriverInterface $driver, array $data, $identifier)
    {
        // keep the name of the main identifier for all packages
        $dataPackageName = isset($data['name']) ? $data['name'] : null;
        $data['name'] = $this->packageName ?: $dataPackageName;

        if (!isset($data['dist'])) {
            $data['dist'] = $driver->getDist($identifier);
        }
        if (!isset($data['source'])) {
            $data['source'] = $driver->getSource($identifier);
        }

        return $data;
    }

    private function validateBranch($branch)
    {
        try {
            $normalizedBranch = $this->versionParser->normalizeBranch($branch);

            // validate that the branch name has no weird characters conflicting with constraints
            $this->versionParser->parseConstraints($normalizedBranch);

            return $normalizedBranch;
        } catch (\Exception $e) {
        }

        return false;
    }

    private function validateTag($version)
    {
        try {
            return $this->versionParser->normalize($version);
        } catch (\Exception $e) {
        }

        return false;
    }

    private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose)
    {
        if (!$this->versionCache) {
            return;
        }

        $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
        if ($cachedPackage === false) {
            if ($isVeryVerbose) {
                $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
            }

            return false;
        }

        if ($cachedPackage) {
            $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally</warning>');
                }
                $cachedPackage = null;
            }
        }

        if ($cachedPackage) {
            return $this->loader->load($cachedPackage);
        }

        return null;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\RootPackageInterface;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Link;

/**
 * Common ancestor class for generic repository functionality.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
abstract class BaseRepository implements RepositoryInterface
{
    /**
     * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
     * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
     * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
     * to the root package. That third value will be false in case a circular recursion was detected.
     *
     * @param  string|string[]          $needle        The package name(s) to inspect.
     * @param  ConstraintInterface|null $constraint    Optional constraint to filter by.
     * @param  bool                     $invert        Whether to invert matches to discover reasons for the package *NOT* to be installed.
     * @param  bool                     $recurse       Whether to recursively expand the requirement tree up to the root package.
     * @param  string[]                 $packagesFound Used internally when recurring
     * @return array                    An associative array of arrays as described above.
     */
    public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null)
    {
        $needles = array_map('strtolower', (array) $needle);
        $results = array();

        // initialize the array with the needles before any recursion occurs
        if (null === $packagesFound) {
            $packagesFound = $needles;
        }

        // locate root package for use below
        $rootPackage = null;
        foreach ($this->getPackages() as $package) {
            if ($package instanceof RootPackageInterface) {
                $rootPackage = $package;
                break;
            }
        }

        // Loop over all currently installed packages.
        foreach ($this->getPackages() as $package) {
            $links = $package->getRequires();

            // each loop needs its own "tree" as we want to show the complete dependent set of every needle
            // without warning all the time about finding circular deps
            $packagesInTree = $packagesFound;

            // Replacements are considered valid reasons for a package to be installed during forward resolution
            if (!$invert) {
                $links += $package->getReplaces();

                // On forward search, check if any replaced package was required and add the replaced
                // packages to the list of needles. Contrary to the cross-reference link check below,
                // replaced packages are the target of links.
                foreach ($package->getReplaces() as $link) {
                    foreach ($needles as $needle) {
                        if ($link->getSource() === $needle) {
                            if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                                // already displayed this node's dependencies, cutting short
                                if (in_array($link->getTarget(), $packagesInTree)) {
                                    $results[] = array($package, $link, false);
                                    continue;
                                }
                                $packagesInTree[] = $link->getTarget();
                                $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
                                $results[] = array($package, $link, $dependents);
                                $needles[] = $link->getTarget();
                            }
                        }
                    }
                }
            }

            // Require-dev is only relevant for the root package
            if ($package instanceof RootPackageInterface) {
                $links += $package->getDevRequires();
            }

            // Cross-reference all discovered links to the needles
            foreach ($links as $link) {
                foreach ($needles as $needle) {
                    if ($link->getTarget() === $needle) {
                        if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                            // already displayed this node's dependencies, cutting short
                            if (in_array($link->getSource(), $packagesInTree)) {
                                $results[] = array($package, $link, false);
                                continue;
                            }
                            $packagesInTree[] = $link->getSource();
                            $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
                            $results[] = array($package, $link, $dependents);
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles against installed packages
            if ($invert && in_array($package->getName(), $needles)) {
                foreach ($package->getConflicts() as $link) {
                    foreach ($this->findPackages($link->getTarget()) as $pkg) {
                        $version = new Constraint('=', $pkg->getVersion());
                        if ($link->getConstraint()->matches($version) === $invert) {
                            $results[] = array($package, $link, false);
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles' requirements against installed packages
            if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
                foreach ($package->getRequires() as $link) {
                    if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                        if ($this->findPackage($link->getTarget(), $link->getConstraint())) {
                            continue;
                        }

                        $platformPkg = $this->findPackage($link->getTarget(), '*');
                        $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing';
                        $results[] = array($package, new Link($package->getName(), $link->getTarget(), null, 'requires', $link->getPrettyConstraint().' '.$description), false);

                        continue;
                    }

                    foreach ($this->getPackages() as $pkg) {
                        if (!in_array($link->getTarget(), $pkg->getNames())) {
                            continue;
                        }

                        $version = new Constraint('=', $pkg->getVersion());

                        if ($link->getTarget() !== $pkg->getName()) {
                            foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) {
                                if ($link->getTarget() === $prov->getTarget()) {
                                    $version = $prov->getConstraint();
                                    break;
                                }
                            }
                        }

                        if (!$link->getConstraint()->matches($version)) {
                            // if we have a root package (we should but can not guarantee..) we show
                            // the root requires as well to perhaps allow to find an issue there
                            if ($rootPackage) {
                                foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) {
                                    if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) {
                                        $results[] = array($package, $link, false);
                                        $results[] = array($rootPackage, $rootReq, false);
                                        continue 3;
                                    }
                                }

                                $results[] = array($package, $link, false);
                                $results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), null, 'does not require', 'but ' . $pkg->getPrettyVersion() . ' is installed'), false);
                            } else {
                                // no root so let's just print whatever we found
                                $results[] = array($package, $link, false);
                            }
                        }

                        continue 2;
                    }
                }
            }
        }

        ksort($results);

        return $results;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Repository interface.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RepositoryInterface extends \Countable
{
    const SEARCH_FULLTEXT = 0;
    const SEARCH_NAME = 1;

    /**
     * Checks if specified package registered (installed).
     *
     * @param PackageInterface $package package instance
     *
     * @return bool
     */
    public function hasPackage(PackageInterface $package);

    /**
     * Searches for the first match of a package by name and version.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface|null
     */
    public function findPackage($name, $constraint);

    /**
     * Searches for all packages matching a name and optionally a version.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface[]
     */
    public function findPackages($name, $constraint = null);

    /**
     * Returns list of registered packages.
     *
     * @return PackageInterface[]
     */
    public function getPackages();

    /**
     * Searches the repository for packages containing the query
     *
     * @param string $query search query
     * @param int    $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only
     *
     * @return array[] an array of array('name' => '...', 'description' => '...')
     */
    public function search($query, $mode = 0);
}
<?php

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

namespace Composer\Repository;

/**
 * Exception thrown when a package repository is utterly broken
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidRepositoryException extends \Exception
{
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Zip;

/**
 * @author Serge Smertin <serg.smertin@gmail.com>
 */
class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var LoaderInterface */
    protected $loader;

    protected $lookup;
    protected $repoConfig;
    private $io;

    public function __construct(array $repoConfig, IOInterface $io)
    {
        parent::__construct();
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
        }

        $this->loader = new ArrayLoader();
        $this->lookup = $repoConfig['url'];
        $this->io = $io;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->scanDirectory($this->lookup);
    }

    private function scanDirectory($path)
    {
        $io = $this->io;

        $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
        $iterator = new \RecursiveIteratorIterator($directory);
        $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i');
        foreach ($regex as $file) {
            /* @var $file \SplFileInfo */
            if (!$file->isFile()) {
                continue;
            }

            $package = $this->getComposerInformation($file);
            if (!$package) {
                $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
                continue;
            }

            $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
            $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);

            $this->addPackage($package);
        }
    }

    private function getComposerInformation(\SplFileInfo $file)
    {
        $json = Zip::getComposerJson($file->getPathname());

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

        $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
        $package['dist'] = array(
            'type' => 'zip',
            'url' => strtr($file->getPathname(), '\\', '/'),
            'shasum' => sha1_file($file->getRealPath()),
        );

        try {
            $package = $this->loader->load($package);
        } catch (\UnexpectedValueException $e) {
            throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e);
        }

        return $package;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\Config;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Util\RemoteFilesystem;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Downloader\TransportException;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    protected $config;
    protected $repoConfig;
    protected $options;
    protected $url;
    protected $baseUrl;
    protected $io;
    protected $rfs;
    protected $cache;
    protected $notifyUrl;
    protected $searchUrl;
    protected $hasProviders = false;
    protected $providersUrl;
    protected $lazyProvidersUrl;
    protected $providerListing;
    protected $providers = array();
    protected $providersByUid = array();
    protected $loader;
    protected $rootAliases;
    protected $allowSslDowngrade = false;
    protected $eventDispatcher;
    protected $sourceMirrors;
    protected $distMirrors;
    private $degradedMode = false;
    private $rootData;
    private $hasPartialPackages;
    private $partialPackagesByName;

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        parent::__construct();
        if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) {
            // assume http as the default protocol
            $repoConfig['url'] = 'http://'.$repoConfig['url'];
        }
        $repoConfig['url'] = rtrim($repoConfig['url'], '/');

        if ('https?' === substr($repoConfig['url'], 0, 6)) {
            $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6);
        }

        $urlBits = parse_url($repoConfig['url']);
        if ($urlBits === false || empty($urlBits['scheme'])) {
            throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
        }

        if (!isset($repoConfig['options'])) {
            $repoConfig['options'] = array();
        }
        if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) {
            $this->allowSslDowngrade = true;
        }

        $this->config = $config;
        $this->options = $repoConfig['options'];
        $this->url = $repoConfig['url'];

        // force url for packagist.org to repo.packagist.org
        if (preg_match('{^(?P<proto>https?)://packagist\.org/?$}i', $this->url, $match)) {
            $this->url = $match['proto'].'://repo.packagist.org';
        }

        $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
        $this->io = $io;
        $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
        $this->loader = new ArrayLoader();
        if ($rfs && $this->options) {
            $rfs = clone $rfs;
            $rfs->setOptions($this->options);
        }
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $this->config, $this->options);
        $this->eventDispatcher = $eventDispatcher;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setRootAliases(array $rootAliases)
    {
        $this->rootAliases = $rootAliases;
    }

    /**
     * {@inheritDoc}
     */
    public function findPackage($name, $constraint)
    {
        if (!$this->hasProviders()) {
            return parent::findPackage($name, $constraint);
        }

        $name = strtolower($name);
        if (!$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getProviderNames() as $providerName) {
            if ($name === $providerName) {
                $packages = $this->whatProvides(new Pool('dev'), $providerName);
                foreach ($packages as $package) {
                    if ($name === $package->getName()) {
                        $pkgConstraint = new Constraint('==', $package->getVersion());
                        if ($constraint->matches($pkgConstraint)) {
                            return $package;
                        }
                    }
                }
                break;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function findPackages($name, $constraint = null)
    {
        if (!$this->hasProviders()) {
            return parent::findPackages($name, $constraint);
        }
        // normalize name
        $name = strtolower($name);

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        $packages = array();

        foreach ($this->getProviderNames() as $providerName) {
            if ($name === $providerName) {
                $candidates = $this->whatProvides(new Pool('dev'), $providerName);
                foreach ($candidates as $package) {
                    if ($name === $package->getName()) {
                        $pkgConstraint = new Constraint('==', $package->getVersion());
                        if (null === $constraint || $constraint->matches($pkgConstraint)) {
                            $packages[] = $package;
                        }
                    }
                }
                break;
            }
        }

        return $packages;
    }

    public function getPackages()
    {
        if ($this->hasProviders()) {
            throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getProviderNames instead.');
        }

        return parent::getPackages();
    }

    /**
     * {@inheritDoc}
     */
    public function search($query, $mode = 0, $type = null)
    {
        $this->loadRootServerFile();

        if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
            $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl);

            $origin = RemoteFilesystem::getOrigin($url);
            $json = $this->rfs->getContents($origin, $url, false);
            $search = JsonFile::parseJson($json, $url);

            if (empty($search['results'])) {
                return array();
            }

            $results = array();
            foreach ($search['results'] as $result) {
                // do not show virtual packages in results as they are not directly useful from a composer perspective
                if (empty($result['virtual'])) {
                    $results[] = $result;
                }
            }

            return $results;
        }

        if ($this->hasProviders()) {
            $results = array();
            $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';

            foreach ($this->getProviderNames() as $name) {
                if (preg_match($regex, $name)) {
                    $results[] = array('name' => $name);
                }
            }

            return $results;
        }

        return parent::search($query, $mode);
    }

    public function getProviderNames()
    {
        $this->loadRootServerFile();

        if (null === $this->providerListing) {
            $this->loadProviderListings($this->loadRootServerFile());
        }

        if ($this->hasPartialPackages) {
            if (null === $this->partialPackagesByName) {
                $this->initializePartialPackages();
            }

            return array_keys($this->partialPackagesByName);
        }

        if ($this->lazyProvidersUrl) {
            // Can not determine list of provided packages for lazy repositories
            return array();
        }

        if ($this->providersUrl) {
            return array_keys($this->providerListing);
        }

        return array();
    }

    protected function configurePackageTransportOptions(PackageInterface $package)
    {
        foreach ($package->getDistUrls() as $url) {
            if (strpos($url, $this->baseUrl) === 0) {
                $package->setTransportOptions($this->options);

                return;
            }
        }
    }

    public function hasProviders()
    {
        $this->loadRootServerFile();

        return $this->hasProviders;
    }

    public function resetPackageIds()
    {
        foreach ($this->providersByUid as $package) {
            if ($package instanceof AliasPackage) {
                $package->getAliasOf()->setId(-1);
            }
            $package->setId(-1);
        }
    }

    /**
     * @param  Pool        $pool
     * @param  string      $name          package name
     * @param  bool        $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache
     * @return array|mixed
     */
    public function whatProvides(Pool $pool, $name, $bypassFilters = false)
    {
        if (isset($this->providers[$name]) && !$bypassFilters) {
            return $this->providers[$name];
        }

        if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
            $this->initializePartialPackages();
        }

        if (!$this->hasPartialPackages || !isset($this->partialPackagesByName[$name])) {
            // skip platform packages, root package and composer-plugin-api
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) {
                return array();
            }

            if (null === $this->providerListing) {
                $this->loadProviderListings($this->loadRootServerFile());
            }

            $useLastModifiedCheck = false;
            if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) {
                $hash = null;
                $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
                $useLastModifiedCheck = true;
            } elseif ($this->providersUrl) {
                // package does not exist in this repo
                if (!isset($this->providerListing[$name])) {
                    return array();
                }

                $hash = $this->providerListing[$name]['sha256'];
                $url = str_replace(array('%package%', '%hash%'), array($name, $hash), $this->providersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
            } else {
                return array();
            }

            $packages = null;
            if ($cacheKey) {
                if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) {
                    $packages = json_decode($this->cache->read($cacheKey), true);
                } elseif ($useLastModifiedCheck) {
                    if ($contents = $this->cache->read($cacheKey)) {
                        $contents = json_decode($contents, true);
                        if (isset($contents['last-modified'])) {
                            $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']);
                            if (true === $response) {
                                $packages = $contents;
                            } elseif ($response) {
                                $packages = $response;
                            }
                        }
                    }
                }
            }

            if (!$packages) {
                try {
                    $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck);
                } catch (TransportException $e) {
                    // 404s are acceptable for lazy provider repos
                    if ($e->getStatusCode() === 404 && $this->lazyProvidersUrl) {
                        $packages = array('packages' => array());
                    } else {
                        throw $e;
                    }
                }
            }

            $loadingPartialPackage = false;
        } else {
            $packages = array('packages' => array('versions' => $this->partialPackagesByName[$name]));
            $loadingPartialPackage = true;
        }

        $this->providers[$name] = array();
        foreach ($packages['packages'] as $versions) {
            foreach ($versions as $version) {
                if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) {
                    continue;
                }

                // avoid loading the same objects twice
                if (isset($this->providersByUid[$version['uid']])) {
                    // skip if already assigned
                    if (!isset($this->providers[$name][$version['uid']])) {
                        // expand alias in two packages
                        if ($this->providersByUid[$version['uid']] instanceof AliasPackage) {
                            $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf();
                            $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']];
                        } else {
                            $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']];
                        }
                        // check for root aliases
                        if (isset($this->providersByUid[$version['uid'].'-root'])) {
                            $this->providers[$name][$version['uid'].'-root'] = $this->providersByUid[$version['uid'].'-root'];
                        }
                    }
                } else {
                    if (!$bypassFilters && !$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) {
                        continue;
                    }

                    // load acceptable packages in the providers
                    $package = $this->createPackage($version, 'Composer\Package\CompletePackage');
                    $package->setRepository($this);

                    if ($package instanceof AliasPackage) {
                        $aliased = $package->getAliasOf();
                        $aliased->setRepository($this);

                        $this->providers[$name][$version['uid']] = $aliased;
                        $this->providers[$name][$version['uid'].'-alias'] = $package;

                        // override provider with its alias so it can be expanded in the if block above
                        $this->providersByUid[$version['uid']] = $package;
                    } else {
                        $this->providers[$name][$version['uid']] = $package;
                        $this->providersByUid[$version['uid']] = $package;
                    }

                    // handle root package aliases
                    unset($rootAliasData);

                    if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
                        $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()];
                    } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) {
                        $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()];
                    }

                    if (isset($rootAliasData)) {
                        $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']);
                        $alias->setRepository($this);

                        $this->providers[$name][$version['uid'].'-root'] = $alias;
                        $this->providersByUid[$version['uid'].'-root'] = $alias;
                    }
                }
            }
        }

        $result = $this->providers[$name];

        // clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache
        // which is likely not an issue but might cause hard to track behaviors depending on how the repo is used
        if ($bypassFilters) {
            foreach ($this->providers[$name] as $uid => $provider) {
                unset($this->providersByUid[$uid]);
            }
            unset($this->providers[$name]);
        }

        return $result;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize()
    {
        parent::initialize();

        $repoData = $this->loadDataFromServer();

        foreach ($repoData as $package) {
            $this->addPackage($this->createPackage($package, 'Composer\Package\CompletePackage'));
        }
    }

    /**
     * Adds a new package to the repository
     *
     * @param PackageInterface $package
     */
    public function addPackage(PackageInterface $package)
    {
        parent::addPackage($package);
        $this->configurePackageTransportOptions($package);
    }

    protected function loadRootServerFile()
    {
        if (null !== $this->rootData) {
            return $this->rootData;
        }

        if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) {
            throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
        }

        $jsonUrlParts = parse_url($this->url);

        if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
            $jsonUrl = $this->url;
        } else {
            $jsonUrl = $this->url . '/packages.json';
        }

        $data = $this->fetchFile($jsonUrl, 'packages.json');

        if (!empty($data['notify-batch'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']);
        } elseif (!empty($data['notify'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify']);
        }

        if (!empty($data['search'])) {
            $this->searchUrl = $this->canonicalizeUrl($data['search']);
        }

        if (!empty($data['mirrors'])) {
            foreach ($data['mirrors'] as $mirror) {
                if (!empty($mirror['git-url'])) {
                    $this->sourceMirrors['git'][] = array('url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred']));
                }
                if (!empty($mirror['hg-url'])) {
                    $this->sourceMirrors['hg'][] = array('url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred']));
                }
                if (!empty($mirror['dist-url'])) {
                    $this->distMirrors[] = array(
                        'url' => $this->canonicalizeUrl($mirror['dist-url']),
                        'preferred' => !empty($mirror['preferred']),
                    );
                }
            }
        }

        if (!empty($data['providers-lazy-url'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']);
            $this->hasProviders = true;

            $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
        }

        if ($this->allowSslDowngrade) {
            $this->url = str_replace('https://', 'http://', $this->url);
            $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl);
        }

        if (!empty($data['providers-url'])) {
            $this->providersUrl = $this->canonicalizeUrl($data['providers-url']);
            $this->hasProviders = true;
        }

        if (!empty($data['providers']) || !empty($data['providers-includes'])) {
            $this->hasProviders = true;
        }

        // force values for packagist
        if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) {
            $this->url = 'https://repo.packagist.org';
            $this->baseUrl = 'https://repo.packagist.org';
            $this->lazyProvidersUrl = $this->canonicalizeUrl('https://repo.packagist.org/p/%package%.json');
            $this->providersUrl = null;
        } elseif (!empty($this->repoConfig['force-lazy-providers'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl('/p/%package%.json');
            $this->providersUrl = null;
        }

        return $this->rootData = $data;
    }

    protected function canonicalizeUrl($url)
    {
        if ('/' === $url[0]) {
            if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) {
                return $matches[0] . $url;
            }

            return $this->url;
        }

        return $url;
    }

    protected function loadDataFromServer()
    {
        $data = $this->loadRootServerFile();

        return $this->loadIncludes($data);
    }

    protected function loadProviderListings($data)
    {
        if (isset($data['providers'])) {
            if (!is_array($this->providerListing)) {
                $this->providerListing = array();
            }
            $this->providerListing = array_merge($this->providerListing, $data['providers']);
        }

        if ($this->providersUrl && isset($data['provider-includes'])) {
            $includes = $data['provider-includes'];
            foreach ($includes as $include => $metadata) {
                $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include);
                $cacheKey = str_replace(array('%hash%','$'), '', $include);
                if ($this->cache->sha256($cacheKey) === $metadata['sha256']) {
                    $includedData = json_decode($this->cache->read($cacheKey), true);
                } else {
                    $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']);
                }

                $this->loadProviderListings($includedData);
            }
        }
    }

    protected function loadIncludes($data)
    {
        $packages = array();

        // legacy repo handling
        if (!isset($data['packages']) && !isset($data['includes'])) {
            foreach ($data as $pkg) {
                foreach ($pkg['versions'] as $metadata) {
                    $packages[] = $metadata;
                }
            }

            return $packages;
        }

        if (isset($data['packages'])) {
            foreach ($data['packages'] as $package => $versions) {
                foreach ($versions as $version => $metadata) {
                    $packages[] = $metadata;
                }
            }
        }

        if (isset($data['includes'])) {
            foreach ($data['includes'] as $include => $metadata) {
                if (isset($metadata['sha1']) && $this->cache->sha1($include) === $metadata['sha1']) {
                    $includedData = json_decode($this->cache->read($include), true);
                } else {
                    $includedData = $this->fetchFile($include);
                }
                $packages = array_merge($packages, $this->loadIncludes($includedData));
            }
        }

        return $packages;
    }

    protected function createPackage(array $data, $class = 'Composer\Package\CompletePackage')
    {
        try {
            if (!isset($data['notification-url'])) {
                $data['notification-url'] = $this->notifyUrl;
            }

            $package = $this->loader->load($data, $class);
            if (isset($this->sourceMirrors[$package->getSourceType()])) {
                $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
            }
            $package->setDistMirrors($this->distMirrors);
            $this->configurePackageTransportOptions($package);

            return $package;
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
        }
    }

    protected function fetchFile($filename, $cacheKey = null, $sha256 = null, $storeLastModifiedTime = false)
    {
        if (null === $cacheKey) {
            $cacheKey = $filename;
            $filename = $this->baseUrl.'/'.$filename;
        }

        // url-encode $ signs in URLs as bad proxies choke on them
        if (($pos = strpos($filename, '$')) && preg_match('{^https?://.*}i', $filename)) {
            $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1);
        }

        $retries = 3;
        while ($retries--) {
            try {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename);
                if ($this->eventDispatcher) {
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                }

                $origin = RemoteFilesystem::getOrigin($filename);
                $rfs = $preFileDownloadEvent->getRemoteFilesystem();

                $json = $rfs->getContents($origin, $filename, false);
                if ($sha256 && $sha256 !== hash('sha256', $json)) {
                    // undo downgrade before trying again if http seems to be hijacked or modifying content somehow
                    if ($this->allowSslDowngrade) {
                        $this->url = str_replace('http://', 'https://', $this->url);
                        $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl);
                        $filename = str_replace('http://', 'https://', $filename);
                    }

                    if ($retries) {
                        usleep(100000);

                        continue;
                    }

                    // TODO use scarier wording once we know for sure it doesn't do false positives anymore
                    throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.');
                }

                $data = JsonFile::parseJson($json, $filename);
                RemoteFilesystem::outputWarnings($this->io, $this->url, $data);

                if ($cacheKey) {
                    if ($storeLastModifiedTime) {
                        $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified');
                        if ($lastModifiedDate) {
                            $data['last-modified'] = $lastModifiedDate;
                            $json = json_encode($data);
                        }
                    }
                    $this->cache->write($cacheKey, $json);
                }

                break;
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($retries) {
                    usleep(100000);
                    continue;
                }

                if ($e instanceof RepositorySecurityException) {
                    throw $e;
                }

                if ($cacheKey && ($contents = $this->cache->read($cacheKey))) {
                    if (!$this->degradedMode) {
                        $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                        $this->io->writeError('<warning>'.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</warning>');
                    }
                    $this->degradedMode = true;
                    $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey);

                    break;
                }

                throw $e;
            }
        }

        return $data;
    }

    protected function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime)
    {
        $retries = 3;
        while ($retries--) {
            try {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename);
                if ($this->eventDispatcher) {
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                }

                $origin = RemoteFilesystem::getOrigin($filename);
                $rfs = $preFileDownloadEvent->getRemoteFilesystem();
                $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime)));
                $json = $rfs->getContents($origin, $filename, false, $options);
                if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) {
                    return true;
                }

                $data = JsonFile::parseJson($json, $filename);
                RemoteFilesystem::outputWarnings($this->io, $this->url, $data);

                $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified');
                if ($lastModifiedDate) {
                    $data['last-modified'] = $lastModifiedDate;
                    $json = json_encode($data);
                }
                $this->cache->write($cacheKey, $json);

                return $data;
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($retries) {
                    usleep(100000);
                    continue;
                }

                if (!$this->degradedMode) {
                    $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                    $this->io->writeError('<warning>'.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</warning>');
                }
                $this->degradedMode = true;

                return true;
            }
        }
    }

    /**
     * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url
     *
     * This should only be called once
     */
    private function initializePartialPackages()
    {
        $rootData = $this->loadRootServerFile();

        $this->partialPackagesByName = array();
        foreach ($rootData['packages'] as $package => $versions) {
            $package = strtolower($package);
            foreach ($versions as $version) {
                $this->partialPackagesByName[$package][] = $version;
                if (!empty($version['provide']) && is_array($version['provide'])) {
                    foreach ($version['provide'] as $provided => $providedVersion) {
                        $this->partialPackagesByName[strtolower($provided)][] = $version;
                    }
                }
                if (!empty($version['replace']) && is_array($version['replace'])) {
                    foreach ($version['replace'] as $provided => $providedVersion) {
                        $this->partialPackagesByName[strtolower($provided)][] = $version;
                    }
                }
            }
        }

        // wipe rootData as it is fully consumed at this point and this saves some memory
        $this->rootData = true;
    }
}
<?php

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

namespace Composer\Repository;

interface VersionCacheInterface
{
    /**
     * @param string $version
     * @param string $identifier
     * @return array|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier
     */
    public function getVersionPackage($version, $identifier);
}
<?php

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

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A repository implementation that simply stores packages in an array
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArrayRepository extends BaseRepository
{
    /** @var PackageInterface[] */
    protected $packages;
    
    /** 
      * @var PackageInterface[] indexed by package unique name and used to cache hasPackage calls
      */
    protected $packageMap;

    public function __construct(array $packages = array())
    {
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function findPackage($name, $constraint)
    {
        $name = strtolower($name);

        if (!$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                $pkgConstraint = new Constraint('==', $package->getVersion());
                if ($constraint->matches($pkgConstraint)) {
                    return $package;
                }
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function findPackages($name, $constraint = null)
    {
        // normalize name
        $name = strtolower($name);
        $packages = array();

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                $pkgConstraint = new Constraint('==', $package->getVersion());
                if (null === $constraint || $constraint->matches($pkgConstraint)) {
                    $packages[] = $package;
                }
            }
        }

        return $packages;
    }

    /**
     * {@inheritDoc}
     */
    public function search($query, $mode = 0, $type = null)
    {
        $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';

        $matches = array();
        foreach ($this->getPackages() as $package) {
            $name = $package->getName();
            if (isset($matches[$name])) {
                continue;
            }
            if (preg_match($regex, $name)
                || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && preg_match($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription()))
            ) {
                if (null !== $type && $package->getType() !== $type) {
                    continue;
                }

                $matches[$name] = array(
                    'name' => $package->getPrettyName(),
                    'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null,
                );
            }
        }

        return array_values($matches);
    }

    /**
     * {@inheritDoc}
     */
    public function hasPackage(PackageInterface $package)
    {
        if ($this->packageMap === null) {
            $this->packageMap = array();
            foreach ($this->getPackages() as $repoPackage) {
                $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage;
            }
        }

        return isset($this->packageMap[$package->getUniqueName()]);
    }

    /**
     * Adds a new package to the repository
     *
     * @param PackageInterface $package
     */
    public function addPackage(PackageInterface $package)
    {
        if (null === $this->packages) {
            $this->initialize();
        }
        $package->setRepository($this);
        $this->packages[] = $package;

        if ($package instanceof AliasPackage) {
            $aliasedPackage = $package->getAliasOf();
            if (null === $aliasedPackage->getRepository()) {
                $this->addPackage($aliasedPackage);
            }
        }

        // invalidate package map cache
        $this->packageMap = null;
    }

    protected function createAliasPackage(PackageInterface $package, $alias, $prettyAlias)
    {
        return new AliasPackage($package instanceof AliasPackage ? $package->getAliasOf() : $package, $alias, $prettyAlias);
    }

    /**
     * Removes package from repository.
     *
     * @param PackageInterface $package package instance
     */
    public function removePackage(PackageInterface $package)
    {
        $packageId = $package->getUniqueName();

        foreach ($this->getPackages() as $key => $repoPackage) {
            if ($packageId === $repoPackage->getUniqueName()) {
                array_splice($this->packages, $key, 1);

                // invalidate package map cache
                $this->packageMap = null;

                return;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getPackages()
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        return $this->packages;
    }

    /**
     * Returns the number of packages in this repository
     *
     * @return int Number of packages
     */
    public function count()
    {
        return count($this->packages);
    }

    /**
     * Initializes the packages array. Mostly meant as an extension point.
     */
    protected function initialize()
    {
        $this->packages = array();
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;

/**
 * Package repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageRepository extends ArrayRepository
{
    private $config;

    /**
     * Initializes filesystem repository.
     *
     * @param array $config package definition
     */
    public function __construct(array $config)
    {
        parent::__construct();
        $this->config = $config['package'];

        // make sure we have an array of package definitions
        if (!is_numeric(key($this->config))) {
            $this->config = array($this->config);
        }
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize()
    {
        parent::initialize();

        $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), false);
        foreach ($this->config as $package) {
            try {
                $package = $loader->load($package);
            } catch (\Exception $e) {
                throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package));
            }

            $this->addPackage($package);
        }
    }
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Package\Version\VersionParser;
use Composer\Repository\Pear\ChannelReader;
use Composer\Package\CompletePackage;
use Composer\Repository\Pear\ChannelInfo;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\RemoteFilesystem;
use Composer\Config;
use Composer\Factory;

/**
 * Builds list of package from PEAR channel.
 *
 * Packages read from channel are named as 'pear-{channelName}/{packageName}'
 * and has aliased as 'pear-{channelAlias}/{packageName}'
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PearRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    private $url;
    private $io;
    private $rfs;
    private $versionParser;
    private $repoConfig;

    /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother
     * package transition to composer-like repositories.
     */
    private $vendorAlias;

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null)
    {
        parent::__construct();
        if (!preg_match('{^https?://}', $repoConfig['url'])) {
            $repoConfig['url'] = 'http://'.$repoConfig['url'];
        }

        $urlBits = parse_url($repoConfig['url']);
        if (empty($urlBits['scheme']) || empty($urlBits['host'])) {
            throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']);
        }

        $this->url = rtrim($repoConfig['url'], '/');
        $this->io = $io;
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
        $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null;
        $this->versionParser = new VersionParser();
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->io->writeError('Initializing PEAR repository '.$this->url);

        $reader = new ChannelReader($this->rfs);
        try {
            $channelInfo = $reader->read($this->url);
        } catch (\Exception $e) {
            $this->io->writeError('<warning>PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().'</warning>');

            return;
        }
        $packages = $this->buildComposerPackages($channelInfo, $this->versionParser);
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * Builds CompletePackages from PEAR package definition data.
     *
     * @param  ChannelInfo         $channelInfo
     * @param  SemverVersionParser $versionParser
     * @return CompletePackage
     */
    private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser)
    {
        $result = array();
        foreach ($channelInfo->getPackages() as $packageDefinition) {
            foreach ($packageDefinition->getReleases() as $version => $releaseInfo) {
                try {
                    $normalizedVersion = $versionParser->normalize($version);
                } catch (\UnexpectedValueException $e) {
                    $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage(), true, IOInterface::VERBOSE);
                    continue;
                }

                $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName());

                // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text()
                // but this location is 'de-facto' standard
                $urlBits = parse_url($this->url);
                $scheme = (isset($urlBits['scheme']) && 'https' === $urlBits['scheme'] && extension_loaded('openssl')) ? 'https' : 'http';
                $distUrl = "{$scheme}://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz";

                $requires = array();
                $suggests = array();
                $conflicts = array();
                $replaces = array();

                // alias package only when its channel matches repository channel,
                // cause we've know only repository channel alias
                if ($channelInfo->getName() == $packageDefinition->getChannelName()) {
                    $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName());
                    $aliasConstraint = new Constraint('==', $normalizedVersion);
                    $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint);
                }

                // alias package with user-specified prefix. it makes private pear channels looks like composer's.
                if (!empty($this->vendorAlias)
                    && ($this->vendorAlias != 'pear-'.$channelInfo->getAlias() || $channelInfo->getName() != $packageDefinition->getChannelName())
                ) {
                    $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}";
                    $aliasConstraint = new Constraint('==', $normalizedVersion);
                    $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint);
                }

                foreach ($releaseInfo->getDependencyInfo()->getRequires() as $dependencyConstraint) {
                    $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName());
                    $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint());
                    $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint());
                    switch ($dependencyConstraint->getType()) {
                        case 'required':
                            $requires[] = $link;
                            break;
                        case 'conflicts':
                            $conflicts[] = $link;
                            break;
                        case 'replaces':
                            $replaces[] = $link;
                            break;
                    }
                }

                foreach ($releaseInfo->getDependencyInfo()->getOptionals() as $group => $dependencyConstraints) {
                    foreach ($dependencyConstraints as $dependencyConstraint) {
                        $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName());
                        $suggests[$group.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint();
                    }
                }

                $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
                $package->setType('pear-library');
                $package->setDescription($packageDefinition->getDescription());
                $package->setLicense(array($packageDefinition->getLicense()));
                $package->setDistType('file');
                $package->setDistUrl($distUrl);
                $package->setAutoload(array('classmap' => array('')));
                $package->setIncludePaths(array('/'));
                $package->setRequires($requires);
                $package->setConflicts($conflicts);
                $package->setSuggests($suggests);
                $package->setReplaces($replaces);
                $result[] = $package;
            }
        }

        return $result;
    }

    private function buildComposerPackageName($channelName, $packageName)
    {
        if ('php' === $channelName) {
            return "php";
        }
        if ('ext' === $channelName) {
            return "ext-{$packageName}";
        }

        return "pear-{$channelName}/{$packageName}";
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Configurable repository interface.
 *
 * @author Lukas Homza <lukashomz@gmail.com>
 */
interface ConfigurableRepositoryInterface
{
    public function getRepoConfig();
}
<?php

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

namespace Composer\Repository;

use Composer\Package\AliasPackage;

/**
 * Writable array repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface
{
    /**
     * {@inheritDoc}
     */
    public function write()
    {
    }

    /**
     * {@inheritDoc}
     */
    public function reload()
    {
    }

    /**
     * {@inheritDoc}
     */
    public function getCanonicalPackages()
    {
        $packages = $this->getPackages();

        // get at most one package of each name, preferring non-aliased ones
        $packagesByName = array();
        foreach ($packages as $package) {
            if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) {
                $packagesByName[$package->getName()] = $package;
            }
        }

        $canonicalPackages = array();

        // unfold aliased packages
        foreach ($packagesByName as $package) {
            while ($package instanceof AliasPackage) {
                $package = $package->getAliasOf();
            }

            $canonicalPackages[] = $package;
        }

        return $canonicalPackages;
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Thrown when a security problem, like a broken or missing signature
 *
 * @author Eric Daspet <edaspet@survol.fr>
 */
class RepositorySecurityException extends \Exception
{
}
<?php

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

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Writable repository interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface WritableRepositoryInterface extends RepositoryInterface
{
    /**
     * Writes repository (f.e. to the disc).
     */
    public function write();

    /**
     * Adds package to the repository.
     *
     * @param PackageInterface $package package instance
     */
    public function addPackage(PackageInterface $package);

    /**
     * Removes package from the repository.
     *
     * @param PackageInterface $package package instance
     */
    public function removePackage(PackageInterface $package);

    /**
     * Get unique packages (at most one package of each name), with aliases resolved and removed.
     *
     * @return PackageInterface[]
     */
    public function getCanonicalPackages();

    /**
     * Forces a reload of all packages.
     */
    public function reload();
}
<?php

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

namespace Composer\Repository;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;

/**
 * This repository allows installing local packages that are not necessarily under their own VCS.
 *
 * The local packages will be symlinked when possible, else they will be copied.
 *
 * @code
 * "require": {
 *     "<vendor>/<local-package>": "*"
 * },
 * "repositories": [
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/several/packages/*"
 *     },
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/",
 *         "options": {
 *             "symlink": false
 *         }
 *     },
 * ]
 * @endcode
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /**
     * @var ArrayLoader
     */
    private $loader;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var string
     */
    private $url;

    /**
     * @var array
     */
    private $repoConfig;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var array
     */
    private $options;

    /**
     * Initializes path repository.
     *
     * @param array       $repoConfig
     * @param IOInterface $io
     * @param Config      $config
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config)
    {
        if (!isset($repoConfig['url'])) {
            throw new \RuntimeException('You must specify the `url` configuration for the path repository');
        }

        $this->loader = new ArrayLoader(null, true);
        $this->url = Platform::expandPath($repoConfig['url']);
        $this->process = new ProcessExecutor($io);
        $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser());
        $this->repoConfig = $repoConfig;
        $this->options = isset($repoConfig['options']) ? $repoConfig['options'] : array();
        if (!isset($this->options['relative'])) {
            $filesystem = new Filesystem();
            $this->options['relative'] = !$filesystem->isAbsolutePath($this->url);
        }

        parent::__construct();
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    /**
     * Initializes path repository.
     *
     * This method will basically read the folder and add the found package.
     */
    protected function initialize()
    {
        parent::initialize();

        $urlMatches = $this->getUrlMatches();

        if (empty($urlMatches)) {
            if (preg_match('{[*{}]}', $this->url)) {
                $url = $this->url;
                while (preg_match('{[*{}]}', $url)) {
                    $url = dirname($url);
                }
                // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty
                if (is_dir($url)) {
                    return;
                }
            }

            throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
        }

        foreach ($urlMatches as $url) {
            $path = realpath($url) . DIRECTORY_SEPARATOR;
            $composerFilePath = $path.'composer.json';

            if (!file_exists($composerFilePath)) {
                continue;
            }

            $json = file_get_contents($composerFilePath);
            $package = JsonFile::parseJson($json, $composerFilePath);
            $package['dist'] = array(
                'type' => 'path',
                'url' => $url,
                'reference' => sha1($json . serialize($this->options)),
            );
            $package['transport-options'] = $this->options;
            unset($package['transport-options']['versions']);

            // use the version provided as option if available
            if (isset($package['name'], $this->options['versions'][$package['name']])) {
                $package['version'] = $this->options['versions'][$package['name']];
            }

            // carry over the root package version if this path repo is in the same git repository as root package
            if (!isset($package['version']) && ($rootVersion = getenv('COMPOSER_ROOT_VERSION'))) {
                if (
                    0 === $this->process->execute('git rev-parse HEAD', $ref1, $path)
                    && 0 === $this->process->execute('git rev-parse HEAD', $ref2)
                    && $ref1 === $ref2
                ) {
                    $package['version'] = $rootVersion;
                }
            }

            $output = '';
            if (is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H'.GitUtil::getNoShowSignatureFlag($this->process), $output, $path)) {
                $package['dist']['reference'] = trim($output);
            }

            if (!isset($package['version'])) {
                $versionData = $this->versionGuesser->guessVersion($package, $path);
                if (is_array($versionData) && $versionData['pretty_version']) {
                    // if there is a feature branch detected, we add a second packages with the feature branch version
                    if (!empty($versionData['feature_pretty_version'])) {
                        $package['version'] = $versionData['feature_pretty_version'];
                        $this->addPackage($this->loader->load($package));
                    }

                    $package['version'] = $versionData['pretty_version'];
                } else {
                    $package['version'] = 'dev-master';
                }
            }

            $package = $this->loader->load($package);
            $this->addPackage($package);
        }
    }

    /**
     * Get a list of all (possibly relative) path names matching given url (supports globbing).
     *
     * @return string[]
     */
    private function getUrlMatches()
    {
        $flags = GLOB_MARK | GLOB_ONLYDIR;

        if (defined('GLOB_BRACE')) {
            $flags |= GLOB_BRACE;
        } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) {
            throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url);
        }

        // Ensure environment-specific path separators are normalized to URL separators
        return array_map(function ($val) {
            return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/');
        }, glob($this->url, $flags));
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Installed array repository.
 *
 * This is used for serving the RootPackage inside an in-memory InstalledRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
{
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\Util\Hg as HgUtils;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDriver extends VcsDriver
{
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoDir;
    protected $infoCache = array();

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->repoDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $cacheDir = $this->config->get('cache-vcs-dir');
            $this->repoDir = $cacheDir . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/';

            $fs = new Filesystem();
            $fs->ensureDirectoryExists($cacheDir);

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.');
            }

            // Ensure we are allowed to use this URL by config
            $this->config->prohibitUrlByConfig($this->url, $this->io);

            $hgUtils = new HgUtils($this->io, $this->config, $this->process);

            // update the repo if it is a valid hg repository
            if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) {
                if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) {
                    $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
                }
            } else {
                // clean up directory and do a fresh clone into it
                $fs->removeDirectory($this->repoDir);

                $repoDir = $this->repoDir;
                $command = function ($url) use ($repoDir) {
                    return sprintf('hg clone --noupdate -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                };

                $hgUtils->runCommand($command, $this->url, null);
            }
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if (null === $this->rootIdentifier) {
            $this->process->execute(sprintf('hg tip --template "{node}"'), $output, $this->repoDir);
            $output = $this->process->splitLines($output);
            $this->rootIdentifier = $output[0];
        }

        return $this->rootIdentifier;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        return array('type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        $this->process->execute($resource, $content, $this->repoDir);

        if (!trim($content)) {
            return;
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        $this->process->execute(
            sprintf(
                'hg log --template "{date|rfc3339date}" -r %s',
                ProcessExecutor::escape($identifier)
            ),
            $output,
            $this->repoDir
        );

        return new \DateTime(trim($output), new \DateTimeZone('UTC'));
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if (null === $this->tags) {
            $tags = array();

            $this->process->execute('hg tags', $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $tag) {
                if ($tag && preg_match('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) {
                    $tags[$match[1]] = $match[2];
                }
            }
            unset($tags['tip']);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if (null === $this->branches) {
            $branches = array();
            $bookmarks = array();

            $this->process->execute('hg branches', $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && preg_match('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match)) {
                    $branches[$match[1]] = $match[2];
                }
            }

            $this->process->execute('hg bookmarks', $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && preg_match('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match)) {
                    $bookmarks[$match[1]] = $match[2];
                }
            }

            // Branches will have preference over bookmarks
            $this->branches = array_merge($bookmarks, $branches);
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a hg repo in that path
            if ($process->execute('hg summary', $output, $url) === 0) {
                return true;
            }
        }

        if (!$deep) {
            return false;
        }

        $processExecutor = new ProcessExecutor($io);
        $exit = $processExecutor->execute(sprintf('hg identify -- %s', ProcessExecutor::escape($url)), $ignored);

        return $exit === 0;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDriver extends VcsDriver
{
    protected $cache;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoDir;
    protected $infoCache = array();

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url);
            if (!is_dir($this->url)) {
                throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist');
            }
            $this->repoDir = $this->url;
            $cacheUrl = realpath($this->url);
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/';

            GitUtil::cleanEnv();

            $fs = new Filesystem();
            $fs->ensureDirectoryExists(dirname($this->repoDir));

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.');
            }

            if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) {
                throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
            }

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs);
            if (!$gitUtil->syncMirror($this->url, $this->repoDir)) {
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated</error>');
            }

            $cacheUrl = $this->url;
        }

        $this->getTags();
        $this->getBranches();

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl));
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'master';

            // select currently checked out branch if master is not available
            $this->process->execute('git branch --no-color', $output, $this->repoDir);
            $branches = $this->process->splitLines($output);
            if (!in_array('* master', $branches)) {
                foreach ($branches as $branch) {
                    if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) {
                        $this->rootIdentifier = $match[1];
                        break;
                    }
                }
            }
        }

        return $this->rootIdentifier;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir);

        if (!trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        $this->process->execute(sprintf(
            'git -c log.showSignature=false log -1 --format=%%at %s',
            ProcessExecutor::escape($identifier)
        ), $output, $this->repoDir);

        return new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if (null === $this->tags) {
            $this->tags = array();

            $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir);
            foreach ($output = $this->process->splitLines($output) as $tag) {
                if ($tag && preg_match('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
                    $this->tags[$match[2]] = $match[1];
                }
            }
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if (null === $this->branches) {
            $branches = array();

            $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
                    if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) {
                        $branches[$match[1]] = $match[2];
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a git repo in that path
            if ($process->execute('git tag', $output, $url) === 0) {
                return true;
            }
        }

        if (!$deep) {
            return false;
        }

        $gitUtil = new GitUtil($io, $config, new ProcessExecutor($io), new Filesystem());
        GitUtil::cleanEnv();

        try {
            $gitUtil->runCommand(function ($url) {
                return 'git ls-remote --heads -- ' . ProcessExecutor::escape($url);
            }, $url, sys_get_temp_dir());
        } catch (\RuntimeException $e) {
            return false;
        }

        return true;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Downloader\TransportException;
use Composer\Util\RemoteFilesystem;
use Composer\Util\GitLab;

/**
 * Driver for GitLab API, use the Git driver for local checkouts.
 *
 * @author Henrik Bjørnskov <henrik@bjrnskov.dk>
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class GitLabDriver extends VcsDriver
{
    private $scheme;
    private $namespace;
    private $repository;

    /**
     * @var array Project data returned by GitLab API
     */
    private $project;

    /**
     * @var array Keeps commits returned by GitLab API
     */
    private $commits = array();

    /**
     * @var array List of tag => reference
     */
    private $tags;

    /**
     * @var array List of branch => reference
     */
    private $branches;

    /**
     * Git Driver
     *
     * @var GitDriver
     */
    protected $gitDriver;

    /**
     * Defaults to true unless we can make sure it is public
     *
     * @var bool defines whether the repo is private or not
     */
    private $isPrivate = true;

    /**
     * @var bool true if the origin has a port number or a path component in it
     */
    private $hasNonstandardOrigin = false;

    const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';

    /**
     * Extracts information from the repository url.
     *
     * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead.
     *
     * {@inheritDoc}
     */
    public function initialize()
    {
        if (!preg_match(self::URL_REGEX, $this->url, $match)) {
            throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.');
        }

        $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
        $configuredDomains = $this->config->get('gitlab-domains');
        $urlParts = explode('/', $match['parts']);

        $this->scheme = !empty($match['scheme'])
            ? $match['scheme']
            : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
        ;
        $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);

        if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) {
            $this->hasNonstandardOrigin = true;
        }

        $this->namespace = implode('/', $urlParts);
        $this->repository = preg_replace('#(\.git)$#', '', $match['repo']);

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository);

        $this->fetchProject();
    }

    /**
     * Updates the RemoteFilesystem instance.
     * Mainly useful for tests.
     *
     * @internal
     */
    public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem)
    {
        $this->remoteFilesystem = $remoteFilesystem;
    }

    /**
     * {@inheritDoc}
     */
    public function getComposerInformation($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer =  JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if ($composer) {
                // specials for gitlab (this data is only available if authentication is provided)
                if (!isset($composer['support']['issues']) && isset($this->project['_links']['issues'])) {
                    $composer['support']['issues'] = $this->project['_links']['issues'];
                }
                if (!isset($composer['abandoned']) && !empty($this->project['archived'])) {
                    $composer['abandoned'] = true;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        // Convert the root identifier to a cacheable commit id
        if (!preg_match('{[a-f0-9]{40}}i', $identifier)) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier;

        try {
            $content = $this->getContents($resource);
        } catch (TransportException $e) {
            if ($e->getCode() !== 404) {
                throw $e;
            }

            return null;
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        if (isset($this->commits[$identifier])) {
            return new \DateTime($this->commits[$identifier]['committed_date']);
        }

        return new \DateTime();
    }

    /**
     * {@inheritDoc}
     */
    public function getRepositoryUrl()
    {
        return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo'];
    }

    /**
     * {@inheritDoc}
     */
    public function getUrl()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return $this->project['web_url'];
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier;

        return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
    }

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }

        return array('type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->project['default_branch'];
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }

        if (!$this->branches) {
            $this->branches = $this->getReferences('branches');
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }

        if (!$this->tags) {
            $this->tags = $this->getReferences('tags');
        }

        return $this->tags;
    }

    /**
     * @return string Base URL for GitLab API v3
     */
    public function getApiUrl()
    {
        return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository);
    }

    /**
     * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.`
     *
     * @param  string $string
     * @return string
     */
    private function urlEncodeAll($string)
    {
        $encoded = '';
        for ($i = 0; isset($string[$i]); $i++) {
            $character = $string[$i];
            if (!ctype_alnum($character) && !in_array($character, array('-', '_'), true)) {
                $character = '%' . sprintf('%02X', ord($character));
            }
            $encoded .= $character;
        }

        return $encoded;
    }

    /**
     * @param string $type
     *
     * @return string[] where keys are named references like tags or branches and the value a sha
     */
    protected function getReferences($type)
    {
        $perPage = 100;
        $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage;

        $references = array();
        do {
            $data = JsonFile::parseJson($this->getContents($resource), $resource);

            foreach ($data as $datum) {
                $references[$datum['name']] = $datum['commit']['id'];

                // Keep the last commit date of a reference to avoid
                // unnecessary API call when retrieving the composer file.
                $this->commits[$datum['commit']['id']] = $datum['commit'];
            }

            if (count($data) >= $perPage) {
                $resource = $this->getNextPage();
            } else {
                $resource = false;
            }
        } while ($resource);

        return $references;
    }

    protected function fetchProject()
    {
        // we need to fetch the default branch from the api
        $resource = $this->getApiUrl();
        $this->project = JsonFile::parseJson($this->getContents($resource, true), $resource);
        if (isset($this->project['visibility'])) {
            $this->isPrivate = $this->project['visibility'] !== 'public';
        } else {
            // client is not authendicated, therefore repository has to be public
            $this->isPrivate = false;
        }
    }

    protected function attemptCloneFallback()
    {
        try {
            if ($this->isPrivate === false) {
                $url = $this->generatePublicUrl();
            } else {
                $url = $this->generateSshUrl();
            }

            // If this repository may be private and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($url);

            return;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials</error>');
            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     *
     * @return string
     */
    protected function generateSshUrl()
    {
        if ($this->hasNonstandardOrigin) {
            return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function generatePublicUrl()
    {
        return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function setupGitDriver($url)
    {
        $this->gitDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->gitDriver->initialize();
    }

    /**
     * {@inheritDoc}
     */
    protected function getContents($url, $fetchingRepoData = false)
    {
        try {
            $res = parent::getContents($url);

            if ($fetchingRepoData) {
                $json = JsonFile::parseJson($res, $url);

                // Accessing the API with a token with Guest (10) access will return
                // more data than unauthenticated access but no default_branch data
                // accessing files via the API will then also fail
                if (!isset($json['default_branch']) && isset($json['permissions'])) {
                    $this->isPrivate = $json['visibility'] !== 'public';

                    $moreThanGuestAccess = false;
                    // Check both access levels (e.g. project, group)
                    // - value will be null if no access is set
                    // - value will be array with key access_level if set
                    foreach ($json['permissions'] as $permission) {
                        if ($permission && $permission['access_level'] > 10) {
                            $moreThanGuestAccess = true;
                        }
                    }

                    if (!$moreThanGuestAccess) {
                        $this->io->writeError('<warning>GitLab token with Guest only access detected</warning>');

                        return $this->attemptCloneFallback();
                    }
                }

                // force auth as the unauthenticated version of the API is broken
                if (!isset($json['default_branch'])) {
                    if (!empty($json['id'])) {
                        $this->isPrivate = false;
                    }

                    throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401);
                }
            }

            return $res;
        } catch (TransportException $e) {
            $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        return $this->attemptCloneFallback();
                    }
                    $this->io->writeError('<warning>Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
                    $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        return $this->attemptCloneFallback();
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Uses the config `gitlab-domains` to see if the driver supports the url for the
     * repository given.
     *
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match(self::URL_REGEX, $url, $match)) {
            return false;
        }

        $scheme = !empty($match['scheme']) ? $match['scheme'] : null;
        $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
        $urlParts = explode('/', $match['parts']);

        if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
            return false;
        }

        if ('https' === $scheme && !extension_loaded('openssl')) {
            $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    private function getNextPage()
    {
        $headers = $this->remoteFilesystem->getLastHeaders();
        foreach ($headers as $header) {
            if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
                $links = explode(',', $match[1]);
                foreach ($links as $link) {
                    if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
                        return $match[1];
                    }
                }
            }
        }
    }

    /**
     * @param  array       $configuredDomains
     * @param  string      $guessedDomain
     * @param  array       $urlParts
     * @return bool|string
     */
    private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
    {
        $guessedDomain = strtolower($guessedDomain);

        if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
            if ($portNumber) {
                return $guessedDomain.':'.$portNumber;
            }
            return $guessedDomain;
        }

        if ($portNumber) {
            $guessedDomain .= ':'.$portNumber;
        }

        while (null !== ($part = array_shift($urlParts))) {
            $guessedDomain .= '/' . $part;

            if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{:\d+}', '', $guessedDomain), $configuredDomains))) {
                return $guessedDomain;
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDriver extends VcsDriver
{
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoFile;
    protected $checkoutDir;
    protected $infoCache = array();

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        // Make sure fossil is installed and reachable.
        $this->checkFossil();

        // Ensure we are allowed to use this URL by config.
        $this->config->prohibitUrlByConfig($this->url, $this->io);

        // Only if url points to a locally accessible directory, assume it's the checkout directory.
        // Otherwise, it should be something fossil can clone from.
        if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) {
            $this->checkoutDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $localName = preg_replace('{[^a-z0-9]}i', '-', $this->url);
            $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil';
            $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/';

            $this->updateLocalRepo();
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * Check that fossil can be invoked via command line.
     */
    protected function checkFossil()
    {
        if (0 !== $this->process->execute('fossil version', $ignoredOutput)) {
            throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * Clone or update existing local fossil repository.
     */
    protected function updateLocalRepo()
    {
        $fs = new Filesystem();
        $fs->ensureDirectoryExists($this->checkoutDir);

        if (!is_writable(dirname($this->checkoutDir))) {
            throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.');
        }

        // update the repo if it is a valid fossil repository
        if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) {
            if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) {
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
            }
        } else {
            // clean up directory and do a fresh clone into it
            $fs->removeDirectory($this->checkoutDir);
            $fs->remove($this->repoFile);

            $fs->ensureDirectoryExists($this->checkoutDir);

            if (0 !== $this->process->execute(sprintf('fossil clone -- %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output);
            }

            if (0 !== $this->process->execute(sprintf('fossil open --nested -- %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'trunk';
        }

        return $this->rootIdentifier;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        return array('type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        $command = sprintf('fossil cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        $this->process->execute($command, $content, $this->checkoutDir);

        if (!trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        $this->process->execute('fossil finfo -b -n 1 composer.json', $output, $this->checkoutDir);
        list($ckout, $date, $message) = explode(' ', trim($output), 3);

        return new \DateTime($date, new \DateTimeZone('UTC'));
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if (null === $this->tags) {
            $tags = array();

            $this->process->execute('fossil tag list', $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $tag) {
                $tags[$tag] = $tag;
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if (null === $this->branches) {
            $branches = array();
            $bookmarks = array();

            $this->process->execute('fossil branch list', $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $branch) {
                $branch = trim(preg_replace('/^\*/', '', trim($branch)));
                $branches[$branch] = $branch;
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) {
            return true;
        }

        if (preg_match('!/fossil/|\.fossil!', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a fossil repo in that path
            if ($process->execute('fossil info', $output, $url) === 0) {
                return true;
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDriver extends VcsDriver
{
    protected $depot;
    protected $branch;
    /** @var Perforce */
    protected $perforce;

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        $this->depot = $this->repoConfig['depot'];
        $this->branch = '';
        if (!empty($this->repoConfig['branch'])) {
            $this->branch = $this->repoConfig['branch'];
        }

        $this->initPerforce($this->repoConfig);
        $this->perforce->p4Login();
        $this->perforce->checkStream();

        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();

        return true;
    }

    private function initPerforce($repoConfig)
    {
        if (!empty($this->perforce)) {
            return;
        }

        if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
            throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
        }

        $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
        $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io);
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        return $this->perforce->getFileContent($file, $identifier);
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        return null;
    }

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

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

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

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        $source = array(
            'type' => 'perforce',
            'url' => $this->repoConfig['url'],
            'reference' => $identifier,
            'p4user' => $this->perforce->getUser(),
        );

        return $source;
    }

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

    /**
     * {@inheritDoc}
     */
    public function hasComposerFile($identifier)
    {
        $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);
        $composerInfoIdentifier = $identifier;

        return !empty($composerInfo);
    }

    /**
     * {@inheritDoc}
     */
    public function getContents($url)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if ($deep || preg_match('#\b(perforce|p4)\b#i', $url)) {
            return Perforce::checkServerExists($url, new ProcessExecutor($io));
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function cleanup()
    {
        $this->perforce->cleanupClientSpec();
        $this->perforce = null;
    }

    public function getDepot()
    {
        return $this->depot;
    }

    public function getBranch()
    {
        return $this->branch;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Config;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\Util\Filesystem;

/**
 * A driver implementation for driver with authentication interaction.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class VcsDriver implements VcsDriverInterface
{
    /** @var string */
    protected $url;
    /** @var string */
    protected $originUrl;
    /** @var array */
    protected $repoConfig;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var RemoteFilesystem */
    protected $remoteFilesystem;
    /** @var array */
    protected $infoCache = array();
    /** @var Cache */
    protected $cache;

    /**
     * Constructor.
     *
     * @param array            $repoConfig       The repository configuration
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        if (Filesystem::isLocalPath($repoConfig['url'])) {
            $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']);
        }

        $this->url = $repoConfig['url'];
        $this->originUrl = $repoConfig['url'];
        $this->repoConfig = $repoConfig;
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
    }

    /**
     * Returns whether or not the given $identifier should be cached or not.
     *
     * @param  string $identifier
     * @return bool
     */
    protected function shouldCache($identifier)
    {
        return $this->cache && preg_match('{[a-f0-9]{40}}i', $identifier);
    }

    /**
     * {@inheritdoc}
     */
    public function getComposerInformation($identifier)
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            $composer = $this->getBaseComposerInformation($identifier);

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier, json_encode($composer));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    protected function getBaseComposerInformation($identifier)
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return null;
        }

        $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json');

        if (empty($composer['time']) && $changeDate = $this->getChangeDate($identifier)) {
            $composer['time'] = $changeDate->format(DATE_RFC3339);
        }

        return $composer;
    }

    /**
     * {@inheritDoc}
     */
    public function hasComposerFile($identifier)
    {
        try {
            return (bool) $this->getComposerInformation($identifier);
        } catch (TransportException $e) {
        }

        return false;
    }

    /**
     * Get the https or http protocol depending on SSL support.
     *
     * Call this only if you know that the server supports both.
     *
     * @return string The correct type of protocol
     */
    protected function getScheme()
    {
        if (extension_loaded('openssl')) {
            return 'https';
        }

        return 'http';
    }

    /**
     * Get the remote content.
     *
     * @param string $url The URL of content
     *
     * @return mixed The result
     */
    protected function getContents($url)
    {
        $options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array();

        return $this->remoteFilesystem->getContents($this->originUrl, $url, false, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function cleanup()
    {
        return;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class GitBitbucketDriver extends BitbucketDriver
{
    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (! $this->getRepoData()) {
                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'git') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a git repository, use '.
                    $this->cloneHttpsUrl.' if this is a mercurial bitbucket repository'
                );
            }

            $mainBranchData = $this->getMainBranchData();
            $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'master';
        }

        return $this->rootIdentifier;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function setupFallbackDriver($url)
    {
        $this->fallbackDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * {@inheritdoc}
     */
    protected function generateSshUrl()
    {
        return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Util\GitHub;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHubDriver extends VcsDriver
{
    protected $cache;
    protected $owner;
    protected $repository;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoData;
    protected $hasIssues;
    protected $infoCache = array();
    protected $isPrivate = false;
    private $isArchived = false;
    private $fundingInfo;

    /**
     * Git Driver
     *
     * @var GitDriver
     */
    protected $gitDriver;

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
        $this->owner = $match[3];
        $this->repository = $match[4];
        $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]);
        if ($this->originUrl === 'www.github.com') {
            $this->originUrl = 'github.com';
        }
        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);

        if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
            $this->setupGitDriver($this->url);

            return;
        }

        $this->fetchRootIdentifier();
    }

    public function getRepositoryUrl()
    {
        return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository;
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->rootIdentifier;
    }

    /**
     * {@inheritDoc}
     */
    public function getUrl()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
    }

    /**
     * {@inheritDoc}
     */
    protected function getApiUrl()
    {
        if ('github.com' === $this->originUrl) {
            $apiUrl = 'api.github.com';
        } else {
            $apiUrl = $this->originUrl . '/api/v3';
        }

        return 'https://' . $apiUrl;
    }

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }
        if ($this->isPrivate) {
            // Private GitHub repositories should be accessed using the
            // SSH version of the URL.
            $url = $this->generateSshUrl();
        } else {
            $url = $this->getUrl();
        }

        return array('type' => 'git', 'url' => $url, 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;

        return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
    }

    /**
     * {@inheritDoc}
     */
    public function getComposerInformation($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer =  JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if ($composer) {
                // specials for github
                if (!isset($composer['support']['source'])) {
                    $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
                    $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label);
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
                }
                if (!isset($composer['abandoned']) && $this->isArchived) {
                    $composer['abandoned'] = true;
                }
                if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) {
                    $composer['funding'] = $funding;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    private function getFundingInfo()
    {
        if (null !== $this->fundingInfo) {
            return $this->fundingInfo;
        }

        if ($this->originUrl !== 'github.com') {
            return $this->fundingInfo = false;
        }

        foreach (array($this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml') as $file) {

            try {
                $result = $this->remoteFilesystem->getContents($this->originUrl, $file, false, array(
                    'retry-auth-failure' => false,
                ));
                $response = json_decode($result, true);
            } catch (TransportException $e) {
                continue;
            }
            if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) {
                continue;
            }
            break;
        }
        if (empty($funding)) {
            return $this->fundingInfo = false;
        }

        $result = array();
        $key = null;
        foreach (preg_split('{\r?\n}', $funding) as $line) {
            $line = trim($line);
            if (preg_match('{^(\w+)\s*:\s*(.+)$}', $line, $match)) {
                if (preg_match('{^\[(.*)\](?:\s*#.*)?$}', $match[2], $match2)) {
                    foreach (array_map('trim', preg_split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) {
                        $result[] = array('type' => $match[1], 'url' => trim($item, '"\' '));
                    }
                } elseif (preg_match('{^([^#].*?)(\s+#.*)?$}', $match[2], $match2)) {
                    $result[] = array('type' => $match[1], 'url' => trim($match2[1], '"\' '));
                }
                $key = null;
            } elseif (preg_match('{^(\w+)\s*:\s*#\s*$}', $line, $match)) {
                $key = $match[1];
            } elseif ($key && preg_match('{^-\s*(.+)(\s+#.*)?$}', $line, $match)) {
                $result[] = array('type' => $key, 'url' => trim($match[1], '"\' '));
            }
        }

        foreach ($result as $key => $item) {
            switch ($item['type']) {
                case 'tidelift':
                    $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url'];
                    break;
                case 'github':
                    $result[$key]['url'] = 'https://github.com/' . basename($item['url']);
                    break;
                case 'patreon':
                    $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']);
                    break;
                case 'otechie':
                    $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']);
                    break;
                case 'open_collective':
                    $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']);
                    break;
                case 'liberapay':
                    $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']);
                    break;
                case 'ko_fi':
                    $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']);
                    break;
                case 'issuehunt':
                    $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url'];
                    break;
                case 'community_bridge':
                    $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']);
                    break;
            }
        }

        return $this->fundingInfo = $result;
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier);
        $resource = JsonFile::parseJson($this->getContents($resource));
        if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) {
            throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
        $commit = JsonFile::parseJson($this->getContents($resource), $resource);

        return new \DateTime($commit['commit']['committer']['date']);
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }
        if (null === $this->tags) {
            $this->tags = array();
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100';

            do {
                $tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
                foreach ($tagsData as $tag) {
                    $this->tags[$tag['name']] = $tag['commit']['sha'];
                }

                $resource = $this->getNextPage();
            } while ($resource);
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }
        if (null === $this->branches) {
            $this->branches = array();
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100';

            do {
                $branchData = JsonFile::parseJson($this->getContents($resource), $resource);
                foreach ($branchData as $branch) {
                    $name = substr($branch['ref'], 11);
                    if ($name !== 'gh-pages') {
                        $this->branches[$name] = $branch['object']['sha'];
                    }
                }

                $resource = $this->getNextPage();
            } while ($resource);
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
            return false;
        }

        $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
        if (!in_array(strtolower(preg_replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <github-api>/repos/<owner>/<repo> result
     *
     * @return array|null
     */
    public function getRepoData()
    {
        $this->fetchRootIdentifier();

        return $this->repoData;
    }

    /**
     * Generate an SSH URL
     *
     * @return string
     */
    protected function generateSshUrl()
    {
        if (false !== strpos($this->originUrl, ':')) {
            return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
    }

    /**
     * {@inheritDoc}
     */
    protected function getContents($url, $fetchingRepoData = false)
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        return $this->attemptCloneFallback();
                    }

                    $scopesIssued = array();
                    $scopesNeeded = array();
                    if ($headers = $e->getHeaders()) {
                        if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) {
                            $scopesIssued = explode(' ', $scopes);
                        }
                        if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
                            $scopesNeeded = explode(' ', $scopes);
                        }
                    }
                    $scopesFailed = array_diff($scopesNeeded, $scopesIssued);
                    // non-authenticated requests get no scopesNeeded, so ask for credentials
                    // authenticated requests which failed some scopes should ask for new credentials too
                    if (!$headers || !count($scopesNeeded) || count($scopesFailed)) {
                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');
                    }

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        return $this->attemptCloneFallback();
                    }

                    $rateLimited = $gitHubUtil->isRateLimited($e->getHeaders());

                    if (!$this->io->hasAuthentication($this->originUrl)) {
                        if (!$this->io->isInteractive()) {
                            $this->io->writeError('<error>GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>');
                            throw $e;
                        }

                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit (<info>'.$this->url.'</info>)');

                        return parent::getContents($url);
                    }

                    if ($rateLimited) {
                        $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders());
                        $this->io->writeError(sprintf(
                            '<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>',
                            $rateLimit['limit'],
                            $rateLimit['reset']
                        ));
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Fetch root identifier from GitHub
     *
     * @throws TransportException
     */
    protected function fetchRootIdentifier()
    {
        if ($this->repoData) {
            return;
        }

        $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;

        $this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl);
        if (null === $this->repoData && null !== $this->gitDriver) {
            return;
        }

        $this->owner = $this->repoData['owner']['login'];
        $this->repository = $this->repoData['name'];

        $this->isPrivate = !empty($this->repoData['private']);
        if (isset($this->repoData['default_branch'])) {
            $this->rootIdentifier = $this->repoData['default_branch'];
        } elseif (isset($this->repoData['master_branch'])) {
            $this->rootIdentifier = $this->repoData['master_branch'];
        } else {
            $this->rootIdentifier = 'master';
        }
        $this->hasIssues = !empty($this->repoData['has_issues']);
        $this->isArchived = !empty($this->repoData['archived']);
    }

    protected function attemptCloneFallback()
    {
        $this->isPrivate = true;

        try {
            // If this repository may be private (hard to say for sure,
            // GitHub returns 404 for private repositories) and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($this->generateSshUrl());

            return;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials</error>');
            throw $e;
        }
    }

    protected function setupGitDriver($url)
    {
        $this->gitDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->gitDriver->initialize();
    }

    protected function getNextPage()
    {
        $headers = $this->remoteFilesystem->getLastHeaders();
        foreach ($headers as $header) {
            if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
                $links = explode(',', $match[1]);
                foreach ($links as $link) {
                    if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
                        return $match[1];
                    }
                }
            }
        }
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface VcsDriverInterface
{
    /**
     * Initializes the driver (git clone, svn checkout, fetch info etc)
     */
    public function initialize();

    /**
     * Return the composer.json file information
     *
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  containing all infos from the composer.json file
     */
    public function getComposerInformation($identifier);

    /**
     * Return the content of $file or null if the file does not exist.
     *
     * @param  string $file
     * @param  string $identifier
     * @return string
     */
    public function getFileContent($file, $identifier);

    /**
     * Get the changedate for $identifier.
     *
     * @param  string    $identifier
     * @return \DateTime
     */
    public function getChangeDate($identifier);

    /**
     * Return the root identifier (trunk, master, default/tip ..)
     *
     * @return string Identifier
     */
    public function getRootIdentifier();

    /**
     * Return list of branches in the repository
     *
     * @return array Branch names as keys, identifiers as values
     */
    public function getBranches();

    /**
     * Return list of tags in the repository
     *
     * @return array Tag names as keys, identifiers as values
     */
    public function getTags();

    /**
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  With type, url reference and shasum keys.
     */
    public function getDist($identifier);

    /**
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  With type, url and reference keys.
     */
    public function getSource($identifier);

    /**
     * Return the URL of the repository
     *
     * @return string
     */
    public function getUrl();

    /**
     * Return true if the repository has a composer file for a given identifier,
     * false otherwise.
     *
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return bool   Whether the repository has a composer file for a given identifier.
     */
    public function hasComposerFile($identifier);

    /**
     * Performs any cleanup necessary as the driver is not longer needed
     */
    public function cleanup();

    /**
     * Checks if this driver can handle a given url
     *
     * @param  IOInterface $io     IO instance
     * @param  Config      $config current $config
     * @param  string      $url    URL to validate/check
     * @param  bool        $deep   unless true, only shallow checks (url matching typically) should be done
     * @return bool
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false);
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Util\Bitbucket;

abstract class BitbucketDriver extends VcsDriver
{
    /** @var Cache */
    protected $cache;
    protected $owner;
    protected $repository;
    protected $hasIssues;
    protected $rootIdentifier;
    protected $tags;
    protected $branches;
    protected $infoCache = array();
    protected $branchesUrl = '';
    protected $tagsUrl = '';
    protected $homeUrl = '';
    protected $website = '';
    protected $cloneHttpsUrl = '';

    /**
     * @var VcsDriver
     */
    protected $fallbackDriver;
    /** @var string|null if set either git or hg */
    protected $vcsType;

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#i', $this->url, $match);
        $this->owner = $match[1];
        $this->repository = $match[2];
        $this->originUrl = 'bitbucket.org';
        $this->cache = new Cache(
            $this->io,
            implode('/', array(
                $this->config->get('cache-repo-dir'),
                $this->originUrl,
                $this->owner,
                $this->repository,
            ))
        );
    }

    /**
     * {@inheritDoc}
     */
    public function getUrl()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getUrl();
        }

        return $this->cloneHttpsUrl;
    }

    /**
     * Attempts to fetch the repository data via the BitBucket API and
     * sets some parameters which are used in other methods
     *
     * @return bool
     */
    protected function getRepoData()
    {
        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s?%s',
            $this->owner,
            $this->repository,
            http_build_query(
                array('fields' => '-project,-owner'),
                null,
                '&'
            )
        );

        $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource);
        if ($this->fallbackDriver) {
            return false;
        }
        $this->parseCloneUrls($repoData['links']['clone']);

        $this->hasIssues = !empty($repoData['has_issues']);
        $this->branchesUrl = $repoData['links']['branches']['href'];
        $this->tagsUrl = $repoData['links']['tags']['href'];
        $this->homeUrl = $repoData['links']['html']['href'];
        $this->website = $repoData['website'];
        $this->vcsType = $repoData['scm'];

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function getComposerInformation($identifier)
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if ($composer) {
                // specials for bitbucket
                if (!isset($composer['support']['source'])) {
                    $label = array_search(
                        $identifier,
                        $this->getTags()
                    ) ?: array_search(
                        $identifier,
                        $this->getBranches()
                    ) ?: $identifier;

                    if (array_key_exists($label, $tags = $this->getTags())) {
                        $hash = $tags[$label];
                    } elseif (array_key_exists($label, $branches = $this->getBranches())) {
                        $hash = $branches[$label];
                    }

                    if (! isset($hash)) {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src',
                            $this->originUrl,
                            $this->owner,
                            $this->repository
                        );
                    } else {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src/%s/?at=%s',
                            $this->originUrl,
                            $this->owner,
                            $this->repository,
                            $hash,
                            $label
                        );
                    }
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf(
                        'https://%s/%s/%s/issues',
                        $this->originUrl,
                        $this->owner,
                        $this->repository
                    );
                }
                if (!isset($composer['homepage'])) {
                    $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($file, $identifier)
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getFileContent($file, $identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s',
            $this->owner,
            $this->repository,
            $identifier,
            $file
        );

        return $this->getContentsWithOAuthCredentials($resource);
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getChangeDate($identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
            $this->owner,
            $this->repository,
            $identifier
        );
        $commit = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);

        return new \DateTime($commit['date']);
    }

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getSource($identifier);
        }

        return array('type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getDist($identifier);
        }

        $url = sprintf(
            'https://bitbucket.org/%s/%s/get/%s.zip',
            $this->owner,
            $this->repository,
            $identifier
        );

        return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getTags();
        }

        if (null === $this->tags) {
            $this->tags = array();
            $resource = sprintf(
                '%s?%s',
                $this->tagsUrl,
                http_build_query(
                    array(
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,next',
                        'sort' => '-target.date',
                    ),
                    null,
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
                foreach ($tagsData['values'] as $data) {
                    $this->tags[$data['name']] = $data['target']['hash'];
                }
                if (empty($tagsData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $tagsData['next'];
                }
            }
            if ($this->vcsType === 'hg') {
                unset($this->tags['tip']);
            }
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getBranches();
        }

        if (null === $this->branches) {
            $this->branches = array();
            $resource = sprintf(
                '%s?%s',
                $this->branchesUrl,
                http_build_query(
                    array(
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,values.heads,next',
                        'sort' => '-target.date',
                    ),
                    null,
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
                foreach ($branchData['values'] as $data) {
                    // skip headless branches which seem to be deleted branches that bitbucket nevertheless returns in the API
                    if ($this->vcsType === 'hg' && empty($data['heads'])) {
                        continue;
                    }

                    $this->branches[$data['name']] = $data['target']['hash'];
                }
                if (empty($branchData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $branchData['next'];
                }
            }
        }

        return $this->branches;
    }

    /**
     * Get the remote content.
     *
     * @param string $url              The URL of content
     * @param bool   $fetchingRepoData
     *
     * @return mixed The result
     */
    protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false)
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem);

            if (403 === $e->getCode() || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) {
                if (!$this->io->hasAuthentication($this->originUrl)
                    && $bitbucketUtil->authorizeOAuth($this->originUrl)
                ) {
                    return parent::getContents($url);
                }

                if (!$this->io->isInteractive() && $fetchingRepoData) {
                    return $this->attemptCloneFallback();
                }
            }

            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     *
     * @return string
     */
    abstract protected function generateSshUrl();

    protected function attemptCloneFallback()
    {
        try {
            $this->setupFallbackDriver($this->generateSshUrl());
        } catch (\RuntimeException $e) {
            $this->fallbackDriver = null;

            $this->io->writeError(
                '<error>Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode'
                    . ' so that you can enter your Bitbucket OAuth consumer credentials</error>'
            );
            throw $e;
        }
    }

    /**
     * @param  string $url
     * @return void
     */
    abstract protected function setupFallbackDriver($url);

    /**
     * @param  array $cloneLinks
     * @return void
     */
    protected function parseCloneUrls(array $cloneLinks)
    {
        foreach ($cloneLinks as $cloneLink) {
            if ($cloneLink['name'] === 'https') {
                // Format: https://(user@)bitbucket.org/{user}/{repo}
                // Strip username from URL (only present in clone URL's for private repositories)
                $this->cloneHttpsUrl = preg_replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']);
            }
        }
    }

    /**
     * @return array|null
     */
    protected function getMainBranchData()
    {
        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s?fields=mainbranch',
            $this->owner,
            $this->repository
        );

        $data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
        if (isset($data['mainbranch'])) {
            return $data['mainbranch'];
        }

        return null;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgBitbucketDriver extends BitbucketDriver
{
    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (! $this->getRepoData()) {
                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'hg') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a mercurial repository, use '.
                    $this->cloneHttpsUrl.' if this is a git bitbucket repository'
                );
            }

            $mainBranchData = $this->getMainBranchData();
            $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'default';
        }

        return $this->rootIdentifier;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function setupFallbackDriver($url)
    {
        $this->fallbackDriver = new HgDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * {@inheritdoc}
     */
    protected function generateSshUrl()
    {
        return 'ssh://hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Svn as SvnUtil;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDriver extends VcsDriver
{
    /**
     * @var Cache
     */
    protected $cache;
    protected $baseUrl;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $infoCache = array();

    protected $trunkPath = 'trunk';
    protected $branchesPath = 'branches';
    protected $tagsPath = 'tags';
    protected $packagePath = '';
    protected $cacheCredentials = true;

    /**
     * @var \Composer\Util\Svn
     */
    private $util;

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/');

        SvnUtil::cleanEnv();

        if (isset($this->repoConfig['trunk-path'])) {
            $this->trunkPath = $this->repoConfig['trunk-path'];
        }
        if (isset($this->repoConfig['branches-path'])) {
            $this->branchesPath = $this->repoConfig['branches-path'];
        }
        if (isset($this->repoConfig['tags-path'])) {
            $this->tagsPath = $this->repoConfig['tags-path'];
        }
        if (array_key_exists('svn-cache-credentials', $this->repoConfig)) {
            $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials'];
        }
        if (isset($this->repoConfig['package-path'])) {
            $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/');
        }

        if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) {
            $this->baseUrl = substr($this->url, 0, $pos);
        }

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl));

        $this->getBranches();
        $this->getTags();
    }

    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        return $this->rootIdentifier ?: $this->trunkPath;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getSource($identifier)
    {
        return array('type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier);
    }

    /**
     * {@inheritDoc}
     */
    public function getDist($identifier)
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    protected function shouldCache($identifier)
    {
        return $this->cache && preg_match('{@\d+$}', $identifier);
    }

    /**
     * {@inheritdoc}
     */
    public function getComposerInformation($identifier)
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            try {
                $composer = $this->getBaseComposerInformation($identifier);
            } catch (TransportException $e) {
                $message = $e->getMessage();
                if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) {
                    throw $e;
                }
                // remember a not-existent composer.json
                $composer = '';
            }

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier.'.json', json_encode($composer));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @param string $file
     * @param string $identifier
     */
    public function getFileContent($file, $identifier)
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match);
        if (!empty($match[2])) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        try {
            $resource = $path.$file;
            $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev);
            if (!trim($output)) {
                return null;
            }
        } catch (\RuntimeException $e) {
            throw new TransportException($e->getMessage());
        }

        return $output;
    }

    /**
     * {@inheritdoc}
     */
    public function getChangeDate($identifier)
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match);
        if (!empty($match[2])) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        $output = $this->execute('svn info', $this->baseUrl . $path . $rev);
        foreach ($this->process->splitLines($output) as $line) {
            if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {
                return new \DateTime($match[1], new \DateTimeZone('UTC'));
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if (null === $this->tags) {
            $this->tags = array();

            if ($this->tagsPath !== false) {
                $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath);
                if ($output) {
                    foreach ($this->process->splitLines($output) as $line) {
                        $line = trim($line);
                        if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if (isset($match[1]) && isset($match[2]) && $match[2] !== './') {
                                $this->tags[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->tagsPath . '/' . $match[2],
                                    $match[1]
                                );
                            }
                        }
                    }
                }
            }
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if (null === $this->branches) {
            $this->branches = array();

            if (false === $this->trunkPath) {
                $trunkParent = $this->baseUrl . '/';
            } else {
                $trunkParent = $this->baseUrl . '/' . $this->trunkPath;
            }

            $output = $this->execute('svn ls --verbose', $trunkParent);
            if ($output) {
                foreach ($this->process->splitLines($output) as $line) {
                    $line = trim($line);
                    if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                        if (isset($match[1]) && isset($match[2]) && $match[2] === './') {
                            $this->branches['trunk'] = $this->buildIdentifier(
                                '/' . $this->trunkPath,
                                $match[1]
                            );
                            $this->rootIdentifier = $this->branches['trunk'];
                            break;
                        }
                    }
                }
            }
            unset($output);

            if ($this->branchesPath !== false) {
                $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath);
                if ($output) {
                    foreach ($this->process->splitLines(trim($output)) as $line) {
                        $line = trim($line);
                        if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if (isset($match[1]) && isset($match[2]) && $match[2] !== './') {
                                $this->branches[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->branchesPath . '/' . $match[2],
                                    $match[1]
                                );
                            }
                        }
                    }
                }
            }
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        $url = self::normalizeUrl($url);
        if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
            return true;
        }

        // proceed with deep check for local urls since they are fast to process
        if (!$deep && !Filesystem::isLocalPath($url)) {
            return false;
        }

        $processExecutor = new ProcessExecutor($io);

        $exit = $processExecutor->execute(
            "svn info --non-interactive -- ".ProcessExecutor::escape($url),
            $ignoredOutput
        );

        if ($exit === 0) {
            // This is definitely a Subversion repository.
            return true;
        }

        // Subversion client 1.7 and older
        if (false !== stripos($processExecutor->getErrorOutput(), 'authorization failed:')) {
            // This is likely a remote Subversion repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        // Subversion client 1.8 and newer
        if (false !== stripos($processExecutor->getErrorOutput(), 'Authentication failed')) {
            // This is likely a remote Subversion or newer repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        return false;
    }

    /**
     * An absolute path (leading '/') is converted to a file:// url.
     *
     * @param string $url
     *
     * @return string
     */
    protected static function normalizeUrl($url)
    {
        $fs = new Filesystem();
        if ($fs->isAbsolutePath($url)) {
            return 'file://' . strtr($url, '\\', '/');
        }

        return $url;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  string            $command The svn command to run.
     * @param  string            $url     The SVN URL.
     * @throws \RuntimeException
     * @return string
     */
    protected function execute($command, $url)
    {
        if (null === $this->util) {
            $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process);
            $this->util->setCacheCredentials($this->cacheCredentials);
        }

        try {
            return $this->util->execute($command, $url);
        } catch (\RuntimeException $e) {
            if (null === $this->util->binaryVersion()) {
                throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput());
            }

            throw new \RuntimeException(
                'Repository '.$this->url.' could not be processed, '.$e->getMessage()
            );
        }
    }

    /**
     * Build the identifier respecting "package-path" config option
     *
     * @param string $baseDir  The path to trunk/branch/tag
     * @param int    $revision The revision mark to add to identifier
     *
     * @return string
     */
    protected function buildIdentifier($baseDir, $revision)
    {
        return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\RemoteFilesystem;

/**
 * Repositories manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class RepositoryManager
{
    private $localRepository;
    private $repositories = array();
    private $repositoryClasses = array();
    private $io;
    private $config;
    private $eventDispatcher;
    private $rfs;

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

    /**
     * Searches for a package by it's name and version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface|null
     */
    public function findPackage($name, $constraint)
    {
        foreach ($this->repositories as $repository) {
            /** @var RepositoryInterface $repository */
            if ($package = $repository->findPackage($name, $constraint)) {
                return $package;
            }
        }

        return null;
    }

    /**
     * Searches for all packages matching a name and optionally a version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface[]
     */
    public function findPackages($name, $constraint)
    {
        $packages = array();

        foreach ($this->getRepositories() as $repository) {
            $packages = array_merge($packages, $repository->findPackages($name, $constraint));
        }

        return $packages;
    }

    /**
     * Adds repository
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function addRepository(RepositoryInterface $repository)
    {
        $this->repositories[] = $repository;
    }

    /**
     * Adds a repository to the beginning of the chain
     *
     * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin.
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function prependRepository(RepositoryInterface $repository)
    {
        array_unshift($this->repositories, $repository);
    }

    /**
     * Returns a new repository for a specific installation type.
     *
     * @param  string                    $type   repository type
     * @param  array                     $config repository configuration
     * @param  string                    $name   repository name
     * @throws \InvalidArgumentException if repository for provided type is not registered
     * @return RepositoryInterface
     */
    public function createRepository($type, $config, $name = null)
    {
        if (!isset($this->repositoryClasses[$type])) {
            throw new \InvalidArgumentException('Repository type is not registered: '.$type);
        }

        if (isset($config['packagist']) && false === $config['packagist']) {
            $this->io->writeError('<warning>Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition</warning>');
        }

        $class = $this->repositoryClasses[$type];

        $reflMethod = new \ReflectionMethod($class, '__construct');
        $params = $reflMethod->getParameters();
        if (isset($params[4])) {
            $paramType = null;
            if (\PHP_VERSION_ID >= 70000) {
                $reflectionType = $params[4]->getType();
                if ($reflectionType) {
                    $paramType = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string)$reflectionType;
                }
            } else {
                $paramType = $params[4]->getClass() ? $params[4]->getClass()->getName() : null;
            }

            if ($paramType  === 'Composer\Util\RemoteFilesystem') {
                return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs);
            }
        }

        return new $class($config, $this->io, $this->config, $this->eventDispatcher);
    }

    /**
     * Stores repository class for a specific installation type.
     *
     * @param string $type  installation type
     * @param string $class class name of the repo implementation
     */
    public function setRepositoryClass($type, $class)
    {
        $this->repositoryClasses[$type] = $class;
    }

    /**
     * Returns all repositories, except local one.
     *
     * @return RepositoryInterface[]
     */
    public function getRepositories()
    {
        return $this->repositories;
    }

    /**
     * Sets local repository for the project.
     *
     * @param WritableRepositoryInterface $repository repository instance
     */
    public function setLocalRepository(WritableRepositoryInterface $repository)
    {
        $this->localRepository = $repository;
    }

    /**
     * Returns local repository for the project.
     *
     * @return WritableRepositoryInterface
     */
    public function getLocalRepository()
    {
        return $this->localRepository;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR channel info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelInfo
{
    private $name;
    private $alias;
    private $packages;

    /**
     * @param string        $name
     * @param string        $alias
     * @param PackageInfo[] $packages
     */
    public function __construct($name, $alias, array $packages)
    {
        $this->name = $name;
        $this->alias = $alias;
        $this->packages = $packages;
    }

    /**
     * Name of the channel
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Alias of the channel
     *
     * @return string
     */
    public function getAlias()
    {
        return $this->alias;
    }

    /**
     * List of channel packages
     *
     * @return PackageInfo[]
     */
    public function getPackages()
    {
        return $this->packages;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ReleaseInfo
{
    private $stability;
    private $dependencyInfo;

    /**
     * @param string         $stability
     * @param DependencyInfo $dependencyInfo
     */
    public function __construct($stability, $dependencyInfo)
    {
        $this->stability = $stability;
        $this->dependencyInfo = $dependencyInfo;
    }

    /**
     * @return DependencyInfo release dependencies
     */
    public function getDependencyInfo()
    {
        return $this->dependencyInfo;
    }

    /**
     * @return string release stability
     */
    public function getStability()
    {
        return $this->stability;
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Util\RemoteFilesystem;

/**
 * Base PEAR Channel reader.
 *
 * Provides xml namespaces and red
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
abstract class BaseChannelReader
{
    /**
     * PEAR REST Interface namespaces
     */
    const CHANNEL_NS = 'http://pear.php.net/channel-1.0';
    const ALL_CATEGORIES_NS = 'http://pear.php.net/dtd/rest.allcategories';
    const CATEGORY_PACKAGES_INFO_NS = 'http://pear.php.net/dtd/rest.categorypackageinfo';
    const ALL_PACKAGES_NS = 'http://pear.php.net/dtd/rest.allpackages';
    const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases';
    const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package';

    /** @var RemoteFilesystem */
    private $rfs;

    protected function __construct(RemoteFilesystem $rfs)
    {
        $this->rfs = $rfs;
    }

    /**
     * Read content from remote filesystem.
     *
     * @param string $origin server
     * @param string $path   relative path to content
     * @throws \UnexpectedValueException
     * @return \SimpleXMLElement
     */
    protected function requestContent($origin, $path)
    {
        $url = rtrim($origin, '/') . '/' . ltrim($path, '/');
        $content = $this->rfs->getContents($origin, $url, false);
        if (!$content) {
            throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.');
        }

        return str_replace('http://pear.php.net/rest/', 'https://pear.php.net/rest/', $content);
    }

    /**
     * Read xml content from remote filesystem
     *
     * @param string $origin server
     * @param string $path   relative path to content
     * @throws \UnexpectedValueException
     * @return \SimpleXMLElement
     */
    protected function requestXml($origin, $path)
    {
        // http://components.ez.no/p/packages.xml is malformed. to read it we must ignore parsing errors.
        $xml = simplexml_load_string($this->requestContent($origin, $path), "SimpleXMLElement", LIBXML_NOERROR);

        if (false === $xml) {
            throw new \UnexpectedValueException(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path));
        }

        return $xml;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR Package info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PackageInfo
{
    private $channelName;
    private $packageName;
    private $license;
    private $shortDescription;
    private $description;
    private $releases;

    /**
     * @param string        $channelName
     * @param string        $packageName
     * @param string        $license
     * @param string        $shortDescription
     * @param string        $description
     * @param ReleaseInfo[] $releases         associative array maps release version to release info
     */
    public function __construct($channelName, $packageName, $license, $shortDescription, $description, $releases)
    {
        $this->channelName = $channelName;
        $this->packageName = $packageName;
        $this->license = $license;
        $this->shortDescription = $shortDescription;
        $this->description = $description;
        $this->releases = $releases;
    }

    /**
     * @return string the package channel name
     */
    public function getChannelName()
    {
        return $this->channelName;
    }

    /**
     * @return string the package name
     */
    public function getPackageName()
    {
        return $this->packageName;
    }

    /**
     * @return string the package description
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @return string the package short description
     */
    public function getShortDescription()
    {
        return $this->shortDescription;
    }

    /**
     * @return string the package license
     */
    public function getLicense()
    {
        return $this->license;
    }

    /**
     * @return ReleaseInfo[]
     */
    public function getReleases()
    {
        return $this->releases;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * Read PEAR packages using REST 1.0 interface
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PackageDependencyParser
{
    /**
     * Builds dependency information. It detects used package.xml format.
     *
     * @param array $depArray
     * @return DependencyInfo
     */
    public function buildDependencyInfo($depArray)
    {
        if (!is_array($depArray)) {
            return new DependencyInfo(array(), array());
        }
        if (!$this->isHash($depArray)) {
            return new DependencyInfo($this->buildDependency10Info($depArray), array());
        }

        return $this->buildDependency20Info($depArray);
    }

    /**
     * Builds dependency information from package.xml 1.0 format
     *
     * https://pear.php.net/manual/en/guide.developers.package2.dependencies.php
     *
     * package.xml 1.0 format consists of array of
     * { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes"
     *   channel="channelName" name="extName|packageName" }
     *
     * @param array $depArray Dependency data in package.xml 1.0 format
     * @return DependencyConstraint[]
     */
    private function buildDependency10Info($depArray)
    {
        static $dep10toOperatorMap = array('has' => '==', 'eq' => '==', 'ge' => '>=', 'gt' => '>', 'le' => '<=', 'lt' => '<', 'not' => '!=');

        $result = array();

        foreach ($depArray as $depItem) {
            if (empty($depItem['rel']) || !array_key_exists($depItem['rel'], $dep10toOperatorMap)) {
                // 'unknown rel type:' . $depItem['rel'];
                continue;
            }

            $depType = !empty($depItem['optional']) && 'yes' == $depItem['optional']
                ? 'optional'
                : 'required';
            $depType = 'not' == $depItem['rel']
                ? 'conflicts'
                : $depType;

            $depVersion = !empty($depItem['version']) ? $this->parseVersion($depItem['version']) : '*';

            // has & not are special operators that does not requires version
            $depVersionConstraint = ('has' == $depItem['rel'] || 'not' == $depItem['rel']) && '*' == $depVersion
                ? '*'
                : $dep10toOperatorMap[$depItem['rel']] . $depVersion;

            switch ($depItem['type']) {
                case 'php':
                    $depChannelName = 'php';
                    $depPackageName = '';
                    break;
                case 'pkg':
                    $depChannelName = !empty($depItem['channel']) ? $depItem['channel'] : 'pear.php.net';
                    $depPackageName = $depItem['name'];
                    break;
                case 'ext':
                    $depChannelName = 'ext';
                    $depPackageName = $depItem['name'];
                    break;
                case 'os':
                case 'sapi':
                    $depChannelName = '';
                    $depPackageName = '';
                break;
                default:
                    $depChannelName = '';
                    $depPackageName = '';
                    break;
            }

            if ('' != $depChannelName) {
                $result[] = new DependencyConstraint(
                    $depType,
                    $depVersionConstraint,
                    $depChannelName,
                    $depPackageName
                );
            }
        }

        return $result;
    }

    /**
     * Builds dependency information from package.xml 2.0 format
     *
     * @param array $depArray Dependency data in package.xml 1.0 format
     * @return DependencyInfo
     */
    private function buildDependency20Info($depArray)
    {
        $result = array();
        $optionals = array();
        $defaultOptionals = array();
        foreach ($depArray as $depType => $depTypeGroup) {
            if (!is_array($depTypeGroup)) {
                continue;
            }
            if ('required' == $depType || 'optional' == $depType) {
                foreach ($depTypeGroup as $depItemType => $depItem) {
                    switch ($depItemType) {
                        case 'php':
                            $result[] = new DependencyConstraint(
                                $depType,
                                $this->parse20VersionConstraint($depItem),
                                'php',
                                ''
                            );
                            break;
                        case 'package':
                            $deps = $this->buildDepPackageConstraints($depItem, $depType);
                            $result = array_merge($result, $deps);
                            break;
                        case 'extension':
                            $deps = $this->buildDepExtensionConstraints($depItem, $depType);
                            $result = array_merge($result, $deps);
                            break;
                        case 'subpackage':
                            $deps = $this->buildDepPackageConstraints($depItem, 'replaces');
                            $defaultOptionals += $deps;
                            break;
                        case 'os':
                        case 'pearinstaller':
                            break;
                        default:
                            break;
                    }
                }
            } elseif ('group' == $depType) {
                if ($this->isHash($depTypeGroup)) {
                    $depTypeGroup = array($depTypeGroup);
                }

                foreach ($depTypeGroup as $depItem) {
                    $groupName = $depItem['attribs']['name'];
                    if (!isset($optionals[$groupName])) {
                        $optionals[$groupName] = array();
                    }

                    if (isset($depItem['subpackage'])) {
                        $optionals[$groupName] += $this->buildDepPackageConstraints($depItem['subpackage'], 'replaces');
                    } else {
                        $result += $this->buildDepPackageConstraints($depItem['package'], 'optional');
                    }
                }
            }
        }

        if (count($defaultOptionals) > 0) {
            $optionals['*'] = $defaultOptionals;
        }

        return new DependencyInfo($result, $optionals);
    }

    /**
     * Builds dependency constraint of 'extension' type
     *
     * @param array $depItem dependency constraint or array of dependency constraints
     * @param string $depType target type of building constraint.
     * @return DependencyConstraint[]
     */
    private function buildDepExtensionConstraints($depItem, $depType)
    {
        if ($this->isHash($depItem)) {
            $depItem = array($depItem);
        }

        $result = array();
        foreach ($depItem as $subDepItem) {
            $depChannelName = 'ext';
            $depPackageName = $subDepItem['name'];
            $depVersionConstraint = $this->parse20VersionConstraint($subDepItem);

            $result[] = new DependencyConstraint(
                $depType,
                $depVersionConstraint,
                $depChannelName,
                $depPackageName
            );
        }

        return $result;
    }

    /**
     * Builds dependency constraint of 'package' type
     *
     * @param array $depItem dependency constraint or array of dependency constraints
     * @param string $depType target type of building constraint.
     * @return DependencyConstraint[]
     */
    private function buildDepPackageConstraints($depItem, $depType)
    {
        if ($this->isHash($depItem)) {
            $depItem = array($depItem);
        }

        $result = array();
        foreach ($depItem as $subDepItem) {
            if (!array_key_exists('channel', $subDepItem)) {
                $subDepItem['channel'] = $subDepItem['uri'];
            }
            $depChannelName = $subDepItem['channel'];
            $depPackageName = $subDepItem['name'];
            $depVersionConstraint = $this->parse20VersionConstraint($subDepItem);
            if (isset($subDepItem['conflicts'])) {
                $depType = 'conflicts';
            }

            $result[] = new DependencyConstraint(
                $depType,
                $depVersionConstraint,
                $depChannelName,
                $depPackageName
            );
        }

        return $result;
    }

    /**
     * Parses version constraint
     *
     * @param  array  $data array containing several 'min', 'max', 'has', 'exclude' and other keys.
     * @return string
     */
    private function parse20VersionConstraint(array $data)
    {
        static $dep20toOperatorMap = array('has' => '==', 'min' => '>=', 'max' => '<=', 'exclude' => '!=');

        $versions = array();
        $values = array_intersect_key($data, $dep20toOperatorMap);
        if (0 == count($values)) {
            return '*';
        }
        if (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) {
            $versions[] = '>' . $this->parseVersion($values['min']);
        } elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) {
            $versions[] = '<' . $this->parseVersion($values['max']);
        } else {
            foreach ($values as $op => $version) {
                if ('exclude' == $op && is_array($version)) {
                    foreach ($version as $versionPart) {
                        $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($versionPart);
                    }
                } else {
                    $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($version);
                }
            }
        }

        return implode(',', $versions);
    }

    /**
     * Softened version parser
     *
     * @param string $version
     * @return null|string
     */
    private function parseVersion($version)
    {
        if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) {
            $version = $matches[1]
                .(!empty($matches[2]) ? $matches[2] : '.0')
                .(!empty($matches[3]) ? $matches[3] : '.0')
                .(!empty($matches[4]) ? $matches[4] : '.0');

            return $version;
        }

        return null;
    }

    /**
     * Test if array is associative or hash type
     *
     * @param  array $array
     * @return bool
     */
    private function isHash(array $array)
    {
        return !array_key_exists(1, $array) && !array_key_exists(0, $array);
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release dependency info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class DependencyConstraint
{
    private $type;
    private $constraint;
    private $channelName;
    private $packageName;

    /**
     * @param string $type
     * @param string $constraint
     * @param string $channelName
     * @param string $packageName
     */
    public function __construct($type, $constraint, $channelName, $packageName)
    {
        $this->type = $type;
        $this->constraint = $constraint;
        $this->channelName = $channelName;
        $this->packageName = $packageName;
    }

    public function getChannelName()
    {
        return $this->channelName;
    }

    public function getConstraint()
    {
        return $this->constraint;
    }

    public function getPackageName()
    {
        return $this->packageName;
    }

    public function getType()
    {
        return $this->type;
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Util\RemoteFilesystem;

/**
 * PEAR Channel package reader.
 *
 * Reads channel packages info from and builds Package's
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelReader extends BaseChannelReader
{
    /** @var array of ('xpath test' => 'rest implementation') */
    private $readerMap;

    public function __construct(RemoteFilesystem $rfs)
    {
        parent::__construct($rfs);

        $rest10reader = new ChannelRest10Reader($rfs);
        $rest11reader = new ChannelRest11Reader($rfs);

        $this->readerMap = array(
            'REST1.3' => $rest11reader,
            'REST1.2' => $rest11reader,
            'REST1.1' => $rest11reader,
            'REST1.0' => $rest10reader,
        );
    }

    /**
     * Reads PEAR channel through REST interface and builds list of packages
     *
     * @param string $url PEAR Channel url
     * @throws \UnexpectedValueException
     * @return ChannelInfo
     */
    public function read($url)
    {
        $xml = $this->requestXml($url, "/channel.xml");

        $channelName = (string) $xml->name;
        $channelAlias = (string) $xml->suggestedalias;

        $supportedVersions = array_keys($this->readerMap);
        $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions);
        if (!$selectedRestVersion) {
            throw new \UnexpectedValueException(sprintf('PEAR repository %s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions)));
        }

        $reader = $this->readerMap[$selectedRestVersion['version']];
        $packageDefinitions = $reader->read($selectedRestVersion['baseUrl']);

        return new ChannelInfo($channelName, $channelAlias, $packageDefinitions);
    }

    /**
     * Reads channel supported REST interfaces and selects one of them
     *
     * @param \SimpleXMLElement $channelXml
     * @param string[] $supportedVersions supported PEAR REST protocols
     * @return array|null hash with selected version and baseUrl
     */
    private function selectRestVersion($channelXml, $supportedVersions)
    {
        $channelXml->registerXPathNamespace('ns', self::CHANNEL_NS);

        foreach ($supportedVersions as $version) {
            $xpathTest = "ns:servers/ns:*/ns:rest/ns:baseurl[@type='{$version}']";
            $testResult = $channelXml->xpath($xpathTest);

            foreach ($testResult as $result) {
                // Choose first https:// option.
                $result = (string) $result;
                if (preg_match('{^https://}i', $result)) {
                    return array('version' => $version, 'baseUrl' => $result);
                }
            }

            // Fallback to non-https if it does not exist.
            if (count($testResult) > 0) {
                return array('version' => $version, 'baseUrl' => (string) $testResult[0]);
            }
        }

        return null;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * Read PEAR packages using REST 1.1 interface
 *
 * At version 1.1 package descriptions read from:
 *  {baseUrl}/c/categories.xml
 *  {baseUrl}/c/{category}/packagesinfo.xml
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelRest11Reader extends BaseChannelReader
{
    private $dependencyReader;

    public function __construct($rfs)
    {
        parent::__construct($rfs);

        $this->dependencyReader = new PackageDependencyParser();
    }

    /**
     * Reads package descriptions using PEAR Rest 1.1 interface
     *
     * @param string $baseUrl base Url interface
     *
     * @return PackageInfo[]
     */
    public function read($baseUrl)
    {
        return $this->readChannelPackages($baseUrl);
    }

    /**
     * Read list of channel categories from
     *  {baseUrl}/c/categories.xml
     *
     * @param string $baseUrl
     * @return PackageInfo[]
     */
    private function readChannelPackages($baseUrl)
    {
        $result = array();

        $xml = $this->requestXml($baseUrl, "/c/categories.xml");
        $xml->registerXPathNamespace('ns', self::ALL_CATEGORIES_NS);
        foreach ($xml->xpath('ns:c') as $node) {
            $categoryName = (string) $node;
            $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName);
            $result = array_merge($result, $categoryPackages);
        }

        return $result;
    }

    /**
     * Read packages from
     *  {baseUrl}/c/{category}/packagesinfo.xml
     *
     * @param string $baseUrl
     * @param string $categoryName
     * @return PackageInfo[]
     */
    private function readCategoryPackages($baseUrl, $categoryName)
    {
        $result = array();

        $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml';
        $xml = $this->requestXml($baseUrl, $categoryPath);
        $xml->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS);
        foreach ($xml->xpath('ns:pi') as $node) {
            $packageInfo = $this->parsePackage($node);
            $result[] = $packageInfo;
        }

        return $result;
    }

    /**
     * Parses package node.
     *
     * @param \SimpleXMLElement $packageInfo  xml element describing package
     * @return PackageInfo
     */
    private function parsePackage($packageInfo)
    {
        $packageInfo->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS);
        $channelName = (string) $packageInfo->p->c;
        $packageName = (string) $packageInfo->p->n;
        $license = (string) $packageInfo->p->l;
        $shortDescription = (string) $packageInfo->p->s;
        $description = (string) $packageInfo->p->d;

        $dependencies = array();
        foreach ($packageInfo->xpath('ns:deps') as $node) {
            $dependencyVersion = (string) $node->v;
            $dependencyArray = unserialize((string) $node->d);

            $dependencyInfo = $this->dependencyReader->buildDependencyInfo($dependencyArray);

            $dependencies[$dependencyVersion] = $dependencyInfo;
        }

        $releases = array();
        $releasesInfo = $packageInfo->xpath('ns:a/ns:r');
        if ($releasesInfo) {
            foreach ($releasesInfo as $node) {
                $releaseVersion = (string) $node->v;
                $releaseStability = (string) $node->s;
                $releases[$releaseVersion] = new ReleaseInfo(
                    $releaseStability,
                    isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array())
                );
            }
        }

        return new PackageInfo(
            $channelName,
            $packageName,
            $license,
            $shortDescription,
            $description,
            $releases
        );
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Downloader\TransportException;

/**
 * Read PEAR packages using REST 1.0 interface
 *
 * At version 1.0 package descriptions read from:
 *  {baseUrl}/p/packages.xml
 *  {baseUrl}/p/{package}/info.xml
 *  {baseUrl}/p/{package}/allreleases.xml
 *  {baseUrl}/p/{package}/deps.{version}.txt
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelRest10Reader extends BaseChannelReader
{
    private $dependencyReader;

    public function __construct($rfs)
    {
        parent::__construct($rfs);

        $this->dependencyReader = new PackageDependencyParser();
    }

    /**
     * Reads package descriptions using PEAR Rest 1.0 interface
     *
     * @param string $baseUrl base Url interface
     *
     * @return PackageInfo[]
     */
    public function read($baseUrl)
    {
        return $this->readPackages($baseUrl);
    }

    /**
     * Read list of packages from
     *  {baseUrl}/p/packages.xml
     *
     * @param string $baseUrl
     * @return PackageInfo[]
     */
    private function readPackages($baseUrl)
    {
        $result = array();

        $xmlPath = '/p/packages.xml';
        $xml = $this->requestXml($baseUrl, $xmlPath);
        $xml->registerXPathNamespace('ns', self::ALL_PACKAGES_NS);
        foreach ($xml->xpath('ns:p') as $node) {
            $packageName = (string) $node;
            $packageInfo = $this->readPackage($baseUrl, $packageName);
            $result[] = $packageInfo;
        }

        return $result;
    }

    /**
     * Read package info from
     *  {baseUrl}/p/{package}/info.xml
     *
     * @param string $baseUrl
     * @param string $packageName
     * @return PackageInfo
     */
    private function readPackage($baseUrl, $packageName)
    {
        $xmlPath = '/p/' . strtolower($packageName) . '/info.xml';
        $xml = $this->requestXml($baseUrl, $xmlPath);
        $xml->registerXPathNamespace('ns', self::PACKAGE_INFO_NS);

        $channelName = (string) $xml->c;
        $packageName = (string) $xml->n;
        $license = (string) $xml->l;
        $shortDescription = (string) $xml->s;
        $description = (string) $xml->d;

        return new PackageInfo(
            $channelName,
            $packageName,
            $license,
            $shortDescription,
            $description,
            $this->readPackageReleases($baseUrl, $packageName)
        );
    }

    /**
     * Read package releases from
     *  {baseUrl}/p/{package}/allreleases.xml
     *
     * @param string $baseUrl
     * @param string $packageName
     * @throws \Composer\Downloader\TransportException|\Exception
     * @return ReleaseInfo[]                                      hash array with keys as version numbers
     */
    private function readPackageReleases($baseUrl, $packageName)
    {
        $result = array();

        try {
            $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml';
            $xml = $this->requestXml($baseUrl, $xmlPath);
            $xml->registerXPathNamespace('ns', self::ALL_RELEASES_NS);
            foreach ($xml->xpath('ns:r') as $node) {
                $releaseVersion = (string) $node->v;
                $releaseStability = (string) $node->s;

                try {
                    $result[$releaseVersion] = new ReleaseInfo(
                        $releaseStability,
                        $this->readPackageReleaseDependencies($baseUrl, $packageName, $releaseVersion)
                    );
                } catch (TransportException $exception) {
                    if ($exception->getCode() != 404) {
                        throw $exception;
                    }
                }
            }
        } catch (TransportException $exception) {
            if ($exception->getCode() != 404) {
                throw $exception;
            }
        }

        return $result;
    }

    /**
     * Read package dependencies from
     *  {baseUrl}/p/{package}/deps.{version}.txt
     *
     * @param string $baseUrl
     * @param string $packageName
     * @param string $version
     * @return DependencyInfo[]
     */
    private function readPackageReleaseDependencies($baseUrl, $packageName, $version)
    {
        $dependencyReader = new PackageDependencyParser();

        $depthPath = '/r/' . strtolower($packageName) . '/deps.' . $version . '.txt';
        $content = $this->requestContent($baseUrl, $depthPath);
        $dependencyArray = unserialize($content);

        return $dependencyReader->buildDependencyInfo($dependencyArray);
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release dependency info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class DependencyInfo
{
    private $requires;
    private $optionals;

    /**
     * @param DependencyConstraint[] $requires  list of requires/conflicts/replaces
     * @param array                  $optionals [groupName => DependencyConstraint[]] list of optional groups
     */
    public function __construct($requires, $optionals)
    {
        $this->requires = $requires;
        $this->optionals = $optionals;
    }

    /**
     * @return DependencyConstraint[] list of requires/conflicts/replaces
     */
    public function getRequires()
    {
        return $this->requires;
    }

    /**
     * @return array [groupName => DependencyConstraint[]] list of optional groups
     */
    public function getOptionals()
    {
        return $this->optionals;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Composite repository.
 *
 * @author Beau Simensen <beau@dflydev.com>
 */
class CompositeRepository extends BaseRepository
{
    /**
     * List of repositories
     * @var array
     */
    private $repositories;

    /**
     * Constructor
     * @param array $repositories
     */
    public function __construct(array $repositories)
    {
        $this->repositories = array();
        foreach ($repositories as $repo) {
            $this->addRepository($repo);
        }
    }

    /**
     * Returns all the wrapped repositories
     *
     * @return array
     */
    public function getRepositories()
    {
        return $this->repositories;
    }

    /**
     * {@inheritdoc}
     */
    public function hasPackage(PackageInterface $package)
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            if ($repository->hasPackage($package)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function findPackage($name, $constraint)
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $package = $repository->findPackage($name, $constraint);
            if (null !== $package) {
                return $package;
            }
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function findPackages($name, $constraint = null)
    {
        $packages = array();
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->findPackages($name, $constraint);
        }

        return $packages ? call_user_func_array('array_merge', $packages) : array();
    }

    /**
     * {@inheritdoc}
     */
    public function search($query, $mode = 0, $type = null)
    {
        $matches = array();
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $matches[] = $repository->search($query, $mode, $type);
        }

        return $matches ? call_user_func_array('array_merge', $matches) : array();
    }

    /**
     * {@inheritdoc}
     */
    public function getPackages()
    {
        $packages = array();
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->getPackages();
        }

        return $packages ? call_user_func_array('array_merge', $packages) : array();
    }

    /**
     * {@inheritdoc}
     */
    public function removePackage(PackageInterface $package)
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $repository->removePackage($package);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function count()
    {
        $total = 0;
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $total += $repository->count();
        }

        return $total;
    }

    /**
     * Add a repository.
     * @param RepositoryInterface $repository
     */
    public function addRepository(RepositoryInterface $repository)
    {
        if ($repository instanceof self) {
            foreach ($repository->getRepositories() as $repo) {
                $this->addRepository($repo);
            }
        } else {
            $this->repositories[] = $repository;
        }
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Installed filesystem repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
{
}
<?php

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

namespace Composer\Repository;

use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;

/**
 * Filesystem repository.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilesystemRepository extends WritableArrayRepository
{
    private $file;

    /**
     * Initializes filesystem repository.
     *
     * @param JsonFile $repositoryFile repository json file
     */
    public function __construct(JsonFile $repositoryFile)
    {
        parent::__construct();
        $this->file = $repositoryFile;
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize()
    {
        parent::initialize();

        if (!$this->file->exists()) {
            return;
        }

        try {
            $packages = $this->file->read();

            // forward compatibility for composer v2 installed.json
            if (isset($packages['packages'])) {
                $packages = $packages['packages'];
            }

            if (!is_array($packages)) {
                throw new \UnexpectedValueException('Could not parse package list from the repository');
            }
        } catch (\Exception $e) {
            throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage());
        }

        $loader = new ArrayLoader(null, true);
        foreach ($packages as $packageData) {
            $package = $loader->load($packageData);
            $this->addPackage($package);
        }
    }

    public function reload()
    {
        $this->packages = null;
        $this->initialize();
    }

    /**
     * Writes writable repository.
     */
    public function write()
    {
        $data = array();
        $dumper = new ArrayDumper();

        foreach ($this->getCanonicalPackages() as $package) {
            $data[] = $dumper->dump($package);
        }

        usort($data, function ($a, $b) {
            return strcmp($a['name'], $b['name']);
        });

        $this->file->write($data);
    }
}
<?php

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

namespace Composer;

use Symfony\Component\Console\Output\OutputInterface;

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

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

    public function __construct(OutputInterface $output)
    {
        parent::__construct('composer', '--ansi');
    }
}
<?php

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

namespace Composer;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Package\Archiver;
use Composer\Package\Version\VersionGuesser;
use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\WritableRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\Util\Silencer;
use Composer\Plugin\PluginEvents;
use Composer\EventDispatcher\Event;
use Seld\JsonLint\DuplicateKeyException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutput;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Version\VersionParser;
use Composer\Downloader\TransportException;
use Seld\JsonLint\JsonParser;

/**
 * Creates a configured instance of composer.
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Factory
{
    /**
     * @throws \RuntimeException
     * @return string
     */
    protected static function getHomeDir()
    {
        $home = getenv('COMPOSER_HOME');
        if ($home) {
            return $home;
        }

        if (Platform::isWindows()) {
            if (!getenv('APPDATA')) {
                throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
            }

            return rtrim(strtr(getenv('APPDATA'), '\\', '/'), '/') . '/Composer';
        }

        $userDir = self::getUserDir();
        if (is_dir($userDir . '/.composer')) {
            return $userDir . '/.composer';
        }

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = getenv('XDG_CONFIG_HOME') ?: $userDir . '/.config';

            return $xdgConfig . '/composer';
        }

        return $userDir . '/.composer';
    }

    /**
     * @param  string $home
     * @return string
     */
    protected static function getCacheDir($home)
    {
        $cacheDir = getenv('COMPOSER_CACHE_DIR');
        if ($cacheDir) {
            return $cacheDir;
        }

        $homeEnv = getenv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv . '/cache';
        }

        if (Platform::isWindows()) {
            if ($cacheDir = getenv('LOCALAPPDATA')) {
                $cacheDir .= '/Composer';
            } else {
                $cacheDir = $home . '/cache';
            }

            return rtrim(strtr($cacheDir, '\\', '/'), '/');
        }

        $userDir = self::getUserDir();
        if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) {
            return $home . '/cache';
        }

        if (self::useXdg()) {
            $xdgCache = getenv('XDG_CACHE_HOME') ?: $userDir . '/.cache';

            return $xdgCache . '/composer';
        }

        return $home . '/cache';
    }

    /**
     * @param  string $home
     * @return string
     */
    protected static function getDataDir($home)
    {
        $homeEnv = getenv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv;
        }

        if (Platform::isWindows()) {
            return strtr($home, '\\', '/');
        }

        $userDir = self::getUserDir();
        if ($home !== $userDir . '/.composer' && self::useXdg()) {
            $xdgData = getenv('XDG_DATA_HOME') ?: $userDir . '/.local/share';

            return $xdgData . '/composer';
        }

        return $home;
    }

    /**
     * @param  IOInterface|null $io
     * @return Config
     */
    public static function createConfig(IOInterface $io = null, $cwd = null)
    {
        $cwd = $cwd ?: getcwd();

        $config = new Config(true, $cwd);

        // determine and add main dirs to the config
        $home = self::getHomeDir();
        $config->merge(array('config' => array(
            'home' => $home,
            'cache-dir' => self::getCacheDir($home),
            'data-dir' => self::getDataDir($home),
        )));

        // load global config
        $file = new JsonFile($config->get('home').'/config.json');
        if ($file->exists()) {
            if ($io && $io->isDebug()) {
                $io->writeError('Loading config file ' . $file->getPath());
            }
            $config->merge($file->read());
        }
        $config->setConfigSource(new JsonConfigSource($file));

        $htaccessProtect = (bool) $config->get('htaccess-protect');
        if ($htaccessProtect) {
            // Protect directory against web access. Since HOME could be
            // the www-data's user home and be web-accessible it is a
            // potential security risk
            $dirs = array($config->get('home'), $config->get('cache-dir'), $config->get('data-dir'));
            foreach ($dirs as $dir) {
                if (!file_exists($dir . '/.htaccess')) {
                    if (!is_dir($dir)) {
                        Silencer::call('mkdir', $dir, 0777, true);
                    }
                    Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all');
                }
            }
        }

        // load global auth file
        $file = new JsonFile($config->get('home').'/auth.json');
        if ($file->exists()) {
            if ($io && $io->isDebug()) {
                $io->writeError('Loading config file ' . $file->getPath());
            }
            $config->merge(array('config' => $file->read()));
        }
        $config->setAuthConfigSource(new JsonConfigSource($file, true));

        // load COMPOSER_AUTH environment variable if set
        if ($composerAuthEnv = getenv('COMPOSER_AUTH')) {
            $authData = json_decode($composerAuthEnv, true);

            if (null === $authData) {
                throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object');
            }

            if ($io && $io->isDebug()) {
                $io->writeError('Loading auth config from COMPOSER_AUTH');
            }
            $config->merge(array('config' => $authData));
        }

        return $config;
    }

    public static function getComposerFile()
    {
        return trim(getenv('COMPOSER')) ?: './composer.json';
    }

    public static function createAdditionalStyles()
    {
        return array(
            'highlight' => new OutputFormatterStyle('red'),
            'warning' => new OutputFormatterStyle('black', 'yellow'),
        );
    }

    /**
     * Creates a ConsoleOutput instance
     *
     * @return ConsoleOutput
     */
    public static function createOutput()
    {
        $styles = self::createAdditionalStyles();
        $formatter = new OutputFormatter(false, $styles);

        return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
    }

    /**
     * @deprecated Use Composer\Repository\RepositoryFactory::defaultRepos instead
     */
    public static function createDefaultRepositories(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null)
    {
        return RepositoryFactory::defaultRepos($io, $config, $rm);
    }

    /**
     * Creates a Composer instance
     *
     * @param  IOInterface               $io             IO instance
     * @param  array|string|null         $localConfig    either a configuration array or a filename to read from, if null it will
     *                                                   read from the default filename
     * @param  bool                      $disablePlugins Whether plugins should not be loaded
     * @param  bool                      $fullLoad       Whether to initialize everything or only main project stuff (used when loading the global composer)
     * @throws \InvalidArgumentException
     * @throws \UnexpectedValueException
     * @return Composer
     */
    public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true)
    {
        $cwd = $cwd ?: getcwd();

        // load Composer configuration
        if (null === $localConfig) {
            $localConfig = static::getComposerFile();
        }

        if (is_string($localConfig)) {
            $composerFile = $localConfig;

            $file = new JsonFile($localConfig, null, $io);

            if (!$file->exists()) {
                if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
                    $message = 'Composer could not find a composer.json file in '.$cwd;
                } else {
                    $message = 'Composer could not find the config file: '.$localConfig;
                }
                $instructions = 'To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section';
                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
            }

            $file->validateSchema(JsonFile::LAX_SCHEMA);
            $jsonParser = new JsonParser;
            try {
                $jsonParser->parse(file_get_contents($localConfig), JsonParser::DETECT_KEY_CONFLICTS);
            } catch (DuplicateKeyException $e) {
                $details = $e->getDetails();
                $io->writeError('<warning>Key '.$details['key'].' is a duplicate in '.$localConfig.' at line '.$details['line'].'</warning>');
            }

            $localConfig = $file->read();
        }

        // Load config and override with local config/auth config
        $config = static::createConfig($io, $cwd);
        $config->merge($localConfig);
        if (isset($composerFile)) {
            $io->writeError('Loading config file ' . $composerFile, true, IOInterface::DEBUG);
            $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io)));

            $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io);
            if ($localAuthFile->exists()) {
                $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG);
                $config->merge(array('config' => $localAuthFile->read()));
                $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
            }
        }

        $vendorDir = $config->get('vendor-dir');

        // initialize composer
        $composer = new Composer();
        $composer->setConfig($config);

        if ($fullLoad) {
            // load auth configs into the IO instance
            $io->loadConfiguration($config);
        }

        $rfs = self::createRemoteFilesystem($io, $config);

        // initialize event dispatcher
        $dispatcher = new EventDispatcher($composer, $io);
        $composer->setEventDispatcher($dispatcher);

        // initialize repository manager
        $rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
        $composer->setRepositoryManager($rm);

        // load local repository
        $this->addLocalRepository($io, $rm, $vendorDir);

        // force-set the version of the global package if not defined as
        // guessing it adds no value and only takes time
        if (!$fullLoad && !isset($localConfig['version'])) {
            $localConfig['version'] = '1.0.0';
        }

        // load package
        $parser = new VersionParser;
        $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser);
        $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
        $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
        $composer->setPackage($package);

        // initialize installation manager
        $im = $this->createInstallationManager();
        $composer->setInstallationManager($im);

        if ($fullLoad) {
            // initialize download manager
            $dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
            $composer->setDownloadManager($dm);

            // initialize autoload generator
            $generator = new AutoloadGenerator($dispatcher, $io);
            $composer->setAutoloadGenerator($generator);

            // initialize archive manager
            $am = $this->createArchiveManager($config, $dm);
            $composer->setArchiveManager($am);
        }

        // add installers to the manager (must happen after download manager is created since they read it out of $composer)
        $this->createDefaultInstallers($im, $composer, $io);

        if ($fullLoad) {
            $globalComposer = null;
            if (realpath($config->get('home')) !== $cwd) {
                $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
            }

            $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins);
            $composer->setPluginManager($pm);

            $pm->loadInstalledPlugins();
        }

        // init locker if possible
        if ($fullLoad && isset($composerFile)) {
            $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
                ? substr($composerFile, 0, -4).'lock'
                : $composerFile . '.lock';

            $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
            $composer->setLocker($locker);
        }

        if ($fullLoad) {
            $initEvent = new Event(PluginEvents::INIT);
            $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent);

            // once everything is initialized we can
            // purge packages from local repos if they have been deleted on the filesystem
            if ($rm->getLocalRepository()) {
                $this->purgePackages($rm->getLocalRepository(), $im);
            }
        }

        return $composer;
    }

    /**
     * @param  IOInterface $io             IO instance
     * @param  bool        $disablePlugins Whether plugins should not be loaded
     * @return Composer
     */
    public static function createGlobal(IOInterface $io, $disablePlugins = false)
    {
        $factory = new static();

        return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, true);
    }

    /**
     * @param Repository\RepositoryManager $rm
     * @param string                       $vendorDir
     */
    protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir)
    {
        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io)));
    }

    /**
     * @param  Config        $config
     * @return Composer|null
     */
    protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, $fullLoad = false)
    {
        $composer = null;
        try {
            $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad);
        } catch (\Exception $e) {
            $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG);
        }

        return $composer;
    }

    /**
     * @param  IO\IOInterface             $io
     * @param  Config                     $config
     * @param  EventDispatcher            $eventDispatcher
     * @return Downloader\DownloadManager
     */
    public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        $cache = null;
        if ($config->get('cache-files-ttl') > 0) {
            $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
        }

        $dm = new Downloader\DownloadManager($io);
        switch ($preferred = $config->get('preferred-install')) {
            case 'dist':
                $dm->setPreferDist(true);
                break;
            case 'source':
                $dm->setPreferSource(true);
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if (is_array($preferred)) {
            $dm->setPreferences($preferred);
        }

        $executor = new ProcessExecutor($io);
        $fs = new Filesystem($executor);

        $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
        $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs));

        return $dm;
    }

    /**
     * @param  Config                     $config The configuration
     * @param  Downloader\DownloadManager $dm     Manager use to download sources
     * @return Archiver\ArchiveManager
     */
    public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
    {
        if (null === $dm) {
            $io = new IO\NullIO();
            $io->loadConfiguration($config);
            $dm = $this->createDownloadManager($io, $config);
        }

        $am = new Archiver\ArchiveManager($dm);
        $am->addArchiver(new Archiver\ZipArchiver);
        $am->addArchiver(new Archiver\PharArchiver);

        return $am;
    }

    /**
     * @param  IOInterface          $io
     * @param  Composer             $composer
     * @param  Composer             $globalComposer
     * @param  bool                 $disablePlugins
     * @return Plugin\PluginManager
     */
    protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false)
    {
        return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
    }

    /**
     * @return Installer\InstallationManager
     */
    protected function createInstallationManager()
    {
        return new Installer\InstallationManager();
    }

    /**
     * @param Installer\InstallationManager $im
     * @param Composer                      $composer
     * @param IO\IOInterface                $io
     */
    protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)
    {
        $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
        $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
        $im->addInstaller(new Installer\PluginInstaller($io, $composer));
        $im->addInstaller(new Installer\MetapackageInstaller($io));
    }

    /**
     * @param WritableRepositoryInterface   $repo repository to purge packages from
     * @param Installer\InstallationManager $im   manager to check whether packages are still installed
     */
    protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
    {
        foreach ($repo->getPackages() as $package) {
            if (!$im->isPackageInstalled($repo, $package)) {
                $repo->removePackage($package);
            }
        }
    }

    /**
     * @param  IOInterface $io             IO instance
     * @param  mixed       $config         either a configuration array or a filename to read from, if null it will read from
     *                                     the default filename
     * @param  bool        $disablePlugins Whether plugins should not be loaded
     * @return Composer
     */
    public static function create(IOInterface $io, $config = null, $disablePlugins = false)
    {
        $factory = new static();

        return $factory->createComposer($io, $config, $disablePlugins);
    }

    /**
     * @param  IOInterface      $io      IO instance
     * @param  Config           $config  Config instance
     * @param  array            $options Array of options passed directly to RemoteFilesystem constructor
     * @return RemoteFilesystem
     */
    public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
    {
        static $warned = false;
        $disableTls = false;
        // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command
        if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) {
            $warned = true;
            $disableTls = !extension_loaded('openssl');
        } elseif ($config && $config->get('disable-tls') === true) {
            if (!$warned) {
                $io->writeError('<warning>You are running Composer with SSL/TLS protection disabled.</warning>');
            }
            $warned = true;
            $disableTls = true;
        } elseif (!extension_loaded('openssl')) {
            throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
        }
        $remoteFilesystemOptions = array();
        if ($disableTls === false) {
            if ($config && $config->get('cafile')) {
                $remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
            }
            if ($config && $config->get('capath')) {
                $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
            }
            $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
        }
        try {
            $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
        } catch (TransportException $e) {
            if (false !== strpos($e->getMessage(), 'cafile')) {
                $io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
                $io->write('<error>A valid CA certificate file is required for SSL/TLS protection.</error>');
                if (PHP_VERSION_ID < 50600) {
                    $io->write('<error>It is recommended you upgrade to PHP 5.6+ which can detect your system CA file automatically.</error>');
                }
                $io->write('<error>You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.</error>');
            }
            throw $e;
        }

        return $remoteFilesystem;
    }

    /**
     * @return bool
     */
    private static function useXdg()
    {
        foreach (array_keys($_SERVER) as $key) {
            if (substr($key, 0, 4) === 'XDG_') {
                return true;
            }
        }

        return false;
    }

    /**
     * @throws \RuntimeException
     * @return string
     */
    private static function getUserDir()
    {
        $home = getenv('HOME');
        if (!$home) {
            throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly');
        }

        return rtrim(strtr($home, '\\', '/'), '/');
    }
}
<?php

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

namespace Composer\Exception;

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

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

namespace Composer;

use Composer\Config\ConfigSourceInterface;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Config
{
    const RELATIVE_PATHS = 1;

    public static $defaultConfig = array(
        'process-timeout' => 300,
        'use-include-path' => false,
        'preferred-install' => 'auto',
        'notify-on-install' => true,
        'github-protocols' => array('https', 'ssh', 'git'),
        'vendor-dir' => 'vendor',
        'bin-dir' => '{$vendor-dir}/bin',
        'cache-dir' => '{$home}/cache',
        'data-dir' => '{$home}',
        'cache-files-dir' => '{$cache-dir}/files',
        'cache-repo-dir' => '{$cache-dir}/repo',
        'cache-vcs-dir' => '{$cache-dir}/vcs',
        'cache-ttl' => 15552000, // 6 months
        'cache-files-ttl' => null, // fallback to cache-ttl
        'cache-files-maxsize' => '300MiB',
        'bin-compat' => 'auto',
        'discard-changes' => false,
        'autoloader-suffix' => null,
        'sort-packages' => false,
        'optimize-autoloader' => false,
        'classmap-authoritative' => false,
        'apcu-autoloader' => false,
        'prepend-autoloader' => true,
        'github-domains' => array('github.com'),
        'bitbucket-expose-hostname' => true,
        'disable-tls' => false,
        'secure-http' => true,
        'cafile' => null,
        'capath' => null,
        'github-expose-hostname' => true,
        'gitlab-domains' => array('gitlab.com'),
        'store-auths' => 'prompt',
        'platform' => array(),
        'archive-format' => 'tar',
        'archive-dir' => '.',
        'htaccess-protect' => true,
        'use-github-api' => true,
        'lock' => true,
        // valid keys without defaults (auth config stuff):
        // bitbucket-oauth
        // github-oauth
        // gitlab-oauth
        // gitlab-token
        // http-basic
        // bearer
    );

    public static $defaultRepositories = array(
        'packagist.org' => array(
            'type' => 'composer',
            'url' => 'https?://repo.packagist.org',
            'allow_ssl_downgrade' => true,
        ),
    );

    private $config;
    private $baseDir;
    private $repositories;
    /** @var ConfigSourceInterface */
    private $configSource;
    /** @var ConfigSourceInterface */
    private $authConfigSource;
    private $useEnvironment;
    private $warnedHosts = array();

    /**
     * @param bool   $useEnvironment Use COMPOSER_ environment variables to replace config settings
     * @param string $baseDir        Optional base directory of the config
     */
    public function __construct($useEnvironment = true, $baseDir = null)
    {
        // load defaults
        $this->config = static::$defaultConfig;
        $this->repositories = static::$defaultRepositories;
        $this->useEnvironment = (bool) $useEnvironment;
        $this->baseDir = $baseDir;
    }

    public function setConfigSource(ConfigSourceInterface $source)
    {
        $this->configSource = $source;
    }

    public function getConfigSource()
    {
        return $this->configSource;
    }

    public function setAuthConfigSource(ConfigSourceInterface $source)
    {
        $this->authConfigSource = $source;
    }

    public function getAuthConfigSource()
    {
        return $this->authConfigSource;
    }

    /**
     * Merges new config values with the existing ones (overriding)
     *
     * @param array $config
     */
    public function merge($config)
    {
        // override defaults with given config
        if (!empty($config['config']) && is_array($config['config'])) {
            foreach ($config['config'] as $key => $val) {
                if (in_array($key, array('bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer')) && isset($this->config[$key])) {
                    $this->config[$key] = array_merge($this->config[$key], $val);
                } elseif ('preferred-install' === $key && isset($this->config[$key])) {
                    if (is_array($val) || is_array($this->config[$key])) {
                        if (is_string($val)) {
                            $val = array('*' => $val);
                        }
                        if (is_string($this->config[$key])) {
                            $this->config[$key] = array('*' => $this->config[$key]);
                        }
                        $this->config[$key] = array_merge($this->config[$key], $val);
                        // the full match pattern needs to be last
                        if (isset($this->config[$key]['*'])) {
                            $wildcard = $this->config[$key]['*'];
                            unset($this->config[$key]['*']);
                            $this->config[$key]['*'] = $wildcard;
                        }
                    } else {
                        $this->config[$key] = $val;
                    }
                } else {
                    $this->config[$key] = $val;
                }
            }
        }

        if (!empty($config['repositories']) && is_array($config['repositories'])) {
            $this->repositories = array_reverse($this->repositories, true);
            $newRepos = array_reverse($config['repositories'], true);
            foreach ($newRepos as $name => $repository) {
                // disable a repository by name
                if (false === $repository) {
                    $this->disableRepoByName($name);
                    continue;
                }

                // disable a repository with an anonymous {"name": false} repo
                if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
                    $this->disableRepoByName(key($repository));
                    continue;
                }

                // store repo
                if (is_int($name)) {
                    $this->repositories[] = $repository;
                } else {
                    if ($name === 'packagist') { // BC support for default "packagist" named repo
                        $this->repositories[$name . '.org'] = $repository;
                    } else {
                        $this->repositories[$name] = $repository;
                    }
                }
            }
            $this->repositories = array_reverse($this->repositories, true);
        }
    }

    /**
     * @return array
     */
    public function getRepositories()
    {
        return $this->repositories;
    }

    /**
     * Returns a setting
     *
     * @param  string            $key
     * @param  int               $flags Options (see class constants)
     * @throws \RuntimeException
     * @return mixed
     */
    public function get($key, $flags = 0)
    {
        switch ($key) {
            case 'vendor-dir':
            case 'bin-dir':
            case 'process-timeout':
            case 'data-dir':
            case 'cache-dir':
            case 'cache-files-dir':
            case 'cache-repo-dir':
            case 'cache-vcs-dir':
            case 'cafile':
            case 'capath':
                // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

                $val = $this->getComposerEnv($env);
                $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\');
                $val = Platform::expandPath($val);

                if (substr($key, -4) !== '-dir') {
                    return $val;
                }

                return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);

            case 'htaccess-protect':
                $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT');
                if (false === $value) {
                    $value = $this->config[$key];
                }
                return $value !== 'false' && (bool) $value;

            case 'cache-ttl':
                return (int) $this->config[$key];

            case 'cache-files-maxsize':
                if (!preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $this->config[$key], $matches)) {
                    throw new \RuntimeException(
                        "Could not parse the value of 'cache-files-maxsize': {$this->config[$key]}"
                    );
                }
                $size = $matches[1];
                if (isset($matches[2])) {
                    switch (strtolower($matches[2])) {
                        case 'g':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'm':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'k':
                            $size *= 1024;
                            break;
                    }
                }

                return $size;

            case 'cache-files-ttl':
                if (isset($this->config[$key])) {
                    return (int) $this->config[$key];
                }

                return (int) $this->config['cache-ttl'];

            case 'home':
                $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $this->config[$key]);

                return rtrim($this->process($val, $flags), '/\\');

            case 'bin-compat':
                $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key];

                if (!in_array($value, array('auto', 'full'))) {
                    throw new \RuntimeException(
                        "Invalid value for 'bin-compat': {$value}. Expected auto, full"
                    );
                }

                return $value;

            case 'discard-changes':
                if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
                    if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"
                        );
                    }
                    if ('stash' === $env) {
                        return 'stash';
                    }

                    // convert string value to bool
                    return $env !== 'false' && (bool) $env;
                }

                if (!in_array($this->config[$key], array(true, false, 'stash'), true)) {
                    throw new \RuntimeException(
                        "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"
                    );
                }

                return $this->config[$key];

            case 'github-protocols':
                $protos = $this->config['github-protocols'];
                if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) {
                    unset($protos[$index]);
                }
                if (reset($protos) === 'http') {
                    throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"');
                }

                return $protos;

            case 'disable-tls':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            case 'secure-http':
                if ($this->get('disable-tls') === true) {
                    return false;
                }

                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            case 'use-github-api':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            case 'lock':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            default:
                if (!isset($this->config[$key])) {
                    return null;
                }

                return $this->process($this->config[$key], $flags);
        }
    }

    public function all($flags = 0)
    {
        $all = array(
            'repositories' => $this->getRepositories(),
        );
        foreach (array_keys($this->config) as $key) {
            $all['config'][$key] = $this->get($key, $flags);
        }

        return $all;
    }

    public function raw()
    {
        return array(
            'repositories' => $this->getRepositories(),
            'config' => $this->config,
        );
    }

    /**
     * Checks whether a setting exists
     *
     * @param  string $key
     * @return bool
     */
    public function has($key)
    {
        return array_key_exists($key, $this->config);
    }

    /**
     * Replaces {$refs} inside a config string
     *
     * @param  string|int|null $value a config string that can contain {$refs-to-other-config}
     * @param  int             $flags Options (see class constants)
     * @return string|int|null
     */
    private function process($value, $flags)
    {
        $config = $this;

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

        return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) {
            return $config->get($match[1], $flags);
        }, $value);
    }

    /**
     * Turns relative paths in absolute paths without realpath()
     *
     * Since the dirs might not exist yet we can not call realpath or it will fail.
     *
     * @param  string $path
     * @return string
     */
    private function realpath($path)
    {
        if (preg_match('{^(?:/|[a-z]:|[a-z0-9.]+://)}i', $path)) {
            return $path;
        }

        return $this->baseDir . '/' . $path;
    }

    /**
     * Reads the value of a Composer environment variable
     *
     * This should be used to read COMPOSER_ environment variables
     * that overload config values.
     *
     * @param  string      $var
     * @return string|bool
     */
    private function getComposerEnv($var)
    {
        if ($this->useEnvironment) {
            return getenv($var);
        }

        return false;
    }

    private function disableRepoByName($name)
    {
        if (isset($this->repositories[$name])) {
            unset($this->repositories[$name]);
        } elseif ($name === 'packagist') { // BC support for default "packagist" named repo
            unset($this->repositories['packagist.org']);
        }
    }

    /**
     * Validates that the passed URL is allowed to be used by current config, or throws an exception.
     *
     * @param string      $url
     * @param IOInterface $io
     */
    public function prohibitUrlByConfig($url, IOInterface $io = null)
    {
        // Return right away if the URL is malformed or custom (see issue #5173)
        if (false === filter_var($url, FILTER_VALIDATE_URL)) {
            return;
        }

        // Extract scheme and throw exception on known insecure protocols
        $scheme = parse_url($url, PHP_URL_SCHEME);
        if (in_array($scheme, array('http', 'git', 'ftp', 'svn'))) {
            if ($this->get('secure-http')) {
                throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details.");
            } elseif ($io) {
                $host = parse_url($url, PHP_URL_HOST);
                if (!isset($this->warnedHosts[$host])) {
                    $io->writeError("<warning>Warning: Accessing $host over $scheme which is an insecure protocol.</warning>");
                }
                $this->warnedHosts[$host] = true;
            }
        }
    }

    /**
     * Used by long-running custom scripts in composer.json
     *
     * "scripts": {
     *   "watch": [
     *     "Composer\\Config::disableProcessTimeout",
     *     "vendor/bin/long-running-script --watch"
     *   ]
     * }
     */
    public static function disableProcessTimeout()
    {
        // Override global timeout set earlier by environment or config
        ProcessExecutor::setTimeout(0);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Cache;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
{
    private $hasStashedChanges = false;
    private $hasDiscardedChanges = false;
    private $gitUtil;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
    {
        parent::__construct($io, $config, $process, $fs);
        $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
    }

    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        $cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/';
        $ref = $package->getSourceReference();
        $flag = Platform::isWindows() ? '/D ' : '';

        // --dissociate option is only available since git 2.3.0-rc0
        $gitVersion = GitUtil::getVersion($this->process);
        $msg = "Cloning ".$this->getShortHash($ref);

        $command = 'git clone --no-checkout -- %url% %path% && cd '.$flag.'%path% && git remote add composer -- %url% && git fetch composer && git remote set-url origin -- %sanitizedUrl% && git remote set-url composer -- %sanitizedUrl%';
        if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('    Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
            try {
                if (!$this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref)) {
                    $this->io->writeError('<error>Failed to update '.$url.' in cache, package installation for '.$package->getPrettyName().' might fail.</error>');
                }
                if (is_dir($cachePath)) {
                    $command =
                        'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
                        . '&& cd '.$flag.'%path% '
                        . '&& git remote set-url origin -- %sanitizedUrl% && git remote add composer -- %sanitizedUrl%';
                    $msg = "Cloning ".$this->getShortHash($ref).' from cache';
                }
            } catch (\RuntimeException $e) {
                if (0 === strpos(get_class($e), 'PHPUnit')) {
                    throw $e;
                }
            }
        }
        $this->io->writeError($msg);

        $commandCallable = function ($url) use ($path, $command, $cachePath) {
            return str_replace(
                array('%url%', '%path%', '%cachePath%', '%sanitizedUrl%'),
                array(
                    ProcessExecutor::escape($url),
                    ProcessExecutor::escape($path),
                    ProcessExecutor::escape($cachePath),
                    ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
                ),
                $command
            );
        };

        $this->gitUtil->runCommand($commandCallable, $url, $path, true);
        if ($url !== $package->getSourceUrl()) {
            $this->updateOriginUrl($path, $package->getSourceUrl());
        } else {
            $this->setPushUrl($path, $url);
        }

        if ($newRef = $this->updateToCommit($path, $ref, $package->getPrettyVersion(), $package->getReleaseDate())) {
            if ($package->getDistReference() === $package->getSourceReference()) {
                $package->setDistReference($newRef);
            }
            $package->setSourceReference($newRef);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        GitUtil::cleanEnv();
        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $updateOriginUrl = false;
        if (
            0 === $this->process->execute('git remote -v', $output, $path)
            && preg_match('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
            && preg_match('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
        ) {
            if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) {
                $updateOriginUrl = true;
            }
        }

        $ref = $target->getSourceReference();
        $this->io->writeError(" Checking out ".$this->getShortHash($ref));
        $command = '(git remote set-url composer -- %s && git rev-parse --quiet --verify %s || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %s';

        $commandCallable = function ($url) use ($command, $ref) {
            return sprintf(
                $command,
                ProcessExecutor::escape($url),
                ProcessExecutor::escape($ref.'^{commit}'),
                ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url))
            );
        };

        $this->gitUtil->runCommand($commandCallable, $url, $path);
        if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
            if ($target->getDistReference() === $target->getSourceReference()) {
                $target->setDistReference($newRef);
            }
            $target->setSourceReference($newRef);
        }

        if ($updateOriginUrl) {
            $this->updateOriginUrl($path, $target->getSourceUrl());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        GitUtil::cleanEnv();
        if (!$this->hasMetadataRepository($path)) {
            return;
        }

        $command = 'git status --porcelain --untracked-files=no';
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        return trim($output) ?: null;
    }

    public function getUnpushedChanges(PackageInterface $package, $path)
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            return;
        }

        $command = 'git show-ref --head -d';
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        $refs = trim($output);
        if (!preg_match('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) {
            // could not match the HEAD for some reason
            return;
        }

        $headRef = $match[1];
        if (!preg_match_all('{^'.$headRef.' refs/heads/(.+)$}mi', $refs, $matches)) {
            // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this
            return;
        }

        // use the first match as branch name for now
        $branch = $matches[1][0];
        $unpushedChanges = null;

        // do two passes, as if we find anything we want to fetch and then re-try
        for ($i = 0; $i <= 1; $i++) {
            // try to find the a matching branch name in the composer remote
            foreach ($matches[1] as $candidate) {
                if (preg_match('{^[a-f0-9]+ refs/remotes/((?:composer|origin)/'.preg_quote($candidate).')$}mi', $refs, $match)) {
                    $branch = $candidate;
                    $remoteBranch = $match[1];
                    break;
                }
            }

            // if it doesn't exist, then we assume it is an unpushed branch
            // this is bad as we have no reference point to do a diff so we just bail listing
            // the branch as being unpushed
            if (!isset($remoteBranch)) {
                $unpushedChanges = 'Branch ' . $branch . ' could not be found on the origin remote and appears to be unpushed';
            } else {
                $command = sprintf('git diff --name-status %s...%s --', $remoteBranch, $branch);
                if (0 !== $this->process->execute($command, $output, $path)) {
                    throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
                }

                $unpushedChanges = trim($output) ?: null;
            }

            // first pass and we found unpushed changes, fetch from both remotes to make sure we have up to date
            // remotes and then try again as outdated remotes can sometimes cause false-positives
            if ($unpushedChanges && $i === 0) {
                $this->process->execute('git fetch composer && git fetch origin', $output, $path);
            }

            // abort after first pass if we didn't find anything
            if (!$unpushedChanges) {
                break;
            }
        }

        return $unpushedChanges;
    }

    /**
     * {@inheritDoc}
     */
    protected function cleanChanges(PackageInterface $package, $path, $update)
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);

        $unpushed = $this->getUnpushedChanges($package, $path);
        if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) {
            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed);
        }

        if (!$changes = $this->getLocalChanges($package, $path)) {
            return;
        }

        if (!$this->io->isInteractive()) {
            $discardChanges = $this->config->get('discard-changes');
            if (true === $discardChanges) {
                return $this->discardChanges($path);
            }
            if ('stash' === $discardChanges) {
                if (!$update) {
                    return parent::cleanChanges($package, $path, $update);
                }

                return $this->stashChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(function ($elem) {
            return '    '.$elem;
        }, preg_split('{\s*\r?\n\s*}', $changes));
        $this->io->writeError('    <error>The package has modified files:</error>');
        $this->io->writeError(array_slice($changes, 0, 10));
        if (count($changes) > 10) {
            $this->io->writeError('    <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 's':
                    if (!$update) {
                        goto help;
                    }

                    $this->stashChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case 'd':
                    $this->viewDiff($path);
                    break;

                case '?':
                default:
                    help:
                    $this->io->writeError(array(
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    d - view local modifications (diff)',
                    ));
                    if ($update) {
                        $this->io->writeError('    s - stash changes and try to reapply them after the update');
                    }
                    $this->io->writeError('    ? - print help');
                    break;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function reapplyChanges($path)
    {
        $path = $this->normalizePath($path);
        if ($this->hasStashedChanges) {
            $this->hasStashedChanges = false;
            $this->io->writeError('    <info>Re-applying stashed changes</info>');
            if (0 !== $this->process->execute('git stash pop', $output, $path)) {
                throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
            }
        }

        $this->hasDiscardedChanges = false;
    }

    /**
     * Updates the given path to the given commit ref
     *
     * @param  string            $path
     * @param  string            $reference
     * @param  string            $branch
     * @param  \DateTime         $date
     * @throws \RuntimeException
     * @return null|string       if a string is returned, it is the commit reference that was checked out if the original could not be found
     */
    protected function updateToCommit($path, $reference, $branch, $date)
    {
        $force = $this->hasDiscardedChanges || $this->hasStashedChanges ? '-f ' : '';

        // This uses the "--" sequence to separate branch from file parameters.
        //
        // Otherwise git tries the branch name as well as file name.
        // If the non-existent branch is actually the name of a file, the file
        // is checked out.
        $template = 'git checkout '.$force.'%s -- && git reset --hard %1$s --';
        $branch = preg_replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $branch);

        $branches = null;
        if (0 === $this->process->execute('git branch -r', $output, $path)) {
            $branches = $output;
        }

        // check whether non-commitish are branches or tags, and fetch branches with the remote name
        $gitRef = $reference;
        if (!preg_match('{^[a-f0-9]{40}$}', $reference)
            && $branches
            && preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
        ) {
            $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
            if (0 === $this->process->execute($command, $output, $path)) {
                return;
            }
        }

        // try to checkout branch by name and then reset it so it's on the proper branch name
        if (preg_match('{^[a-f0-9]{40}$}', $reference)) {
            // add 'v' in front of the branch if it was stripped when generating the pretty name
            if (!preg_match('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && preg_match('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) {
                $branch = 'v' . $branch;
            }

            $command = sprintf('git checkout %s --', ProcessExecutor::escape($branch));
            $fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
            if (0 === $this->process->execute($command, $output, $path)
                || 0 === $this->process->execute($fallbackCommand, $output, $path)
            ) {
                $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
                if (0 === $this->process->execute($command, $output, $path)) {
                    return;
                }
            }
        }

        $command = sprintf($template, ProcessExecutor::escape($gitRef));
        if (0 === $this->process->execute($command, $output, $path)) {
            return;
        }

        // reference was not found (prints "fatal: reference is not a tree: $ref")
        if (false !== strpos($this->process->getErrorOutput(), $reference)) {
            $this->io->writeError('    <warning>'.$reference.' is gone (history was rewritten?)</warning>');
        }

        throw new \RuntimeException(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
    }

    protected function updateOriginUrl($path, $url)
    {
        $this->process->execute(sprintf('git remote set-url origin -- %s', ProcessExecutor::escape($url)), $output, $path);
        $this->setPushUrl($path, $url);
    }

    protected function setPushUrl($path, $url)
    {
        // set push url for github projects
        if (preg_match('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
            $protocols = $this->config->get('github-protocols');
            $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
            if (!in_array('ssh', $protocols, true)) {
                $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
            }
            $cmd = sprintf('git remote set-url --push origin -- %s', ProcessExecutor::escape($pushUrl));
            $this->process->execute($cmd, $ignoredOutput, $path);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        $path = $this->normalizePath($path);
        $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"'.GitUtil::getNoShowSignatureFlag($this->process), ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));

        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function discardChanges($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git clean -df && git reset --hard', $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }

        $this->hasDiscardedChanges = true;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function stashChanges($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) {
            throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput());
        }

        $this->hasStashedChanges = true;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function viewDiff($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git diff HEAD', $output, $path)) {
            throw new \RuntimeException("Could not view diff\n\n:".$this->process->getErrorOutput());
        }

        $this->io->writeError($output);
    }

    protected function normalizePath($path)
    {
        if (Platform::isWindows() && strlen($path) > 0) {
            $basePath = $path;
            $removed = array();

            while (!is_dir($basePath) && $basePath !== '\\') {
                array_unshift($removed, basename($basePath));
                $basePath = dirname($basePath);
            }

            if ($basePath === '\\') {
                return $path;
            }

            $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/');
        }

        return $path;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        $path = $this->normalizePath($path);

        return is_dir($path.'/.git');
    }

    protected function getShortHash($reference)
    {
        if (!$this->io->isVerbose() && preg_match('{^[0-9a-f]{40}$}', $reference)) {
            return substr($reference, 0, 10);
        }

        return $reference;
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\Archiver\ArchivableFilesFinder;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem as ComposerFilesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;

/**
 * Download a package from a local path.
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface
{
    const STRATEGY_SYMLINK = 10;
    const STRATEGY_MIRROR = 20;

    /**
     * {@inheritdoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        $url = $package->getDistUrl();
        $realUrl = realpath($url);
        if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) {
            throw new \RuntimeException(sprintf(
                'Source path "%s" is not found for package %s',
                $url,
                $package->getName()
            ));
        }

        if (realpath($path) === $realUrl) {
            if ($output) {
                $this->io->writeError(sprintf(
                    '  - Installing <info>%s</info> (<comment>%s</comment>): Source already present',
                    $package->getName(),
                    $package->getFullPrettyVersion()
                ));
            }

            return;
        }

        if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) {
            // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours.
            //
            // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174
            // for previous attempts that were shut down because they did not work well enough or introduced too many risks.
            throw new \RuntimeException(sprintf(
                'Package %s cannot install to "%s" inside its source at "%s"',
                $package->getName(),
                realpath($path),
                $realUrl
            ));
        }

        // Get the transport options with default values
        $transportOptions = $package->getTransportOptions() + array('symlink' => null, 'relative' => true);

        // When symlink transport option is null, both symlink and mirror are allowed
        $currentStrategy = self::STRATEGY_SYMLINK;
        $allowedStrategies = array(self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR);

        $mirrorPathRepos = getenv('COMPOSER_MIRROR_PATH_REPOS');
        if ($mirrorPathRepos) {
            $currentStrategy = self::STRATEGY_MIRROR;
        }

        if (true === $transportOptions['symlink']) {
            $currentStrategy = self::STRATEGY_SYMLINK;
            $allowedStrategies = array(self::STRATEGY_SYMLINK);
        } elseif (false === $transportOptions['symlink']) {
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = array(self::STRATEGY_MIRROR);
        }

        // Check we can use junctions safely if we are on Windows
        if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) {
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = array(self::STRATEGY_MIRROR);
        }

        $fileSystem = new Filesystem();
        $this->filesystem->removeDirectory($path);

        if ($output) {
            $this->io->writeError(sprintf(
                '  - Installing <info>%s</info> (<comment>%s</comment>): ',
                $package->getName(),
                $package->getFullPrettyVersion()
            ), false);
        }

        $isFallback = false;
        if (self::STRATEGY_SYMLINK == $currentStrategy) {
            try {
                if (Platform::isWindows()) {
                    // Implement symlinks as NTFS junctions on Windows
                    $this->io->writeError(sprintf('Junctioning from %s', $url), false);
                    $this->filesystem->junction($realUrl, $path);
                } else {
                    $absolutePath = $path;
                    if (!$this->filesystem->isAbsolutePath($absolutePath)) {
                        $absolutePath = getcwd() . DIRECTORY_SEPARATOR . $path;
                    }
                    $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl);
                    $path = rtrim($path, "/");
                    $this->io->writeError(sprintf('Symlinking from %s', $url), false);
                    if ($transportOptions['relative']) {
                        $fileSystem->symlink($shortestPath, $path);
                    } else {
                        $fileSystem->symlink($realUrl, $path);
                    }
                }
            } catch (IOException $e) {
                if (in_array(self::STRATEGY_MIRROR, $allowedStrategies)) {
                    $this->io->writeError('');
                    $this->io->writeError('    <error>Symlink failed, fallback to use mirroring!</error>');
                    $currentStrategy = self::STRATEGY_MIRROR;
                    $isFallback = true;
                } else {
                    throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path));
                }
            }
        }

        // Fallback if symlink failed or if symlink is not allowed for the package
        if (self::STRATEGY_MIRROR == $currentStrategy) {
            $fs = new ComposerFilesystem();
            $realUrl = $fs->normalizePath($realUrl);

            $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? '    ' : '', $url), false);
            $iterator = new ArchivableFilesFinder($realUrl, array());
            $fileSystem->mirror($realUrl, $path, $iterator);
        }

        $this->io->writeError('');
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path, $output = true)
    {
        $realUrl = realpath($package->getDistUrl());

        if (realpath($path) === $realUrl) {
            if ($output) {
                $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>), source is still present in $path");
            }

            return;
        }

        /**
         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which
         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.
         */
        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {
            if ($output) {
                $this->io->writeError("  - Removing junction for <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
            }
            if (!$this->filesystem->removeJunction($path)) {
                $this->io->writeError("    <warning>Could not remove junction at " . $path . " - is another process locking it?</warning>");
                throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName());
            }
        } else {
            parent::remove($package, $path, $output);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getVcsReference(PackageInterface $package, $path)
    {
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, new ProcessExecutor($this->io), $parser);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) {
            return $packageVersion['commit'];
        }
    }

    /**
     * Returns true if junctions can be created and safely used on Windows
     *
     * A PHP bug makes junction detection fragile, leading to possible data loss
     * when removing a package. See https://bugs.php.net/bug.php?id=77552
     *
     * For safety we require a minimum version of Windows 7, so we can call the
     * system rmdir which will preserve target content if given a junction.
     *
     * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
     *
     * @return bool
     */
    private function safeJunctions()
    {
        // We need to call mklink, and rmdir on Windows 7 (version 6.1)
        return function_exists('proc_open') &&
            (PHP_WINDOWS_VERSION_MAJOR > 6 ||
            (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1));
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use RarArchive;

/**
 * RAR archive downloader.
 *
 * Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
 *
 * @author Derrick Nelson <drrcknlsn@gmail.com>
 */
class RarDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($file, $path)
    {
        $processError = null;

        // Try to use unrar on *nix
        if (!Platform::isWindows()) {
            $command = 'unrar x -- ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path);

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return;
            }

            $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
        }

        if (!class_exists('RarArchive')) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();

            $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
                . $iniMessage . "\n" . $processError;

            if (!Platform::isWindows()) {
                $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
            }

            throw new \RuntimeException($error);
        }

        $rarArchive = RarArchive::open($file);

        if (false === $rarArchive) {
            throw new \UnexpectedValueException('Could not open RAR archive: ' . $file);
        }

        $entries = $rarArchive->getEntries();

        if (false === $entries) {
            throw new \RuntimeException('Could not retrieve RAR archive entries');
        }

        foreach ($entries as $entry) {
            if (false === $entry->extract($path)) {
                throw new \RuntimeException('Could not extract entry');
            }
        }

        $rarArchive->close();
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->filesystem = $fs ?: new Filesystem($this->process);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallationSource()
    {
        return 'source';
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path)
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
        $this->filesystem->emptyDirectory($path);

        $urls = $package->getSourceUrls();
        while ($url = array_shift($urls)) {
            try {
                if (Filesystem::isLocalPath($url)) {
                    // realpath() below will not understand
                    // url that starts with "file://"
                    $needle = 'file://';
                    $isFileProtocol = false;
                    if (0 === strpos($url, $needle)) {
                        $url = substr($url, strlen($needle));
                        $isFileProtocol = true;
                    }

                    // realpath() below will not understand %20 spaces etc.
                    if (false !== strpos($url, '%')) {
                        $url = rawurldecode($url);
                    }

                    $url = realpath($url);

                    if ($isFileProtocol) {
                        $url = $needle . $url;
                    }
                }
                $this->doDownload($package, $path, $url);
                break;
            } catch (\Exception $e) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($e instanceof \PHPUnit_Framework_Exception) {
                    throw $e;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
                if (!count($urls)) {
                    throw $e;
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path)
    {
        if (!$target->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
        }

        $name = $target->getName();
        if ($initial->getPrettyVersion() == $target->getPrettyVersion()) {
            if ($target->getSourceType() === 'svn') {
                $from = $initial->getSourceReference();
                $to = $target->getSourceReference();
            } else {
                $from = substr($initial->getSourceReference(), 0, 7);
                $to = substr($target->getSourceReference(), 0, 7);
            }
            $name .= ' '.$initial->getPrettyVersion();
        } else {
            $from = $initial->getFullPrettyVersion();
            $to = $target->getFullPrettyVersion();
        }

        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

        $this->cleanChanges($initial, $path, true);
        $urls = $target->getSourceUrls();

        $exception = null;
        while ($url = array_shift($urls)) {
            try {
                if (Filesystem::isLocalPath($url)) {
                    $url = realpath($url);
                }
                $this->doUpdate($initial, $target, $path, $url);

                $exception = null;
                break;
            } catch (\Exception $exception) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($exception instanceof \PHPUnit_Framework_Exception) {
                    throw $exception;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
            }
        }

        $this->reapplyChanges($path);

        // print the commit logs if in verbose mode and VCS metadata is present
        // because in case of missing metadata code would trigger another exception
        if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
            $message = 'Pulling in changes:';
            $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);

            if (!trim($logs)) {
                $message = 'Rolling back changes:';
                $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path);
            }

            if (trim($logs)) {
                $logs = implode("\n", array_map(function ($line) {
                    return '      ' . $line;
                }, explode("\n", $logs)));

                // escape angle brackets for proper output in the console
                $logs = str_replace('<', '\<', $logs);

                $this->io->writeError('    '.$message);
                $this->io->writeError($logs);
            }
        }

        if (!$urls && $exception) {
            throw $exception;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path)
    {
        $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
        $this->cleanChanges($package, $path, false);
        if (!$this->filesystem->removeDirectory($path)) {
            throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
        }
    }

    /**
     * Download progress information is not available for all VCS downloaders.
     * {@inheritDoc}
     */
    public function setOutputProgress($outputProgress)
    {
        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function getVcsReference(PackageInterface $package, $path)
    {
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, $this->process, $parser);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) {
            return $packageVersion['commit'];
        }
    }

    /**
     * Prompt the user to check if changes should be stashed/removed or the operation aborted
     *
     * @param  PackageInterface  $package
     * @param  string            $path
     * @param  bool              $update  if true (update) the changes can be stashed and reapplied after an update,
     *                                    if false (remove) the changes should be assumed to be lost if the operation is not aborted
     * @throws \RuntimeException in case the operation must be aborted
     */
    protected function cleanChanges(PackageInterface $package, $path, $update)
    {
        // the default implementation just fails if there are any changes, override in child classes to provide stash-ability
        if (null !== $this->getLocalChanges($package, $path)) {
            throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.');
        }
    }

    /**
     * Guarantee that no changes have been made to the local copy
     *
     * @param  string            $path
     * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
     */
    protected function reapplyChanges($path)
    {
    }

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     * @param string           $url     package url
     */
    abstract protected function doDownload(PackageInterface $package, $path, $url);

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  updated package
     * @param string           $path    download path
     * @param string           $url     package url
     */
    abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);

    /**
     * Fetches the commit logs between two commits
     *
     * @param  string $fromReference the source reference
     * @param  string $toReference   the target reference
     * @param  string $path          the package path
     * @return string
     */
    abstract protected function getCommitLogs($fromReference, $toReference, $path);

    /**
     * Checks if VCS metadata repository has been initialized
     * repository example: .git|.svn|.hg
     *
     * @param  string $path
     * @return bool
     */
    abstract protected function hasMetadataRepository($path);
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * VCS Capable Downloader interface.
 *
 * @author Steve Buzonas <steve@fancyguy.com>
 */
interface VcsCapableDownloaderInterface
{
    /**
     * Gets the VCS Reference for the package at path
     *
     * @param  PackageInterface $package package directory
     * @param  string           $path    package directory
     * @return string|null      reference or null
     */
    public function getVcsReference(PackageInterface $package, $path);
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;

/**
 * Xz archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 * @author Pierre Rudloff <contact@rudloff.pro>
 */
class XzDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);

        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($file, $path)
    {
        $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);

        if (0 === $this->process->execute($command, $ignoredOutput)) {
            return;
        }

        $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();

        throw new \RuntimeException($processError);
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * Downloader interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface DownloaderInterface
{
    /**
     * Returns installation source (either source or dist).
     *
     * @return string "source" or "dist"
     */
    public function getInstallationSource();

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     */
    public function download(PackageInterface $package, $path);

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  updated package
     * @param string           $path    download path
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path);

    /**
     * Removes specific package from specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     */
    public function remove(PackageInterface $package, $path);

    /**
     * Sets whether to output download progress information or not
     *
     * @param  bool                $outputProgress
     * @return DownloaderInterface
     */
    public function setOutputProgress($outputProgress);
}
<?php

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

namespace Composer\Downloader;

/**
 * Exception thrown when issues exist on local filesystem
 *
 * @author Javier Spagnoletti <jspagnoletti@javierspagnoletti.com.ar>
 */
class FilesystemException extends \Exception
{
    public function __construct($message = '', $code = 0, \Exception $previous = null)
    {
        parent::__construct("Filesystem exception: \n".$message, $code, $previous);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;

/**
 * Downloaders manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class DownloadManager
{
    private $io;
    private $preferDist = false;
    private $preferSource = false;
    private $packagePreferences = array();
    private $filesystem;
    private $downloaders = array();

    /**
     * Initializes download manager.
     *
     * @param IOInterface     $io           The Input Output Interface
     * @param bool            $preferSource prefer downloading from source
     * @param Filesystem|null $filesystem   custom Filesystem object
     */
    public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->preferSource = $preferSource;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    /**
     * Makes downloader prefer source installation over the dist.
     *
     * @param  bool            $preferSource prefer downloading from source
     * @return DownloadManager
     */
    public function setPreferSource($preferSource)
    {
        $this->preferSource = $preferSource;

        return $this;
    }

    /**
     * Makes downloader prefer dist installation over the source.
     *
     * @param  bool            $preferDist prefer downloading from dist
     * @return DownloadManager
     */
    public function setPreferDist($preferDist)
    {
        $this->preferDist = $preferDist;

        return $this;
    }

    /**
     * Sets fine tuned preference settings for package level source/dist selection.
     *
     * @param  array           $preferences array of preferences by package patterns
     * @return DownloadManager
     */
    public function setPreferences(array $preferences)
    {
        $this->packagePreferences = $preferences;

        return $this;
    }

    /**
     * Sets whether to output download progress information for all registered
     * downloaders
     *
     * @param  bool            $outputProgress
     * @return DownloadManager
     */
    public function setOutputProgress($outputProgress)
    {
        foreach ($this->downloaders as $downloader) {
            $downloader->setOutputProgress($outputProgress);
        }

        return $this;
    }

    /**
     * Sets installer downloader for a specific installation type.
     *
     * @param  string              $type       installation type
     * @param  DownloaderInterface $downloader downloader instance
     * @return DownloadManager
     */
    public function setDownloader($type, DownloaderInterface $downloader)
    {
        $type = strtolower($type);
        $this->downloaders[$type] = $downloader;

        return $this;
    }

    /**
     * Returns downloader for a specific installation type.
     *
     * @param  string                    $type installation type
     * @throws \InvalidArgumentException if downloader for provided type is not registered
     * @return DownloaderInterface
     */
    public function getDownloader($type)
    {
        $type = strtolower($type);
        if (!isset($this->downloaders[$type])) {
            throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
        }

        return $this->downloaders[$type];
    }

    /**
     * Returns downloader for already installed package.
     *
     * @param  PackageInterface          $package package instance
     * @throws \InvalidArgumentException if package has no installation source specified
     * @throws \LogicException           if specific downloader used to load package with
     *                                           wrong type
     * @return DownloaderInterface|null
     */
    public function getDownloaderForInstalledPackage(PackageInterface $package)
    {
        $installationSource = $package->getInstallationSource();

        if ('metapackage' === $package->getType()) {
            return;
        }

        if ('dist' === $installationSource) {
            $downloader = $this->getDownloader($package->getDistType());
        } elseif ('source' === $installationSource) {
            $downloader = $this->getDownloader($package->getSourceType());
        } else {
            throw new \InvalidArgumentException(
                'Package '.$package.' seems not been installed properly'
            );
        }

        if ($installationSource !== $downloader->getInstallationSource()) {
            throw new \LogicException(sprintf(
                'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s',
                get_class($downloader),
                $downloader->getInstallationSource(),
                $installationSource,
                $package
            ));
        }

        return $downloader;
    }

    /**
     * Downloads package into target dir.
     *
     * @param PackageInterface $package      package instance
     * @param string           $targetDir    target dir
     * @param bool             $preferSource prefer installation from source
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function download(PackageInterface $package, $targetDir, $preferSource = null)
    {
        $preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
        $sourceType = $package->getSourceType();
        $distType = $package->getDistType();

        $sources = array();
        if ($sourceType) {
            $sources[] = 'source';
        }
        if ($distType) {
            $sources[] = 'dist';
        }

        if (empty($sources)) {
            throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
        }

        if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
            $sources = array_reverse($sources);
        }

        $this->filesystem->ensureDirectoryExists($targetDir);

        foreach ($sources as $i => $source) {
            if (isset($e)) {
                $this->io->writeError('    <warning>Now trying to download from ' . $source . '</warning>');
            }
            $package->setInstallationSource($source);
            try {
                $downloader = $this->getDownloaderForInstalledPackage($package);
                if ($downloader) {
                    $downloader->download($package, $targetDir);
                }
                break;
            } catch (\RuntimeException $e) {
                if ($i === count($sources) - 1) {
                    throw $e;
                }

                $this->io->writeError(
                    '    <warning>Failed to download '.
                    $package->getPrettyName().
                    ' from ' . $source . ': '.
                    $e->getMessage().'</warning>'
                );
            }
        }
    }

    /**
     * Updates package from initial to target version.
     *
     * @param PackageInterface $initial   initial package version
     * @param PackageInterface $target    target package version
     * @param string           $targetDir target dir
     *
     * @throws \InvalidArgumentException if initial package is not installed
     */
    public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
    {
        $downloader = $this->getDownloaderForInstalledPackage($initial);
        if (!$downloader) {
            return;
        }

        $installationSource = $initial->getInstallationSource();

        if ('dist' === $installationSource) {
            $initialType = $initial->getDistType();
            $targetType = $target->getDistType();
        } else {
            $initialType = $initial->getSourceType();
            $targetType = $target->getSourceType();
        }

        // upgrading from a dist stable package to a dev package, force source reinstall
        if ($target->isDev() && 'dist' === $installationSource) {
            $downloader->remove($initial, $targetDir);
            $this->download($target, $targetDir);

            return;
        }

        if ($initialType === $targetType) {
            $target->setInstallationSource($installationSource);
            try {
                $downloader->update($initial, $target, $targetDir);

                return;
            } catch (\RuntimeException $e) {
                if (!$this->io->isInteractive()) {
                    throw $e;
                }
                $this->io->writeError('<error>    Update failed ('.$e->getMessage().')</error>');
                if (!$this->io->askConfirmation('    Would you like to try reinstalling the package instead [<comment>yes</comment>]? ', true)) {
                    throw $e;
                }
            }
        }

        $downloader->remove($initial, $targetDir);
        $this->download($target, $targetDir, 'source' === $installationSource);
    }

    /**
     * Removes package from target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     */
    public function remove(PackageInterface $package, $targetDir)
    {
        $downloader = $this->getDownloaderForInstalledPackage($package);
        if ($downloader) {
            $downloader->remove($package, $targetDir);
        }
    }

    /**
     * Determines the install preference of a package
     *
     * @param PackageInterface $package package instance
     *
     * @return string
     */
    protected function resolvePackageInstallPreference(PackageInterface $package)
    {
        foreach ($this->packagePreferences as $pattern => $preference) {
            $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i';
            if (preg_match($pattern, $package->getName())) {
                if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) {
                    return 'dist';
                }

                return 'source';
            }
        }

        return $package->isDev() ? 'source' : 'dist';
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDownloader extends VcsDownloader
{
    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $url = ProcessExecutor::escape($url);
        $ref = ProcessExecutor::escape($package->getSourceReference());
        $repoFile = $path . '.fossil';
        $this->io->writeError("Cloning ".$package->getSourceReference());
        $command = sprintf('fossil clone -- %s %s', $url, ProcessExecutor::escape($repoFile));
        if (0 !== $this->process->execute($command, $ignoredOutput)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
        $command = sprintf('fossil open --nested -- %s', ProcessExecutor::escape($repoFile));
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
        $command = sprintf('fossil update -- %s', $ref);
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $url = ProcessExecutor::escape($url);
        $ref = ProcessExecutor::escape($target->getSourceReference());
        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $command = sprintf('fossil pull && fossil up %s', $ref);
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute('fossil changes', $output, realpath($path));

        return trim($output) ?: null;
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference));

        if (0 !== $this->process->execute($command, $output, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        $log = '';
        $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/';

        foreach ($this->process->splitLines($output) as $line) {
            if (preg_match($match, $line)) {
                break;
            }
            $log .= $line;
        }

        return $log;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_');
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Hg as HgUtils;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDownloader extends VcsDownloader
{
    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $cloneCommand = function ($url) use ($path) {
            return sprintf('hg clone -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path));
        };

        $hgUtils->runCommand($cloneCommand, $url, $path);

        $ref = ProcessExecutor::escape($package->getSourceReference());
        $command = sprintf('hg up -- %s', $ref);
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $ref = $target->getSourceReference();
        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $command = function ($url) use ($ref) {
            return sprintf('hg pull -- %s && hg up -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref));
        };

        $hgUtils->runCommand($command, $url, $path);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!is_dir($path.'/.hg')) {
            return null;
        }

        $this->process->execute('hg st', $output, realpath($path));

        return trim($output) ?: null;
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        $command = sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));

        if (0 !== $this->process->execute($command, $output, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return is_dir($path . '/.hg');
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use Symfony\Component\Process\ExecutableFinder;
use ZipArchive;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ZipDownloader extends ArchiveDownloader
{
    protected static $hasSystemUnzip;
    private static $hasZipArchive;
    private static $isWindows;

    protected $process;
    private $zipArchiveObject;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        if (null === self::$hasSystemUnzip) {
            $finder = new ExecutableFinder;
            self::$hasSystemUnzip = (bool) $finder->find('unzip');
        }

        if (null === self::$hasZipArchive) {
            self::$hasZipArchive = class_exists('ZipArchive');
        }

        if (!self::$hasZipArchive && !self::$hasSystemUnzip) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();
            $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;

            throw new \RuntimeException($error);
        }

        if (null === self::$isWindows) {
            self::$isWindows = Platform::isWindows();

            if (!self::$isWindows && !self::$hasSystemUnzip) {
                $this->io->writeError("<warning>As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension.</warning>");
                $this->io->writeError("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
                $this->io->writeError("<warning>Installing 'unzip' may remediate them.</warning>");
            }
        }

        return parent::download($package, $path, $output);
    }

    /**
     * extract $file to $path with "unzip" command
     *
     * @param  string $file         File to extract
     * @param  string $path         Path where to extract file
     * @param  bool   $isLastChance If true it is called as a fallback and should throw an exception
     * @return bool   Success status
     */
    protected function extractWithSystemUnzip($file, $path, $isLastChance)
    {
        if (!self::$hasZipArchive) {
            // Force Exception throwing if the Other alternative is not available
            $isLastChance = true;
        }

        if (!self::$hasSystemUnzip && !$isLastChance) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithZipArchive($file, $path, true);
        }

        $processError = null;
        // When called after a ZipArchive failed, perhaps there is some files to overwrite
        $overwrite = $isLastChance ? '-o' : '';

        $command = 'unzip -qq '.$overwrite.' '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path);

        try {
            if (0 === $exitCode = $this->process->execute($command, $ignoredOutput)) {
                return true;
            }

            $processError = new \RuntimeException('Failed to execute ('.$exitCode.') '.$command."\n\n".$this->process->getErrorOutput());
        } catch (\Exception $e) {
            $processError = $e;
        }

        if ($isLastChance) {
            throw $processError;
        }

        $this->io->writeError('    '.$processError->getMessage());
        $this->io->writeError('    The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)');
        $this->io->writeError('    Unzip with unzip command failed, falling back to ZipArchive class');

        return $this->extractWithZipArchive($file, $path, true);
    }

    /**
     * extract $file to $path with ZipArchive
     *
     * @param  string $file         File to extract
     * @param  string $path         Path where to extract file
     * @param  bool   $isLastChance If true it is called as a fallback and should throw an exception
     * @return bool   Success status
     */
    protected function extractWithZipArchive($file, $path, $isLastChance)
    {
        if (!self::$hasSystemUnzip) {
            // Force Exception throwing if the Other alternative is not available
            $isLastChance = true;
        }

        if (!self::$hasZipArchive && !$isLastChance) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithSystemUnzip($file, $path, true);
        }

        $processError = null;
        $zipArchive = $this->zipArchiveObject ?: new ZipArchive();

        try {
            if (true === ($retval = $zipArchive->open($file))) {
                $extractResult = $zipArchive->extractTo($path);

                if (true === $extractResult) {
                    $zipArchive->close();

                    return true;
                }

                $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n"));
            } else {
                $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval);
            }
        } catch (\ErrorException $e) {
            $processError = new \RuntimeException('The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e);
        } catch (\Exception $e) {
            $processError = $e;
        }

        if ($isLastChance) {
            throw $processError;
        }

        $this->io->writeError('    '.$processError->getMessage());
        $this->io->writeError('    Unzip with ZipArchive class failed, falling back to unzip command');

        return $this->extractWithSystemUnzip($file, $path, true);
    }

    /**
     * extract $file to $path
     *
     * @param string $file File to extract
     * @param string $path Path where to extract file
     */
    public function extract($file, $path)
    {
        // Each extract calls its alternative if not available or fails
        if (self::$isWindows) {
            $this->extractWithZipArchive($file, $path, false);
        } else {
            $this->extractWithSystemUnzip($file, $path, false);
        }
    }

    /**
     * Give a meaningful error message to the user.
     *
     * @param  int    $retval
     * @param  string $file
     * @return string
     */
    protected function getErrorMessage($retval, $file)
    {
        switch ($retval) {
            case ZipArchive::ER_EXISTS:
                return sprintf("File '%s' already exists.", $file);
            case ZipArchive::ER_INCONS:
                return sprintf("Zip archive '%s' is inconsistent.", $file);
            case ZipArchive::ER_INVAL:
                return sprintf("Invalid argument (%s)", $file);
            case ZipArchive::ER_MEMORY:
                return sprintf("Malloc failure (%s)", $file);
            case ZipArchive::ER_NOENT:
                return sprintf("No such zip file: '%s'", $file);
            case ZipArchive::ER_NOZIP:
                return sprintf("'%s' is not a zip archive.", $file);
            case ZipArchive::ER_OPEN:
                return sprintf("Can't open zip file: %s", $file);
            case ZipArchive::ER_READ:
                return sprintf("Zip read error (%s)", $file);
            case ZipArchive::ER_SEEK:
                return sprintf("Zip seek error (%s)", $file);
            default:
                return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval);
        }
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\Comparer\Comparer;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
use Composer\Util\Url as UrlUtil;

/**
 * Base downloader for files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class FileDownloader implements DownloaderInterface, ChangeReportInterface
{
    protected $io;
    protected $config;
    protected $rfs;
    protected $filesystem;
    protected $cache;
    protected $outputProgress = true;
    private $lastCacheWrites = array();
    private $eventDispatcher;

    /**
     * Constructor.
     *
     * @param IOInterface      $io              The IO instance
     * @param Config           $config          The config
     * @param EventDispatcher  $eventDispatcher The event dispatcher
     * @param Cache            $cache           Optional cache instance
     * @param RemoteFilesystem $rfs             The remote filesystem
     * @param Filesystem       $filesystem      The filesystem
     */
    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->eventDispatcher = $eventDispatcher;
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->cache = $cache;

        if ($this->cache && $this->cache->gcIsNecessary()) {
            $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallationSource()
    {
        return 'dist';
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        if (!$package->getDistUrl()) {
            throw new \InvalidArgumentException('The given package is missing url information');
        }

        if ($output) {
            $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
        }

        $urls = $package->getDistUrls();
        while ($url = array_shift($urls)) {
            try {
                $fileName = $this->doDownload($package, $path, $url);
                break;
            } catch (\Exception $e) {
                if ($this->io->isDebug()) {
                    $this->io->writeError('');
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('');
                    $this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
                }

                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        if ($output) {
            $this->io->writeError('');
        }

        return $fileName;
    }

    protected function doDownload(PackageInterface $package, $path, $url)
    {
        $this->filesystem->emptyDirectory($path);

        $fileName = $this->getFileName($package, $path);

        $processedUrl = $this->processUrl($package, $url);
        $origin = RemoteFilesystem::getOrigin($processedUrl);

        $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl);
        if ($this->eventDispatcher) {
            $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
        }
        $rfs = $preFileDownloadEvent->getRemoteFilesystem();

        try {
            $checksum = $package->getDistSha1Checksum();
            $cacheKey = $this->getCacheKey($package, $processedUrl);

            // use from cache if it is present and has a valid checksum or we have no checksum to check against
            if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
                $this->io->writeError('Loading from cache', false);
            } else {
                // download if cache restore failed
                if (!$this->outputProgress) {
                    $this->io->writeError('Downloading', false);
                }

                // try to download 3 times then fail hard
                $retries = 3;
                while ($retries--) {
                    try {
                        $rfs->copy($origin, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
                        break;
                    } catch (TransportException $e) {
                        // if we got an http response with a proper code, then requesting again will probably not help, abort
                        if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
                            throw $e;
                        }
                        $this->io->writeError('');
                        $this->io->writeError('    Download failed, retrying...', true, IOInterface::VERBOSE);
                        usleep(500000);
                    }
                }

                if (!$this->outputProgress) {
                    $this->io->writeError(' (<comment>100%</comment>)', false);
                }

                if ($this->cache) {
                    $this->lastCacheWrites[$package->getName()] = $cacheKey;
                    $this->cache->copyFrom($cacheKey, $fileName);
                }
            }

            if (!file_exists($fileName)) {
                throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
                    .' directory is writable and you have internet connectivity');
            }

            if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
                throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
            }
        } catch (\Exception $e) {
            // clean up
            $this->filesystem->removeDirectory($path);
            $this->clearLastCacheWrite($package);
            throw $e;
        }

        return $fileName;
    }

    /**
     * {@inheritDoc}
     */
    public function setOutputProgress($outputProgress)
    {
        $this->outputProgress = $outputProgress;

        return $this;
    }

    protected function clearLastCacheWrite(PackageInterface $package)
    {
        if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
            $this->cache->remove($this->lastCacheWrites[$package->getName()]);
            unset($this->lastCacheWrites[$package->getName()]);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path)
    {
        $name = $target->getName();
        $from = $initial->getFullPrettyVersion();
        $to = $target->getFullPrettyVersion();

        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

        $this->remove($initial, $path, false);
        $this->download($target, $path, false);

        $this->io->writeError('');
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path, $output = true)
    {
        if ($output) {
            $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
        }
        if (!$this->filesystem->removeDirectory($path)) {
            throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
        }
    }

    /**
     * Gets file name for specific package
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    download path
     * @return string           file name
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }

    /**
     * Process the download url
     *
     * @param  PackageInterface  $package package the url is coming from
     * @param  string            $url     download url
     * @throws \RuntimeException If any problem with the url
     * @return string            url
     */
    protected function processUrl(PackageInterface $package, $url)
    {
        if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) {
            throw new \RuntimeException('You must enable the openssl extension to download files via https');
        }

        if ($package->getDistReference()) {
            $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference());
        }

        return $url;
    }

    private function getCacheKey(PackageInterface $package, $processedUrl)
    {
        // we use the complete download url here to avoid conflicting entries
        // from different packages, which would potentially allow a given package
        // in a third party repo to pre-populate the cache for the same package in
        // packagist for example.
        $cacheKey = sha1($processedUrl);

        return $package->getName().'/'.$cacheKey.'.'.$package->getDistType();
    }

    /**
     * {@inheritDoc}
     * @throws \RuntimeException
     */
    public function getLocalChanges(PackageInterface $package, $targetDir)
    {
        $prevIO = $this->io;
        $prevProgress = $this->outputProgress;

        $this->io = new NullIO;
        $this->io->loadConfiguration($this->config);
        $this->outputProgress = false;
        $e = null;

        try {
            $this->download($package, $targetDir.'_compare', false);

            $comparer = new Comparer();
            $comparer->setSource($targetDir.'_compare');
            $comparer->setUpdate($targetDir);
            $comparer->doCompare();
            $output = $comparer->getChanged(true, true);
            $this->filesystem->removeDirectory($targetDir.'_compare');
        } catch (\Exception $e) {
        }

        $this->io = $prevIO;
        $this->outputProgress = $prevProgress;

        if ($e) {
            throw $e;
        }

        return trim($output);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * DVCS Downloader interface.
 *
 * @author James Titcumb <james@asgrim.com>
 */
interface DvcsDownloaderInterface
{
    /**
     * Checks for unpushed changes to a current branch
     *
     * @param  PackageInterface $package package directory
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getUnpushedChanges(PackageInterface $package, $path);
}
<?php

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

namespace Composer\Downloader;

/**
 * Downloader for tar files: tar, tar.gz or tar.bz2
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class TarDownloader extends ArchiveDownloader
{
    /**
     * {@inheritDoc}
     */
    protected function extract($file, $path)
    {
        // Can throw an UnexpectedValueException
        $archive = new \PharData($file);
        $archive->extractTo($path, null, true);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Repository\VcsRepository;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDownloader extends VcsDownloader
{
    /** @var Perforce */
    protected $perforce;

    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        $ref = $package->getSourceReference();
        $label = $this->getLabelFromSourceReference($ref);

        $this->io->writeError('Cloning ' . $ref);
        $this->initPerforce($package, $path, $url);
        $this->perforce->setStream($ref);
        $this->perforce->p4Login();
        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();
        $this->perforce->syncCodeBase($label);
        $this->perforce->cleanupClientSpec();
    }

    private function getLabelFromSourceReference($ref)
    {
        $pos = strpos($ref, '@');
        if (false !== $pos) {
            return substr($ref, $pos + 1);
        }

        return null;
    }

    public function initPerforce(PackageInterface $package, $path, $url)
    {
        if (!empty($this->perforce)) {
            $this->perforce->initializePath($path);

            return;
        }

        $repository = $package->getRepository();
        $repoConfig = null;
        if ($repository instanceof VcsRepository) {
            $repoConfig = $this->getRepoConfig($repository);
        }
        $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io);
    }

    private function getRepoConfig(VcsRepository $repository)
    {
        return $repository->getRepoConfig();
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        $this->doDownload($target, $path, $url);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        return $this->perforce->getCommitLogs($fromReference, $toReference);
    }

    public function setPerforce($perforce)
    {
        $this->perforce = $perforce;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return true;
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Util\Filesystem;

/**
 * Extractor for pear packages.
 *
 * Composer cannot rely on tar files structure when place it inside package target dir. Correct source files
 * disposition must be read from package.xml
 * This extract pear package source files to target dir.
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PearPackageExtractor
{
    private static $rolesWithoutPackageNamePrefix = array('php', 'script', 'www');
    /** @var Filesystem */
    private $filesystem;
    private $file;

    public function __construct($file)
    {
        if (!is_file($file)) {
            throw new \UnexpectedValueException('PEAR package file is not found at '.$file);
        }

        $this->filesystem = new Filesystem();
        $this->file = $file;
    }

    /**
     * Installs PEAR source files according to package.xml definitions and removes extracted files
     *
     * @param  string                    $target target install location. all source installation would be performed relative to target path.
     * @param  array                     $roles  types of files to install. default role for PEAR source files are 'php'.
     * @param  array                     $vars   used for replacement tasks
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array())
    {
        $extractionPath = $target.'/tarball';

        try {
            $archive = new \PharData($this->file);
            $archive->extractTo($extractionPath, null, true);

            if (!is_file($this->combine($extractionPath, '/package.xml'))) {
                throw new \RuntimeException('Invalid PEAR package. It must contain package.xml file.');
            }

            $fileCopyActions = $this->buildCopyActions($extractionPath, $roles, $vars);
            $this->copyFiles($fileCopyActions, $extractionPath, $target, $roles, $vars);
            $this->filesystem->removeDirectory($extractionPath);
        } catch (\Exception $exception) {
            throw new \UnexpectedValueException(sprintf('Failed to extract PEAR package %s to %s. Reason: %s', $this->file, $target, $exception->getMessage()), 0, $exception);
        }
    }

    /**
     * Perform copy actions on files
     *
     * @param array $files array of copy actions ('from', 'to') with relative paths
     * @param string $source path to source dir.
     * @param string $target path to destination dir
     * @param array $roles array [role => roleRoot] relative root for files having that role
     * @param array $vars  list of values can be used for replacement tasks
     */
    private function copyFiles($files, $source, $target, $roles, $vars)
    {
        foreach ($files as $file) {
            $from = $this->combine($source, $file['from']);
            $to = $this->combine($target, $roles[$file['role']]);
            $to = $this->combine($to, $file['to']);
            $tasks = $file['tasks'];
            $this->copyFile($from, $to, $tasks, $vars);
        }
    }

    private function copyFile($from, $to, $tasks, $vars)
    {
        if (!is_file($from)) {
            throw new \RuntimeException('Invalid PEAR package. package.xml defines file that is not located inside tarball.');
        }

        $this->filesystem->ensureDirectoryExists(dirname($to));

        if (0 == count($tasks)) {
            $copied = copy($from, $to);
        } else {
            $content = file_get_contents($from);
            $replacements = array();
            foreach ($tasks as $task) {
                $pattern = $task['from'];
                $varName = $task['to'];
                if (isset($vars[$varName])) {
                    if ($varName === 'php_bin' && false === strpos($to, '.bat')) {
                        $replacements[$pattern] = preg_replace('{\.bat$}', '', $vars[$varName]);
                    } else {
                        $replacements[$pattern] = $vars[$varName];
                    }
                }
            }
            $content = strtr($content, $replacements);

            $copied = file_put_contents($to, $content);
        }

        if (false === $copied) {
            throw new \RuntimeException(sprintf('Failed to copy %s to %s', $from, $to));
        }
    }

    /**
     * Builds list of copy and list of remove actions that would transform extracted PEAR tarball into installed package.
     *
     * @param  string            $source string path to extracted files
     * @param  array             $roles  array [role => roleRoot] relative root for files having that role
     * @param  array             $vars   list of values can be used for replacement tasks
     * @throws \RuntimeException
     * @return array             array of 'source' => 'target', where source is location of file in the tarball (relative to source
     *                                  path, and target is destination of file (also relative to $source path)
     */
    private function buildCopyActions($source, array $roles, $vars)
    {
        /** @var \SimpleXmlElement $package */
        $package = simplexml_load_string(file_get_contents($this->combine($source, 'package.xml')));
        if (false === $package) {
            throw new \RuntimeException('Package definition file is not valid.');
        }

        $packageSchemaVersion = $package['version'];
        if ('1.0' == $packageSchemaVersion) {
            $children = $package->release->filelist->children();
            $packageName = (string) $package->name;
            $packageVersion = (string) $package->release->version;
            $sourceDir = $packageName . '-' . $packageVersion;
            $result = $this->buildSourceList10($children, $roles, $sourceDir, '', null, $packageName);
        } elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) {
            $children = $package->contents->children();
            $packageName = (string) $package->name;
            $packageVersion = (string) $package->version->release;
            $sourceDir = $packageName . '-' . $packageVersion;
            $result = $this->buildSourceList20($children, $roles, $sourceDir, '', null, $packageName);

            $namespaces = $package->getNamespaces();
            $package->registerXPathNamespace('ns', $namespaces['']);
            $releaseNodes = $package->xpath('ns:phprelease');
            $this->applyRelease($result, $releaseNodes, $vars);
        } else {
            throw new \RuntimeException('Unsupported schema version of package definition file.');
        }

        return $result;
    }

    private function applyRelease(&$actions, $releaseNodes, $vars)
    {
        foreach ($releaseNodes as $releaseNode) {
            $requiredOs = $releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name ? (string) $releaseNode->installconditions->os->name : '';
            if ($requiredOs && $vars['os'] != $requiredOs) {
                continue;
            }

            if ($releaseNode->filelist) {
                foreach ($releaseNode->filelist->children() as $action) {
                    if ('install' == $action->getName()) {
                        $name = (string) $action['name'];
                        $as = (string) $action['as'];
                        if (isset($actions[$name])) {
                            $actions[$name]['to'] = $as;
                        }
                    } elseif ('ignore' == $action->getName()) {
                        $name = (string) $action['name'];
                        unset($actions[$name]);
                    } else {
                        // unknown action
                    }
                }
            }
            break;
        }
    }

    private function buildSourceList10($children, $targetRoles, $source, $target, $role, $packageName)
    {
        $result = array();

        // enumerating files
        foreach ($children as $child) {
            /** @var $child \SimpleXMLElement */
            if ($child->getName() == 'dir') {
                $dirSource = $this->combine($source, (string) $child['name']);
                $dirTarget = $child['baseinstalldir'] ?: $target;
                $dirRole = $child['role'] ?: $role;
                $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
                $result = array_merge($result, $dirFiles);
            } elseif ($child->getName() == 'file') {
                $fileRole = (string) $child['role'] ?: $role;
                if (isset($targetRoles[$fileRole])) {
                    $fileName = (string) ($child['name'] ?: $child[0]); // $child[0] means text content
                    $fileSource = $this->combine($source, $fileName);
                    $fileTarget = $this->combine((string) $child['baseinstalldir'] ?: $target, $fileName);
                    if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
                        $fileTarget = $packageName . '/' . $fileTarget;
                    }
                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array());
                }
            }
        }

        return $result;
    }

    private function buildSourceList20($children, $targetRoles, $source, $target, $role, $packageName)
    {
        $result = array();

        // enumerating files
        foreach ($children as $child) {
            /** @var $child \SimpleXMLElement */
            if ('dir' == $child->getName()) {
                $dirSource = $this->combine($source, $child['name']);
                $dirTarget = $child['baseinstalldir'] ?: $target;
                $dirRole = $child['role'] ?: $role;
                $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
                $result = array_merge($result, $dirFiles);
            } elseif ('file' == $child->getName()) {
                $fileRole = (string) $child['role'] ?: $role;
                if (isset($targetRoles[$fileRole])) {
                    $fileSource = $this->combine($source, (string) $child['name']);
                    $fileTarget = $this->combine((string) ($child['baseinstalldir'] ?: $target), (string) $child['name']);
                    $fileTasks = array();
                    foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) {
                        if ('replace' == $taskNode->getName()) {
                            $fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to);
                        }
                    }
                    if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
                        $fileTarget = $packageName . '/' . $fileTarget;
                    }
                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks);
                }
            }
        }

        return $result;
    }

    private function combine($left, $right)
    {
        return rtrim($left, '/') . '/' . ltrim($right, '/');
    }
}
<?php

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

namespace Composer\Downloader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class TransportException extends \RuntimeException
{
    protected $headers;
    protected $response;
    protected $statusCode;

    public function setHeaders($headers)
    {
        $this->headers = $headers;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function setResponse($response)
    {
        $this->response = $response;
    }

    public function getResponse()
    {
        return $this->response;
    }

    public function setStatusCode($statusCode)
    {
        $this->statusCode = $statusCode;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
<?php

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

namespace Composer\Downloader;

/**
 * Downloader for phar files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class PharDownloader extends ArchiveDownloader
{
    /**
     * {@inheritDoc}
     */
    protected function extract($file, $path)
    {
        // Can throw an UnexpectedValueException
        $archive = new \Phar($file);
        $archive->extractTo($path, null, true);
        /* TODO: handle openssl signed phars
         * https://github.com/composer/composer/pull/33#issuecomment-2250768
         * https://github.com/koto/phar-util
         * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html
         */
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;

/**
 * GZip archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 */
class GzipDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($file, $path)
    {
        $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));

        // Try to use gunzip on *nix
        if (!Platform::isWindows()) {
            $command = 'gzip -cd -- ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return;
            }

            if (extension_loaded('zlib')) {
                // Fallback to using the PHP extension.
                $this->extractUsingExt($file, $targetFilepath);

                return;
            }

            $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
            throw new \RuntimeException($processError);
        }

        // Windows version of PHP has built-in support of gzip functions
        $this->extractUsingExt($file, $targetFilepath);
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }

    private function extractUsingExt($file, $targetFilepath)
    {
        $archiveFile = gzopen($file, 'rb');
        $targetFile = fopen($targetFilepath, 'wb');
        while ($string = gzread($archiveFile, 4096)) {
            fwrite($targetFile, $string, Platform::strlen($string));
        }
        gzclose($archiveFile);
        fclose($targetFile);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\Svn as SvnUtil;
use Composer\Repository\VcsRepository;
use Composer\Util\ProcessExecutor;

/**
 * @author Ben Bieker <mail@ben-bieker.de>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDownloader extends VcsDownloader
{
    protected $cacheCredentials = true;

    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        SvnUtil::cleanEnv();
        $ref = $package->getSourceReference();

        $repo = $package->getRepository();
        if ($repo instanceof VcsRepository) {
            $repoConfig = $repo->getRepoConfig();
            if (array_key_exists('svn-cache-credentials', $repoConfig)) {
                $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials'];
            }
        }

        $this->io->writeError(" Checking out ".$package->getSourceReference());
        $this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        SvnUtil::cleanEnv();
        $ref = $target->getSourceReference();

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $util = new SvnUtil($url, $this->io, $this->config);
        $flags = "";
        if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
            $flags .= ' --ignore-ancestry';
        }

        $this->io->writeError(" Checking out " . $ref);
        $this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute('svn status --ignore-externals', $output, $path);

        return preg_match('{^ *[^X ] +}m', $output) ? $output : null;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  string            $baseUrl Base URL of the repository
     * @param  string            $command SVN command to run
     * @param  string            $url     SVN url
     * @param  string            $cwd     Working directory
     * @param  string            $path    Target for a checkout
     * @throws \RuntimeException
     * @return string
     */
    protected function execute($baseUrl, $command, $url, $cwd = null, $path = null)
    {
        $util = new SvnUtil($baseUrl, $this->io, $this->config);
        $util->setCacheCredentials($this->cacheCredentials);
        try {
            return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
        } catch (\RuntimeException $e) {
            throw new \RuntimeException(
                'Package could not be downloaded, '.$e->getMessage()
            );
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function cleanChanges(PackageInterface $package, $path, $update)
    {
        if (!$changes = $this->getLocalChanges($package, $path)) {
            return;
        }

        if (!$this->io->isInteractive()) {
            if (true === $this->config->get('discard-changes')) {
                return $this->discardChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(function ($elem) {
            return '    '.$elem;
        }, preg_split('{\s*\r?\n\s*}', $changes));
        $countChanges = count($changes);
        $this->io->writeError(sprintf('    <error>The package has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
        $this->io->writeError(array_slice($changes, 0, 10));
        if ($countChanges > 10) {
            $remainingChanges = $countChanges - 10;
            $this->io->writeError(
                sprintf(
                    '    <info>'.$remainingChanges.' more file%s modified, choose "v" to view the full list</info>',
                    $remainingChanges === 1 ? '' : 's'
                )
            );
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case '?':
                default:
                    $this->io->writeError(array(
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    ? - print help',
                    ));
                    break;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) {
            // retrieve the svn base url from the checkout folder
            $command = sprintf('svn info --non-interactive --xml -- %s', ProcessExecutor::escape($path));
            if (0 !== $this->process->execute($command, $output, $path)) {
                throw new \RuntimeException(
                    'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
                );
            }

            $urlPattern = '#<url>(.*)</url>#';
            if (preg_match($urlPattern, $output, $matches)) {
                $baseUrl = $matches[1];
            } else {
                throw new \RuntimeException(
                    'Unable to determine svn url for path '. $path
                );
            }

            // strip paths from references and only keep the actual revision
            $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference);
            $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference);

            $command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision));

            $util = new SvnUtil($baseUrl, $this->io, $this->config);
            $util->setCacheCredentials($this->cacheCredentials);
            try {
                return $util->executeLocal($command, $path, null, $this->io->isVerbose());
            } catch (\RuntimeException $e) {
                throw new \RuntimeException(
                    'Failed to execute ' . $command . "\n\n".$e->getMessage()
                );
            }
        }

        return "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
    }

    protected function discardChanges($path)
    {
        if (0 !== $this->process->execute('svn revert -R .', $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return is_dir($path.'/.svn');
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * ChangeReport interface.
 *
 * @author Sascha Egerer <sascha.egerer@dkd.de>
 */
interface ChangeReportInterface
{
    /**
     * Checks for changes to the local copy
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getLocalChanges(PackageInterface $package, $path);
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;

/**
 * Base downloader for archives
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class ArchiveDownloader extends FileDownloader
{
    /**
     * {@inheritDoc}
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
        $retries = 3;
        while ($retries--) {
            $fileName = parent::download($package, $path, $output);

            if ($output) {
                $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
            }

            try {
                $this->filesystem->ensureDirectoryExists($temporaryDir);
                try {
                    $this->extract($fileName, $temporaryDir);
                } catch (\Exception $e) {
                    // remove cache if the file was corrupted
                    parent::clearLastCacheWrite($package);
                    throw $e;
                }

                $this->filesystem->unlink($fileName);

                $contentDir = $this->getFolderContent($temporaryDir);

                // only one dir in the archive, extract its contents out of it
                if (1 === count($contentDir) && is_dir(reset($contentDir))) {
                    $contentDir = $this->getFolderContent((string) reset($contentDir));
                }

                // move files back out of the temp dir
                foreach ($contentDir as $file) {
                    $file = (string) $file;
                    $this->filesystem->rename($file, $path . '/' . basename($file));
                }

                $this->filesystem->removeDirectory($temporaryDir);
                if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
                    $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
                }
                if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
                    $this->filesystem->removeDirectory($this->config->get('vendor-dir'));
                }
            } catch (\Exception $e) {
                // clean up
                $this->filesystem->removeDirectory($path);
                $this->filesystem->removeDirectory($temporaryDir);

                // retry downloading if we have an invalid zip file
                if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
                    $this->io->writeError('');
                    if ($this->io->isDebug()) {
                        $this->io->writeError('    Invalid zip file ('.$e->getMessage().'), retrying...');
                    } else {
                        $this->io->writeError('    Invalid zip file, retrying...');
                    }
                    usleep(500000);
                    continue;
                }

                throw $e;
            }

            break;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
    }

    /**
     * Extract file to directory
     *
     * @param string $file Extracted file
     * @param string $path Directory
     *
     * @throws \UnexpectedValueException If can not extract downloaded file to path
     */
    abstract protected function extract($file, $path);

    /**
     * Returns the folder content, excluding dotfiles
     *
     * @param  string         $dir Directory
     * @return \SplFileInfo[]
     */
    private function getFolderContent($dir)
    {
        $finder = Finder::create()
            ->ignoreVCS(false)
            ->ignoreDotFiles(false)
            ->notName('.DS_Store')
            ->depth(0)
            ->in($dir);

        return iterator_to_array($finder);
    }
}
<?php

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

namespace Composer\Config;

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

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

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

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

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

    /**
     * Remove a property
     *
     * @param string $name
     */
    public function removeProperty($name);

    /**
     * Add a package link
     *
     * @param string $type  Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name  Name
     * @param string $value Value
     */
    public function addLink($type, $name, $value);

    /**
     * Remove a package link
     *
     * @param string $type Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name Name
     */
    public function removeLink($type, $name);

    /**
     * Gives a user-friendly name to this source (file path or so)
     *
     * @return string
     */
    public function getName();
}
<?php

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

namespace Composer\Config;

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

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

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

    /**
     * Constructor
     *
     * @param JsonFile $file
     * @param bool     $authConfig
     */
    public function __construct(JsonFile $file, $authConfig = false)
    {
        $this->file = $file;
        $this->authConfig = $authConfig;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->file->getPath();
    }

    /**
     * {@inheritdoc}
     */
    public function addRepository($name, $config)
    {
        $this->manipulateJson('addRepository', $name, $config, function (&$config, $repo, $repoConfig) {
            // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have
            // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation
            if (isset($config['repositories'])) {
                foreach ($config['repositories'] as $index => $val) {
                    if ($index === $repo) {
                        continue;
                    }
                    if (is_numeric($index) && ($val === array('packagist' => false) || $val === array('packagist.org' => false))) {
                        unset($config['repositories'][$index]);
                        $config['repositories']['packagist.org'] = false;
                        break;
                    }
                }
            }

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

    /**
     * {@inheritdoc}
     */
    public function removeRepository($name)
    {
        $this->manipulateJson('removeRepository', $name, function (&$config, $repo) {
            unset($config['repositories'][$repo]);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function addConfigSetting($name, $value)
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) use ($authConfig) {
            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                list($key, $host) = explode('.', $key, 2);
                if ($authConfig) {
                    $config[$key][$host] = $val;
                } else {
                    $config['config'][$key][$host] = $val;
                }
            } else {
                $config['config'][$key] = $val;
            }
        });
    }

    /**
     * {@inheritdoc}
     */
    public function removeConfigSetting($name)
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) use ($authConfig) {
            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                list($key, $host) = explode('.', $key, 2);
                if ($authConfig) {
                    unset($config[$key][$host]);
                } else {
                    unset($config['config'][$key][$host]);
                }
            } else {
                unset($config['config'][$key]);
            }
        });
    }

    /**
     * {@inheritdoc}
     */
    public function addProperty($name, $value)
    {
        $this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) {
            if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        $arr[$bit] = array();
                    }
                    $arr = &$arr[$bit];
                }
                $arr[$last] = $val;
            } else {
                $config[$key] = $val;
            }
        });
    }

    /**
     * {@inheritdoc}
     */
    public function removeProperty($name)
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('removeProperty', $name, function (&$config, $key) {
            if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        return;
                    }
                    $arr = &$arr[$bit];
                }
                unset($arr[$last]);
            } else {
                unset($config[$key]);
            }
        });
    }

    /**
     * {@inheritdoc}
     */
    public function addLink($type, $name, $value)
    {
        $this->manipulateJson('addLink', $type, $name, $value, function (&$config, $type, $name, $value) {
            $config[$type][$name] = $value;
        });
    }

    /**
     * {@inheritdoc}
     */
    public function removeLink($type, $name)
    {
        $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) {
            unset($config[$type][$name]);

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

    protected function manipulateJson($method, $args, $fallback)
    {
        $args = func_get_args();
        // remove method & fallback
        array_shift($args);
        $fallback = array_pop($args);

        if ($this->file->exists()) {
            if (!is_writable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath()));
            }

            if (!is_readable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath()));
            }

            $contents = file_get_contents($this->file->getPath());
        } elseif ($this->authConfig) {
            $contents = "{\n}\n";
        } else {
            $contents = "{\n    \"config\": {\n    }\n}\n";
        }

        $manipulator = new JsonManipulator($contents);

        $newFile = !$this->file->exists();

        // override manipulator method for auth config files
        if ($this->authConfig && $method === 'addConfigSetting') {
            $method = 'addSubNode';
            list($mainNode, $name) = explode('.', $args[0], 2);
            $args = array($mainNode, $name, $args[1]);
        } elseif ($this->authConfig && $method === 'removeConfigSetting') {
            $method = 'removeSubNode';
            list($mainNode, $name) = explode('.', $args[0], 2);
            $args = array($mainNode, $name);
        }

        // try to update cleanly
        if (call_user_func_array(array($manipulator, $method), $args)) {
            file_put_contents($this->file->getPath(), $manipulator->getContents());
        } else {
            // on failed clean update, call the fallback and rewrite the whole file
            $config = $this->file->read();
            $this->arrayUnshiftRef($args, $config);
            call_user_func_array($fallback, $args);
            // avoid ending up with arrays for keys that should be objects
            foreach (array('require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev') as $linkType) {
                if (isset($config[$linkType]) && $config[$linkType] === array()) {
                    $config[$linkType] = new \stdClass;
                }
            }
            $this->file->write($config);
        }

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

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

        return $return;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;

/**
 * Utility to handle installation of package "bin"/binaries
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Helmut Hummel <info@helhum.io>
 */
class BinaryInstaller
{
    protected $binDir;
    protected $binCompat;
    protected $io;
    protected $filesystem;

    /**
     * @param IOInterface $io
     * @param string      $binDir
     * @param string      $binCompat
     * @param Filesystem  $filesystem
     */
    public function __construct(IOInterface $io, $binDir, $binCompat, Filesystem $filesystem = null)
    {
        $this->binDir = $binDir;
        $this->binCompat = $binCompat;
        $this->io = $io;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    public function installBinaries(PackageInterface $package, $installPath, $warnOnOverwrite = true)
    {
        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }
        foreach ($binaries as $bin) {
            $binPath = $installPath.'/'.$bin;
            if (!file_exists($binPath)) {
                $this->io->writeError('    <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
                continue;
            }

            // in case a custom installer returned a relative path for the
            // $package, we can now safely turn it into a absolute path (as we
            // already checked the binary's existence). The following helpers
            // will require absolute paths to work properly.
            $binPath = realpath($binPath);

            $this->initializeBinDir();
            $link = $this->binDir.'/'.basename($bin);
            if (file_exists($link)) {
                if (is_link($link)) {
                    // likely leftover from a previous install, make sure
                    // that the target is still executable in case this
                    // is a fresh install of the vendor.
                    Silencer::call('chmod', $link, 0777 & ~umask());
                }
                if ($warnOnOverwrite) {
                    $this->io->writeError('    Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
                }
                continue;
            }

            if ($this->binCompat === "auto") {
                if (Platform::isWindows()) {
                    $this->installFullBinaries($binPath, $link, $bin, $package);
                } else {
                    $this->installSymlinkBinaries($binPath, $link);
                }
            } elseif ($this->binCompat === "full") {
                $this->installFullBinaries($binPath, $link, $bin, $package);
            }
            Silencer::call('chmod', $link, 0777 & ~umask());
        }
    }

    public function removeBinaries(PackageInterface $package)
    {
        $this->initializeBinDir();

        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }
        foreach ($binaries as $bin) {
            $link = $this->binDir.'/'.basename($bin);
            if (is_link($link) || file_exists($link)) {
                $this->filesystem->unlink($link);
            }
            if (file_exists($link.'.bat')) {
                $this->filesystem->unlink($link.'.bat');
            }
        }

        // attempt removing the bin dir in case it is left empty
        if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) {
            Silencer::call('rmdir', $this->binDir);
        }
    }

    public static function determineBinaryCaller($bin)
    {
        if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
            return 'call';
        }

        $handle = fopen($bin, 'r');
        $line = fgets($handle);
        fclose($handle);
        if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
            return trim($match[1]);
        }

        return 'php';
    }

    protected function getBinaries(PackageInterface $package)
    {
        return $package->getBinaries();
    }

    protected function installFullBinaries($binPath, $link, $bin, PackageInterface $package)
    {
        // add unixy support for cygwin and similar environments
        if ('.bat' !== substr($binPath, -4)) {
            $this->installUnixyProxyBinaries($binPath, $link);
            @chmod($link, 0777 & ~umask());
            $link .= '.bat';
            if (file_exists($link)) {
                $this->io->writeError('    Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed');
            }
        }
        if (!file_exists($link)) {
            file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link));
        }
    }

    protected function installSymlinkBinaries($binPath, $link)
    {
        if (!$this->filesystem->relativeSymlink($binPath, $link)) {
            $this->installUnixyProxyBinaries($binPath, $link);
        }
    }

    protected function installUnixyProxyBinaries($binPath, $link)
    {
        file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
    }

    protected function initializeBinDir()
    {
        $this->filesystem->ensureDirectoryExists($this->binDir);
        $this->binDir = realpath($this->binDir);
    }

    protected function generateWindowsProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        $caller = self::determineBinaryCaller($bin);

        return "@ECHO OFF\r\n".
            "setlocal DISABLEDELAYEDEXPANSION\r\n".
            "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n".
            "{$caller} \"%BIN_TARGET%\" %*\r\n";
    }

    protected function generateUnixyProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);

        $binDir = ProcessExecutor::escape(dirname($binPath));
        $binFile = basename($binPath);

        $proxyCode = <<<PROXY
#!/usr/bin/env sh

dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd)

if [ -d /proc/cygdrive ]; then
    case \$(which php) in
        \$(readlink -n /proc/cygdrive)/*)
            # We are in Cygwin using Windows php, so the path must be translated
            dir=\$(cygpath -m "\$dir");
            ;;
    esac
fi

"\${dir}/$binFile" "\$@"

PROXY;

        return $proxyCode;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Add suggested packages from different places to output them in the end.
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 */
class SuggestedPackagesReporter
{
    /**
     * @var array
     */
    protected $suggestedPackages = array();

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

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @return array Suggested packages with source, target and reason keys.
     */
    public function getPackages()
    {
        return $this->suggestedPackages;
    }

    /**
     * Add suggested packages to be listed after install
     *
     * Could be used to add suggested packages both from the installer
     * or from CreateProjectCommand.
     *
     * @param  string                    $source Source package which made the suggestion
     * @param  string                    $target Target package to be suggested
     * @param  string                    $reason Reason the target package to be suggested
     * @return SuggestedPackagesReporter
     */
    public function addPackage($source, $target, $reason)
    {
        $this->suggestedPackages[] = array(
            'source' => $source,
            'target' => $target,
            'reason' => $reason,
        );

        return $this;
    }

    /**
     * Add all suggestions from a package.
     *
     * @param  PackageInterface          $package
     * @return SuggestedPackagesReporter
     */
    public function addSuggestionsFromPackage(PackageInterface $package)
    {
        $source = $package->getPrettyName();
        foreach ($package->getSuggests() as $target => $reason) {
            $this->addPackage(
                $source,
                $target,
                $reason
            );
        }

        return $this;
    }

    /**
     * Output suggested packages.
     * Do not list the ones already installed if installed repository provided.
     *
     * @param  RepositoryInterface       $installedRepo Installed packages
     * @return SuggestedPackagesReporter
     */
    public function output(RepositoryInterface $installedRepo = null)
    {
        $suggestedPackages = $this->getPackages();
        $installedPackages = array();
        if (null !== $installedRepo && ! empty($suggestedPackages)) {
            foreach ($installedRepo->getPackages() as $package) {
                $installedPackages = array_merge(
                    $installedPackages,
                    $package->getNames()
                );
            }
        }

        foreach ($suggestedPackages as $suggestion) {
            if (in_array($suggestion['target'], $installedPackages)) {
                continue;
            }

            $this->io->writeError(sprintf(
                '%s suggests installing %s%s',
                $suggestion['source'],
                $this->escapeOutput($suggestion['target']),
                $this->escapeOutput('' !== $suggestion['reason'] ? ' ('.$suggestion['reason'].')' : '')
            ));
        }

        return $this;
    }

    /**
     * @param  string $string
     * @return string
     */
    private function escapeOutput($string)
    {
        return OutputFormatter::escape(
            $this->removeControlCharacters($string)
        );
    }

    /**
     * @param  string $string
     * @return string
     */
    private function removeControlCharacters($string)
    {
        return preg_replace(
            '/[[:cntrl:]]/',
            '',
            str_replace("\n", ' ', $string)
        );
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Package\PackageInterface;
use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;

/**
 * Project Installer is used to install a single package into a directory as
 * root project.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 */
class ProjectInstaller implements InstallerInterface
{
    private $installPath;
    private $downloadManager;
    private $filesystem;

    public function __construct($installPath, DownloadManager $dm)
    {
        $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/';
        $this->downloadManager = $dm;
        $this->filesystem = new Filesystem;
    }

    /**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    public function supports($packageType)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $installPath = $this->installPath;
        if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
            throw new \InvalidArgumentException("Project directory $installPath is not empty.");
        }
        if (!is_dir($installPath)) {
            mkdir($installPath, 0777, true);
        }
        $this->downloadManager->download($package, $installPath);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path
     */
    public function getInstallPath(PackageInterface $package)
    {
        return $this->installPath;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\Repository\CompositeRepository;

/**
 * The Package Event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvent extends InstallerEvent
{
    /**
     * @var OperationInterface The package instance
     */
    private $operation;

    /**
     * Constructor.
     *
     * @param string               $eventName
     * @param Composer             $composer
     * @param IOInterface          $io
     * @param bool                 $devMode
     * @param PolicyInterface      $policy
     * @param Pool                 $pool
     * @param CompositeRepository  $installedRepo
     * @param Request              $request
     * @param OperationInterface[] $operations
     * @param OperationInterface   $operation
     */
    public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
    {
        parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations);

        $this->operation = $operation;
    }

    /**
     * Returns the package instance.
     *
     * @return OperationInterface
     */
    public function getOperation()
    {
        return $this->operation;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Composer;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\EventDispatcher\Event;
use Composer\IO\IOInterface;
use Composer\Repository\CompositeRepository;

/**
 * An event for all installer.
 *
 * @author François Pluchino <francois.pluchino@gmail.com>
 */
class InstallerEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

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

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

    /**
     * @var PolicyInterface
     */
    private $policy;

    /**
     * @var Pool
     */
    private $pool;

    /**
     * @var CompositeRepository
     */
    private $installedRepo;

    /**
     * @var Request
     */
    private $request;

    /**
     * @var OperationInterface[]
     */
    private $operations;

    /**
     * Constructor.
     *
     * @param string               $eventName
     * @param Composer             $composer
     * @param IOInterface          $io
     * @param bool                 $devMode
     * @param PolicyInterface      $policy
     * @param Pool                 $pool
     * @param CompositeRepository  $installedRepo
     * @param Request              $request
     * @param OperationInterface[] $operations
     */
    public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installedRepo = $installedRepo;
        $this->request = $request;
        $this->operations = $operations;
    }

    /**
     * @return Composer
     */
    public function getComposer()
    {
        return $this->composer;
    }

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

    /**
     * @return bool
     */
    public function isDevMode()
    {
        return $this->devMode;
    }

    /**
     * @return PolicyInterface
     */
    public function getPolicy()
    {
        return $this->policy;
    }

    /**
     * @return Pool
     */
    public function getPool()
    {
        return $this->pool;
    }

    /**
     * @return CompositeRepository
     */
    public function getInstalledRepo()
    {
        return $this->installedRepo;
    }

    /**
     * @return Request
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations()
    {
        return $this->operations;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Downloader\PearPackageExtractor;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Composer\Util\Filesystem;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class PearInstaller extends LibraryInstaller
{
    /**
     * Initializes library installer.
     *
     * @param IOInterface $io       io instance
     * @param Composer    $composer
     * @param string      $type     package type that this installer handles
     */
    public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library')
    {
        $filesystem = new Filesystem();
        $binaryInstaller = new PearBinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), rtrim($composer->getConfig()->get('vendor-dir'), '/'), $composer->getConfig()->get('bin-compat'), $filesystem, $this);

        parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $this->uninstall($repo, $initial);
        $this->install($repo, $target);
    }

    protected function installCode(PackageInterface $package)
    {
        parent::installCode($package);

        $isWindows = Platform::isWindows();
        $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');

        if (!$isWindows) {
            $php_bin = '/usr/bin/env ' . $php_bin;
        }

        $installPath = $this->getInstallPath($package);
        $vars = array(
            'os' => $isWindows ? 'windows' : 'linux',
            'php_bin' => $php_bin,
            'pear_php' => $installPath,
            'php_dir' => $installPath,
            'bin_dir' => $installPath . '/bin',
            'data_dir' => $installPath . '/data',
            'version' => $package->getPrettyVersion(),
        );

        $packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME);
        $pearExtractor = new PearPackageExtractor($packageArchive);
        $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars);

        $this->io->writeError('    Cleaning up', true, IOInterface::VERBOSE);
        $this->filesystem->unlink($packageArchive);
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Does not install anything but marks packages installed in the repo
 *
 * Useful for dry runs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoopInstaller implements InstallerInterface
{
    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }
        $repo->removePackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        $targetDir = $package->getTargetDir();

        return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\IO\IOInterface;

/**
 * Metapackage installation manager.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class MetapackageInstaller implements InstallerInterface
{
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === 'metapackage';
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");

        $repo->addPackage(clone $package);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $name = $target->getName();
        $from = $initial->getFullPrettyVersion();
        $to = $target->getFullPrettyVersion();
        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");

        $repo->removePackage($initial);
        $repo->addPackage(clone $target);
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");

        $repo->removePackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        return '';
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\Util\StreamContextFactory;

/**
 * Package operation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallationManager
{
    private $installers = array();
    private $cache = array();
    private $notifiablePackages = array();

    public function reset()
    {
        $this->notifiablePackages = array();
    }

    /**
     * Adds installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function addInstaller(InstallerInterface $installer)
    {
        array_unshift($this->installers, $installer);
        $this->cache = array();
    }

    /**
     * Removes installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function removeInstaller(InstallerInterface $installer)
    {
        if (false !== ($key = array_search($installer, $this->installers, true))) {
            array_splice($this->installers, $key, 1);
            $this->cache = array();
        }
    }

    /**
     * Disables plugins.
     *
     * We prevent any plugins from being instantiated by simply
     * deactivating the installer for them. This ensure that no third-party
     * code is ever executed.
     */
    public function disablePlugins()
    {
        foreach ($this->installers as $i => $installer) {
            if (!$installer instanceof PluginInstaller) {
                continue;
            }

            unset($this->installers[$i]);
        }
    }

    /**
     * Returns installer for a specific package type.
     *
     * @param string $type package type
     *
     * @throws \InvalidArgumentException if installer for provided type is not registered
     * @return InstallerInterface
     */
    public function getInstaller($type)
    {
        $type = strtolower($type);

        if (isset($this->cache[$type])) {
            return $this->cache[$type];
        }

        foreach ($this->installers as $installer) {
            if ($installer->supports($type)) {
                return $this->cache[$type] = $installer;
            }
        }

        throw new \InvalidArgumentException('Unknown installer type: '.$type);
    }

    /**
     * Checks whether provided package is installed in one of the registered installers.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     *
     * @return bool
     */
    public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if ($package instanceof AliasPackage) {
            return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf());
        }

        return $this->getInstaller($package->getType())->isInstalled($repo, $package);
    }

    /**
     * Install binary for the given package.
     * If the installer associated to this package doesn't handle that function, it'll do nothing.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package)
    {
        try {
            $installer = $this->getInstaller($package->getType());
        } catch (\InvalidArgumentException $e) {
            // no installer found for the current package type (@see `getInstaller()`)
            return;
        }

        // if the given installer support installing binaries
        if ($installer instanceof BinaryPresenceInterface) {
            $installer->ensureBinariesPresence($package);
        }
    }

    /**
     * Executes solver operation.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param OperationInterface  $operation operation instance
     */
    public function execute(RepositoryInterface $repo, OperationInterface $operation)
    {
        $method = $operation->getJobType();
        $this->$method($repo, $operation);
    }

    /**
     * Executes install operation.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param InstallOperation    $operation operation instance
     */
    public function install(RepositoryInterface $repo, InstallOperation $operation)
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $installer->install($repo, $package);
        $this->markForNotification($package);
    }

    /**
     * Executes update operation.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param UpdateOperation     $operation operation instance
     */
    public function update(RepositoryInterface $repo, UpdateOperation $operation)
    {
        $initial = $operation->getInitialPackage();
        $target = $operation->getTargetPackage();

        $initialType = $initial->getType();
        $targetType = $target->getType();

        if ($initialType === $targetType) {
            $installer = $this->getInstaller($initialType);
            $installer->update($repo, $initial, $target);
            $this->markForNotification($target);
        } else {
            $this->getInstaller($initialType)->uninstall($repo, $initial);
            $this->getInstaller($targetType)->install($repo, $target);
        }
    }

    /**
     * Uninstalls package.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param UninstallOperation  $operation operation instance
     */
    public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $installer->uninstall($repo, $package);
    }

    /**
     * Executes markAliasInstalled operation.
     *
     * @param RepositoryInterface         $repo      repository in which to check
     * @param MarkAliasInstalledOperation $operation operation instance
     */
    public function markAliasInstalled(RepositoryInterface $repo, MarkAliasInstalledOperation $operation)
    {
        $package = $operation->getPackage();

        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * Executes markAlias operation.
     *
     * @param RepositoryInterface           $repo      repository in which to check
     * @param MarkAliasUninstalledOperation $operation operation instance
     */
    public function markAliasUninstalled(RepositoryInterface $repo, MarkAliasUninstalledOperation $operation)
    {
        $package = $operation->getPackage();

        $repo->removePackage($package);
    }

    /**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path
     */
    public function getInstallPath(PackageInterface $package)
    {
        $installer = $this->getInstaller($package->getType());

        return $installer->getInstallPath($package);
    }

    public function notifyInstalls(IOInterface $io)
    {
        foreach ($this->notifiablePackages as $repoUrl => $packages) {
            $repositoryName = parse_url($repoUrl, PHP_URL_HOST);
            if ($io->hasAuthentication($repositoryName)) {
                $auth = $io->getAuthentication($repositoryName);
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $authHeader = 'Authorization: Basic '.$authStr;
            }

            // non-batch API, deprecated
            if (strpos($repoUrl, '%package%')) {
                foreach ($packages as $package) {
                    $url = str_replace('%package%', $package->getPrettyName(), $repoUrl);

                    $params = array(
                        'version' => $package->getPrettyVersion(),
                        'version_normalized' => $package->getVersion(),
                    );
                    $opts = array('http' =>
                        array(
                            'method' => 'POST',
                            'header' => array('Content-type: application/x-www-form-urlencoded'),
                            'content' => http_build_query($params, '', '&'),
                            'timeout' => 3,
                        ),
                    );
                    if (isset($authHeader)) {
                        $opts['http']['header'][] = $authHeader;
                    }

                    $context = StreamContextFactory::getContext($url, $opts);
                    @file_get_contents($url, false, $context);
                }

                continue;
            }

            $postData = array('downloads' => array());
            foreach ($packages as $package) {
                $postData['downloads'][] = array(
                    'name' => $package->getPrettyName(),
                    'version' => $package->getVersion(),
                );
            }

            $opts = array('http' =>
                array(
                    'method' => 'POST',
                    'header' => array('Content-Type: application/json'),
                    'content' => json_encode($postData),
                    'timeout' => 6,
                ),
            );
            if (isset($authHeader)) {
                $opts['http']['header'][] = $authHeader;
            }

            $context = StreamContextFactory::getContext($repoUrl, $opts);
            @file_get_contents($repoUrl, false, $context);
        }

        $this->reset();
    }

    private function markForNotification(PackageInterface $package)
    {
        if ($package->getNotificationUrl()) {
            $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
        }
    }
}
<?php

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

namespace Composer\Installer;

/**
 * The Installer Events.
 *
 * @author François Pluchino <francois.pluchino@gmail.com>
 */
class InstallerEvents
{
    /**
     * The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins
     * resolve operations.
     *
     * The event listener method receives a
     * Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving';

    /**
     * The POST_DEPENDENCIES_SOLVING event occurs as a installer after
     * resolve operations.
     *
     * The event listener method receives a
     * Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    const POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving';
}
<?php

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

namespace Composer\Installer;

use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;

/**
 * Interface for the package installation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstallerInterface
{
    /**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    public function supports($packageType);

    /**
     * Checks that provided package is installed.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     *
     * @return bool
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Installs specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Updates specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $initial already installed package version
     * @param PackageInterface             $target  updated version
     *
     * @throws InvalidArgumentException if $initial package is not installed
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);

    /**
     * Uninstalls specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path to install to, which MUST not end with a slash
     */
    public function getInstallPath(PackageInterface $package);
}
<?php

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

namespace Composer\Installer;

use Composer\Package\PackageInterface;

/**
 * Interface for the package installation manager that handle binary installation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface BinaryPresenceInterface
{
    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package package instance
     */
    public function ensureBinariesPresence(PackageInterface $package);
}
<?php

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

namespace Composer\Installer;

/**
 * Package Events.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvents
{
    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const PRE_PACKAGE_INSTALL = 'pre-package-install';

    /**
     * The POST_PACKAGE_INSTALL event occurs after a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const POST_PACKAGE_INSTALL = 'post-package-install';

    /**
     * The PRE_PACKAGE_UPDATE event occurs before a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const PRE_PACKAGE_UPDATE = 'pre-package-update';

    /**
     * The POST_PACKAGE_UPDATE event occurs after a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const POST_PACKAGE_UPDATE = 'post-package-update';

    /**
     * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall';

    /**
     * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
}
<?php

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

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Composer\Util\Platform;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
{
    protected $composer;
    protected $vendorDir;
    protected $binDir;
    protected $downloadManager;
    protected $io;
    protected $type;
    protected $filesystem;
    protected $binCompat;
    protected $binaryInstaller;

    /**
     * Initializes library installer.
     *
     * @param IOInterface     $io
     * @param Composer        $composer
     * @param string          $type
     * @param Filesystem      $filesystem
     * @param BinaryInstaller $binaryInstaller
     */
    public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $binaryInstaller = null)
    {
        $this->composer = $composer;
        $this->downloadManager = $composer->getDownloadManager();
        $this->io = $io;
        $this->type = $type;

        $this->filesystem = $filesystem ?: new Filesystem();
        $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
        $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem);
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === $this->type || null === $this->type;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            return false;
        }

        $installPath = $this->getInstallPath($package);

        if (is_readable($installPath)) {
            return true;
        }

        return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        // remove the binaries if it appears the package files are missing
        if (!is_readable($downloadPath) && $repo->hasPackage($package)) {
            $this->binaryInstaller->removeBinaries($package);
        }

        $this->installCode($package);
        $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package));
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->initializeVendorDir();

        $this->binaryInstaller->removeBinaries($initial);
        $this->updateCode($initial, $target);
        $this->binaryInstaller->installBinaries($target, $this->getInstallPath($target));
        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->removeCode($package);
        $this->binaryInstaller->removeBinaries($package);
        $repo->removePackage($package);

        $downloadPath = $this->getPackageBasePath($package);
        if (strpos($package->getName(), '/')) {
            $packageVendorDir = dirname($downloadPath);
            if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) {
                Silencer::call('rmdir', $packageVendorDir);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        $this->initializeVendorDir();

        $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName();
        $targetDir = $package->getTargetDir();

        return $basePath . ($targetDir ? '/'.$targetDir : '');
    }

    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package)
    {
        $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false);
    }

    /**
     * Returns the base path of the package without target-dir path
     *
     * It is used for BC as getInstallPath tends to be overridden by
     * installer plugins but not getPackageBasePath
     *
     * @param  PackageInterface $package
     * @return string
     */
    protected function getPackageBasePath(PackageInterface $package)
    {
        $installPath = $this->getInstallPath($package);
        $targetDir = $package->getTargetDir();

        if ($targetDir) {
            return preg_replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath);
        }

        return $installPath;
    }

    protected function installCode(PackageInterface $package)
    {
        $downloadPath = $this->getInstallPath($package);
        $this->downloadManager->download($package, $downloadPath);
    }

    protected function updateCode(PackageInterface $initial, PackageInterface $target)
    {
        $initialDownloadPath = $this->getInstallPath($initial);
        $targetDownloadPath = $this->getInstallPath($target);
        if ($targetDownloadPath !== $initialDownloadPath) {
            // if the target and initial dirs intersect, we force a remove + install
            // to avoid the rename wiping the target dir as part of the initial dir cleanup
            if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath
                || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath
            ) {
                $this->removeCode($initial);
                $this->installCode($target);

                return;
            }

            $this->filesystem->rename($initialDownloadPath, $targetDownloadPath);
        }
        $this->downloadManager->update($initial, $target, $targetDownloadPath);
    }

    protected function removeCode(PackageInterface $package)
    {
        $downloadPath = $this->getPackageBasePath($package);
        $this->downloadManager->remove($package, $downloadPath);
    }

    protected function initializeVendorDir()
    {
        $this->filesystem->ensureDirectoryExists($this->vendorDir);
        $this->vendorDir = realpath($this->vendorDir);
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Installer for plugin packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginInstaller extends LibraryInstaller
{
    private $installationManager;

    /**
     * Initializes Plugin installer.
     *
     * @param IOInterface $io
     * @param Composer    $composer
     */
    public function __construct(IOInterface $io, Composer $composer)
    {
        parent::__construct($io, $composer, 'composer-plugin');
        $this->installationManager = $composer->getInstallationManager();
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }

        parent::install($repo, $package);
        try {
            $this->composer->getPluginManager()->registerPackage($package, true);
        } catch (\Exception $e) {
            // Rollback installation
            $this->io->writeError('Plugin installation failed ('.$e->getMessage().'), rolling back');
            parent::uninstall($repo, $package);
            throw $e;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $extra = $target->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }

        parent::update($repo, $initial, $target);
        $this->composer->getPluginManager()->registerPackage($target, true);
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;

/**
 * Utility to handle installation of package "bin"/binaries for PEAR packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PearBinaryInstaller extends BinaryInstaller
{
    private $installer;
    private $vendorDir;

    /**
     * @param IOInterface   $io
     * @param string        $binDir
     * @param string        $vendorDir
     * @param string        $binCompat
     * @param Filesystem    $filesystem
     * @param PearInstaller $installer
     */
    public function __construct(IOInterface $io, $binDir, $vendorDir, $binCompat, Filesystem $filesystem, PearInstaller $installer)
    {
        parent::__construct($io, $binDir, $binCompat, $filesystem);
        $this->installer = $installer;
        $this->vendorDir = $vendorDir;
    }

    protected function getBinaries(PackageInterface $package)
    {
        $binariesPath = $this->installer->getInstallPath($package) . '/bin/';
        $binaries = array();
        if (file_exists($binariesPath)) {
            foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO) as $fileName => $value) {
                if (!$value->isDir()) {
                    $binaries[] = 'bin/'.$fileName;
                }
            }
        }

        return $binaries;
    }

    protected function initializeBinDir()
    {
        parent::initializeBinDir();
        file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode());
        @chmod($this->binDir.'/composer-php', 0777 & ~umask());
        file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode());
        @chmod($this->binDir.'/composer-php.bat', 0777 & ~umask());
    }

    protected function generateWindowsProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        if ('.bat' === substr($bin, -4)) {
            $caller = 'call';
        } else {
            $handle = fopen($bin, 'r');
            $line = fgets($handle);
            fclose($handle);
            if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
                $caller = trim($match[1]);
            } else {
                $caller = 'php';
            }

            if ($caller === 'php') {
                return "@echo off\r\n".
                    "pushd .\r\n".
                    "cd %~dp0\r\n".
                    "set PHP_PROXY=%CD%\\composer-php.bat\r\n".
                    "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n".
                    "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
                    "popd\r\n".
                    "%PHP_PROXY% \"%BIN_TARGET%\" %*\r\n";
            }
        }

        return "@echo off\r\n".
            "pushd .\r\n".
            "cd %~dp0\r\n".
            "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n".
            "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
            "popd\r\n".
            $caller." \"%BIN_TARGET%\" %*\r\n";
    }

    private function generateWindowsPhpProxyCode()
    {
        $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);

        return
            "@echo off\r\n" .
            "setlocal enabledelayedexpansion\r\n" .
            "set BIN_DIR=%~dp0\r\n" .
            "set VENDOR_DIR=%BIN_DIR%\\".$binToVendor."\r\n" .
            "set DIRS=.\r\n" .
            "FOR /D %%V IN (%VENDOR_DIR%\\*) DO (\r\n" .
            "    FOR /D %%P IN (%%V\\*) DO (\r\n" .
            "        set DIRS=!DIRS!;%%~fP\r\n" .
            "    )\r\n" .
            ")\r\n" .
            "php.exe -d include_path=!DIRS! %*\r\n";
    }

    private function generateUnixyPhpProxyCode()
    {
        $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);

        return
            "#!/usr/bin/env sh\n".
            "SRC_DIR=`pwd`\n".
            "BIN_DIR=`dirname $0`\n".
            "VENDOR_DIR=\$BIN_DIR/".escapeshellarg($binToVendor)."\n".
            "DIRS=\"\"\n".
            "for vendor in \$VENDOR_DIR/*; do\n".
            "    if [ -d \"\$vendor\" ]; then\n".
            "        for package in \$vendor/*; do\n".
            "            if [ -d \"\$package\" ]; then\n".
            "                DIRS=\"\${DIRS}:\${package}\"\n".
            "            fi\n".
            "        done\n".
            "    fi\n".
            "done\n".
            "php -d include_path=\".\$DIRS\" $@\n";
    }
}
<?php

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

namespace Composer\Util;

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Filesystem
{
    private $processExecutor;

    public function __construct(ProcessExecutor $executor = null)
    {
        $this->processExecutor = $executor ?: new ProcessExecutor();
    }

    public function remove($file)
    {
        if (is_dir($file)) {
            return $this->removeDirectory($file);
        }

        if (file_exists($file)) {
            return $this->unlink($file);
        }

        return false;
    }

    /**
     * Checks if a directory is empty
     *
     * @param  string $dir
     * @return bool
     */
    public function isDirEmpty($dir)
    {
        $finder = Finder::create()
            ->ignoreVCS(false)
            ->ignoreDotFiles(false)
            ->depth(0)
            ->in($dir);

        return count($finder) === 0;
    }

    public function emptyDirectory($dir, $ensureDirectoryExists = true)
    {
        if (file_exists($dir) && is_link($dir)) {
            $this->unlink($dir);
        }

        if ($ensureDirectoryExists) {
            $this->ensureDirectoryExists($dir);
        }

        if (is_dir($dir)) {
            $finder = Finder::create()
                ->ignoreVCS(false)
                ->ignoreDotFiles(false)
                ->depth(0)
                ->in($dir);

            foreach ($finder as $path) {
                $this->remove((string) $path);
            }
        }
    }

    /**
     * Recursively remove a directory
     *
     * Uses the process component if proc_open is enabled on the PHP
     * installation.
     *
     * @param  string            $directory
     * @throws \RuntimeException
     * @return bool
     */
    public function removeDirectory($directory)
    {
        if ($this->isSymlinkedDirectory($directory)) {
            return $this->unlinkSymlinkedDirectory($directory);
        }

        if ($this->isJunction($directory)) {
            return $this->removeJunction($directory);
        }

        if (is_link($directory)) {
            return unlink($directory);
        }

        if (!file_exists($directory) || !is_dir($directory)) {
            return true;
        }

        if (preg_match('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) {
            throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.');
        }

        if (!function_exists('proc_open')) {
            return $this->removeDirectoryPhp($directory);
        }

        if (Platform::isWindows()) {
            $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory)));
        } else {
            $cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory));
        }

        $result = $this->getProcess()->execute($cmd, $output) === 0;

        // clear stat cache because external processes aren't tracked by the php stat cache
        clearstatcache();

        if ($result && !file_exists($directory)) {
            return true;
        }

        return $this->removeDirectoryPhp($directory);
    }

    /**
     * Recursively delete directory using PHP iterators.
     *
     * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
     * before directories, creating a single non-recursive loop
     * to delete files/directories in the correct order.
     *
     * @param  string $directory
     * @return bool
     */
    public function removeDirectoryPhp($directory)
    {
        try {
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            // re-try once after clearing the stat cache if it failed as it
            // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009
            clearstatcache();
            usleep(100000);
            if (!is_dir($directory)) {
                return true;
            }
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        }
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($ri as $file) {
            if ($file->isDir()) {
                $this->rmdir($file->getPathname());
            } else {
                $this->unlink($file->getPathname());
            }
        }

        return $this->rmdir($directory);
    }

    public function ensureDirectoryExists($directory)
    {
        if (!is_dir($directory)) {
            if (file_exists($directory)) {
                throw new \RuntimeException(
                    $directory.' exists and is not a directory.'
                );
            }
            if (!@mkdir($directory, 0777, true)) {
                throw new \RuntimeException(
                    $directory.' does not exist and could not be created.'
                );
            }
        }
    }

    /**
     * Attempts to unlink a file and in case of failure retries after 350ms on windows
     *
     * @param  string            $path
     * @throws \RuntimeException
     * @return bool
     */
    public function unlink($path)
    {
        $unlinked = @$this->unlinkImplementation($path);
        if (!$unlinked) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $unlinked = @$this->unlinkImplementation($path);
            }

            if (!$unlinked) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . @$error['message'];
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Attempts to rmdir a file and in case of failure retries after 350ms on windows
     *
     * @param  string            $path
     * @throws \RuntimeException
     * @return bool
     */
    public function rmdir($path)
    {
        $deleted = @rmdir($path);
        if (!$deleted) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $deleted = @rmdir($path);
            }

            if (!$deleted) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . @$error['message'];
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Copy then delete is a non-atomic version of {@link rename}.
     *
     * Some systems can't rename and also don't have proc_open,
     * which requires this solution.
     *
     * @param string $source
     * @param string $target
     */
    public function copyThenRemove($source, $target)
    {
        $this->copy($source, $target);
        if (!is_dir($source)) {
            $this->unlink($source);

            return;
        }

        $this->removeDirectoryPhp($source);
    }

    /**
     * Copies a file or directory from $source to $target.
     *
     * @param string $source
     * @param string $target
     * @return bool
     */
    public function copy($source, $target)
    {
        if (!is_dir($source)) {
            return copy($source, $target);
        }

        $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
        $this->ensureDirectoryExists($target);

        $result = true;
        foreach ($ri as $file) {
            $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
            if ($file->isDir()) {
                $this->ensureDirectoryExists($targetPath);
            } else {
                $result = $result && copy($file->getPathname(), $targetPath);
            }
        }

        return $result;
    }

    public function rename($source, $target)
    {
        if (true === @rename($source, $target)) {
            return;
        }

        if (!function_exists('proc_open')) {
            $this->copyThenRemove($source, $target);
            
            return;
        }

        if (Platform::isWindows()) {
            // Try to copy & delete - this is a workaround for random "Access denied" errors.
            $command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
            $result = $this->processExecutor->execute($command, $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                $this->remove($source);

                return;
            }
        } else {
            // We do not use PHP's "rename" function here since it does not support
            // the case where $source, and $target are located on different partitions.
            $command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
            $result = $this->processExecutor->execute($command, $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                return;
            }
        }

        $this->copyThenRemove($source, $target);
    }

    /**
     * Returns the shortest path from $from to $to
     *
     * @param  string                    $from
     * @param  string                    $to
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPath($from, $to, $directories = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = lcfirst($this->normalizePath($from));
        $to = lcfirst($this->normalizePath($to));

        if ($directories) {
            $from = rtrim($from, '/') . '/dummy_file';
        }

        if (dirname($from) === dirname($to)) {
            return './'.basename($to);
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath)) {
            $commonPath = strtr(dirname($commonPath), '\\', '/');
        }

        if (0 !== strpos($from, $commonPath) || '/' === $commonPath) {
            return $to;
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/');
        $commonPathCode = str_repeat('../', $sourcePathDepth);

        return ($commonPathCode . substr($to, strlen($commonPath))) ?: './';
    }

    /**
     * Returns PHP code that, when executed in $from, will return the path to $to
     *
     * @param  string                    $from
     * @param  string                    $to
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $staticCode
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPathCode($from, $to, $directories = false, $staticCode = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = lcfirst($this->normalizePath($from));
        $to = lcfirst($this->normalizePath($to));

        if ($from === $to) {
            return $directories ? '__DIR__' : '__FILE__';
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
            $commonPath = strtr(dirname($commonPath), '\\', '/');
        }

        if (0 !== strpos($from, $commonPath) || '/' === $commonPath || '.' === $commonPath) {
            return var_export($to, true);
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        if (strpos($to, $from.'/') === 0) {
            return '__DIR__ . '.var_export(substr($to, strlen($from)), true);
        }
        $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/') + $directories;
        if ($staticCode) {
            $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'";
        } else {
            $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth);
        }
        $relTarget = substr($to, strlen($commonPath));

        return $commonPathCode . (strlen($relTarget) ? '.' . var_export('/' . $relTarget, true) : '');
    }

    /**
     * Checks if the given path is absolute
     *
     * @param  string $path
     * @return bool
     */
    public function isAbsolutePath($path)
    {
        return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\';
    }

    /**
     * Returns size of a file or directory specified by path. If a directory is
     * given, it's size will be computed recursively.
     *
     * @param  string            $path Path to the file or directory
     * @throws \RuntimeException
     * @return int
     */
    public function size($path)
    {
        if (!file_exists($path)) {
            throw new \RuntimeException("$path does not exist.");
        }
        if (is_dir($path)) {
            return $this->directorySize($path);
        }

        return filesize($path);
    }

    /**
     * Normalize a path. This replaces backslashes with slashes, removes ending
     * slash and collapses redundant separators and up-level references.
     *
     * @param  string $path Path to the file or directory
     * @return string
     */
    public function normalizePath($path)
    {
        $parts = array();
        $path = strtr($path, '\\', '/');
        $prefix = '';
        $absolute = false;

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, strlen($prefix));
        }

        if (substr($path, 0, 1) === '/') {
            $absolute = true;
            $path = substr($path, 1);
        }

        $up = false;
        foreach (explode('/', $path) as $chunk) {
            if ('..' === $chunk && ($absolute || $up)) {
                array_pop($parts);
                $up = !(empty($parts) || '..' === end($parts));
            } elseif ('.' !== $chunk && '' !== $chunk) {
                $parts[] = $chunk;
                $up = '..' !== $chunk;
            }
        }

        return $prefix.($absolute ? '/' : '').implode('/', $parts);
    }

    /**
     * Return if the given path is local
     *
     * @param  string $path
     * @return bool
     */
    public static function isLocalPath($path)
    {
        return (bool) preg_match('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
    }

    public static function getPlatformPath($path)
    {
        if (Platform::isWindows()) {
            $path = preg_replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path);
        }

        return preg_replace('{^file://}i', '', $path);
    }

    protected function directorySize($directory)
    {
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        $size = 0;
        foreach ($ri as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    protected function getProcess()
    {
        return $this->processExecutor;
    }

    /**
     * delete symbolic link implementation (commonly known as "unlink()")
     *
     * symbolic links on windows which link to directories need rmdir instead of unlink
     *
     * @param string $path
     *
     * @return bool
     */
    private function unlinkImplementation($path)
    {
        if (Platform::isWindows() && is_dir($path) && is_link($path)) {
            return rmdir($path);
        }

        return unlink($path);
    }

    /**
     * Creates a relative symlink from $link to $target
     *
     * @param  string $target The path of the binary file to be symlinked
     * @param  string $link   The path where the symlink should be created
     * @return bool
     */
    public function relativeSymlink($target, $link)
    {
        $cwd = getcwd();

        $relativePath = $this->findShortestPath($link, $target);
        chdir(dirname($link));
        $result = @symlink($relativePath, $link);

        chdir($cwd);

        return $result;
    }

    /**
     * return true if that directory is a symlink.
     *
     * @param string $directory
     *
     * @return bool
     */
    public function isSymlinkedDirectory($directory)
    {
        if (!is_dir($directory)) {
            return false;
        }

        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return is_link($resolved);
    }

    /**
     * @param string $directory
     *
     * @return bool
     */
    private function unlinkSymlinkedDirectory($directory)
    {
        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return $this->unlink($resolved);
    }

    /**
     * resolve pathname to symbolic link of a directory
     *
     * @param string $pathname directory path to resolve
     *
     * @return string resolved path to symbolic link or original pathname (unresolved)
     */
    private function resolveSymlinkedDirectorySymlink($pathname)
    {
        if (!is_dir($pathname)) {
            return $pathname;
        }

        $resolved = rtrim($pathname, '/');

        if (!strlen($resolved)) {
            return $pathname;
        }

        return $resolved;
    }

    /**
     * Creates an NTFS junction.
     *
     * @param string $target
     * @param string $junction
     */
    public function junction($target, $junction)
    {
        if (!Platform::isWindows()) {
            throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__));
        }
        if (!is_dir($target)) {
            throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target);
        }
        $cmd = sprintf(
            'mklink /J %s %s',
            ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)),
            ProcessExecutor::escape(realpath($target))
        );
        if ($this->getProcess()->execute($cmd, $output) !== 0) {
            throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
        }
        clearstatcache(true, $junction);
    }

    /**
     * Returns whether the target directory is a Windows NTFS Junction.
     *
     * We test if the path is a directory and not an ordinary link, then check
     * that the mode value returned from lstat (which gives the status of the
     * link itself) is not a directory, by replicating the POSIX S_ISDIR test.
     *
     * This logic works because PHP does not set the mode value for a junction,
     * since there is no universal file type flag for it. Unfortunately an
     * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a
     * random value to be returned. See https://bugs.php.net/bug.php?id=77552
     *
     * If this random value passes the S_ISDIR test, then a junction will not be
     * detected and a recursive delete operation could lead to loss of data in
     * the target directory. Note that Windows rmdir can handle this situation
     * and will only delete the junction (from Windows 7 onwards).
     *
     * @param  string $junction Path to check.
     * @return bool
     */
    public function isJunction($junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // Important to clear all caches first
        clearstatcache(true, $junction);

        if (!is_dir($junction) || is_link($junction)) {
            return false;
        }

        $stat = lstat($junction);

        // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
        return $stat ? 0x4000 !== ($stat['mode'] & 0xF000) : false;
    }

    /**
     * Removes a Windows NTFS junction.
     *
     * @param  string $junction
     * @return bool
     */
    public function removeJunction($junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }
        $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR);
        if (!$this->isJunction($junction)) {
            throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
        }

        return $this->rmdir($junction);
    }
}
<?php


namespace Composer\Util;

use Composer\Package\Link;
use Composer\Package\PackageInterface;

class PackageSorter
{
    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight retain the original order
     *
     * @param  array $packages
     * @return array
     */
    public static function sortPackages(array $packages) {
        $usageList = array();

        foreach ($packages as $package) { /** @var PackageInterface $package */
            foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */
                $target = $link->getTarget();
                $usageList[$target][] = $package->getName();
            }
        }
        $computing = array();
        $computed = array();
        $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) {
            // reusing computed importance
            if (isset($computed[$name])) {
                return $computed[$name];
            }

            // canceling circular dependency
            if (isset($computing[$name])) {
                return 0;
            }

            $computing[$name] = true;
            $weight = 0;

            if (isset($usageList[$name])) {
                foreach ($usageList[$name] as $user) {
                    $weight -= 1 - $computeImportance($user);
                }
            }

            unset($computing[$name]);
            $computed[$name] = $weight;

            return $weight;
        };

        $weightList = array();

        foreach ($packages as $name => $package) {
            $weight = $computeImportance($name);
            $weightList[$name] = $weight;
        }

        $stable_sort = function (&$array) {
            static $transform, $restore;

            $i = 0;

            if (!$transform) {
                $transform = function (&$v, $k) use (&$i) {
                    $v = array($v, ++$i, $k, $v);
                };

                $restore = function (&$v) {
                    $v = $v[3];
                };
            }

            array_walk($array, $transform);
            asort($array);
            array_walk($array, $restore);
        };

        $stable_sort($weightList);

        $sortedPackages = array();

        foreach (array_keys($weightList) as $name) {
            $sortedPackages[] = $packages[$name];
        }
        return $sortedPackages;
    }
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;
use Symfony\Component\Process\Process;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class Perforce
{
    protected $path;
    protected $p4Depot;
    protected $p4Client;
    protected $p4User;
    protected $p4Password;
    protected $p4Port;
    protected $p4Stream;
    protected $p4ClientSpec;
    protected $p4DepotType;
    protected $p4Branch;
    protected $process;
    protected $uniquePerforceClientName;
    protected $windowsFlag;
    protected $commandResult;

    protected $io;

    protected $filesystem;

    public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows, IOInterface $io)
    {
        $this->windowsFlag = $isWindows;
        $this->p4Port = $port;
        $this->initializePath($path);
        $this->process = $process;
        $this->initialize($repoConfig);
        $this->io = $io;
    }

    public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io)
    {
        return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
    }

    public static function checkServerExists($url, ProcessExecutor $processExecutor)
    {
        $output = null;

        return  0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $output);
    }

    public function initialize($repoConfig)
    {
        $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
        if (!$repoConfig) {
            return;
        }
        if (isset($repoConfig['unique_perforce_client_name'])) {
            $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
        }

        if (isset($repoConfig['depot'])) {
            $this->p4Depot = $repoConfig['depot'];
        }
        if (isset($repoConfig['branch'])) {
            $this->p4Branch = $repoConfig['branch'];
        }
        if (isset($repoConfig['p4user'])) {
            $this->p4User = $repoConfig['p4user'];
        } else {
            $this->p4User = $this->getP4variable('P4USER');
        }
        if (isset($repoConfig['p4password'])) {
            $this->p4Password = $repoConfig['p4password'];
        }
    }

    public function initializeDepotAndBranch($depot, $branch)
    {
        if (isset($depot)) {
            $this->p4Depot = $depot;
        }
        if (isset($branch)) {
            $this->p4Branch = $branch;
        }
    }

    public function generateUniquePerforceClientName()
    {
        return gethostname() . "_" . time();
    }

    public function cleanupClientSpec()
    {
        $client = $this->getClient();
        $task = 'client -d ' . ProcessExecutor::escape($client);
        $useP4Client = false;
        $command = $this->generateP4Command($task, $useP4Client);
        $this->executeCommand($command);
        $clientSpec = $this->getP4ClientSpec();
        $fileSystem = $this->getFilesystem();
        $fileSystem->remove($clientSpec);
    }

    protected function executeCommand($command)
    {
        $this->commandResult = '';

        return $this->process->execute($command, $this->commandResult);
    }

    public function getClient()
    {
        if (!isset($this->p4Client)) {
            $cleanStreamName = str_replace(array('//', '/', '@'), array('', '_', ''), $this->getStream());
            $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
        }

        return $this->p4Client;
    }

    protected function getPath()
    {
        return $this->path;
    }

    public function initializePath($path)
    {
        $this->path = $path;
        $fs = $this->getFilesystem();
        $fs->ensureDirectoryExists($path);
    }

    protected function getPort()
    {
        return $this->p4Port;
    }

    public function setStream($stream)
    {
        $this->p4Stream = $stream;
        $index = strrpos($stream, '/');
        //Stream format is //depot/stream, while non-streaming depot is //depot
        if ($index > 2) {
            $this->p4DepotType = 'stream';
        }
    }

    public function isStream()
    {
        return (strcmp($this->p4DepotType, 'stream') === 0);
    }

    public function getStream()
    {
        if (!isset($this->p4Stream)) {
            if ($this->isStream()) {
                $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
            } else {
                $this->p4Stream = '//' . $this->p4Depot;
            }
        }

        return $this->p4Stream;
    }

    public function getStreamWithoutLabel($stream)
    {
        $index = strpos($stream, '@');
        if ($index === false) {
            return $stream;
        }

        return substr($stream, 0, $index);
    }

    public function getP4ClientSpec()
    {
        return $this->path . '/' . $this->getClient() . '.p4.spec';
    }

    public function getUser()
    {
        return $this->p4User;
    }

    public function setUser($user)
    {
        $this->p4User = $user;
    }

    public function queryP4User()
    {
        $this->getUser();
        if (strlen($this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->getP4variable('P4USER');
        if (strlen($this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->io->ask('Enter P4 User:');
        if ($this->windowsFlag) {
            $command = 'p4 set P4USER=' . $this->p4User;
        } else {
            $command = 'export P4USER=' . $this->p4User;
        }
        $this->executeCommand($command);
    }

    protected function getP4variable($name)
    {
        if ($this->windowsFlag) {
            $command = 'p4 set';
            $this->executeCommand($command);
            $result = trim($this->commandResult);
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $fields = explode('=', $line);
                if (strcmp($name, $fields[0]) == 0) {
                    $index = strpos($fields[1], ' ');
                    if ($index === false) {
                        $value = $fields[1];
                    } else {
                        $value = substr($fields[1], 0, $index);
                    }
                    $value = trim($value);

                    return $value;
                }
            }

            return null;
        }

        $command = 'echo $' . $name;
        $this->executeCommand($command);
        $result = trim($this->commandResult);

        return $result;
    }

    public function queryP4Password()
    {
        if (isset($this->p4Password)) {
            return $this->p4Password;
        }
        $password = $this->getP4variable('P4PASSWD');
        if (strlen($password) <= 0) {
            $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
        }
        $this->p4Password = $password;

        return $password;
    }

    public function generateP4Command($command, $useClient = true)
    {
        $p4Command = 'p4 ';
        $p4Command .= '-u ' . $this->getUser() . ' ';
        if ($useClient) {
            $p4Command .= '-c ' . $this->getClient() . ' ';
        }
        $p4Command = $p4Command . '-p ' . $this->getPort() . ' ' . $command;

        return $p4Command;
    }

    public function isLoggedIn()
    {
        $command = $this->generateP4Command('login -s', false);
        $exitCode = $this->executeCommand($command);
        if ($exitCode) {
            $errorOutput = $this->process->getErrorOutput();
            $index = strpos($errorOutput, $this->getUser());
            if ($index === false) {
                $index = strpos($errorOutput, 'p4');
                if ($index === false) {
                    return false;
                }
                throw new \Exception('p4 command not found in path: ' . $errorOutput);
            }
            throw new \Exception('Invalid user name: ' . $this->getUser());
        }

        return true;
    }

    public function connectClient()
    {
        $p4CreateClientCommand = $this->generateP4Command(
            'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec())
        );
        $this->executeCommand($p4CreateClientCommand);
    }

    public function syncCodeBase($sourceReference)
    {
        $prevDir = getcwd();
        chdir($this->path);
        $p4SyncCommand = $this->generateP4Command('sync -f ');
        if (null !== $sourceReference) {
            $p4SyncCommand .= '@' . $sourceReference;
        }
        $this->executeCommand($p4SyncCommand);
        chdir($prevDir);
    }

    public function writeClientSpecToFile($spec)
    {
        fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
        fwrite($spec, 'Owner:  ' . $this->getUser() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Description:' . PHP_EOL);
        fwrite($spec, '  Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Options:  noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'SubmitOptions:  revertunchanged' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'LineEnd:  local' . PHP_EOL . PHP_EOL);
        if ($this->isStream()) {
            fwrite($spec, 'Stream:' . PHP_EOL);
            fwrite($spec, '  ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
        } else {
            fwrite(
                $spec,
                'View:  ' . $this->getStream() . '/...  //' . $this->getClient() . '/... ' . PHP_EOL
            );
        }
    }

    public function writeP4ClientSpec()
    {
        $clientSpec = $this->getP4ClientSpec();
        $spec = fopen($clientSpec, 'w');
        try {
            $this->writeClientSpecToFile($spec);
        } catch (\Exception $e) {
            fclose($spec);
            throw $e;
        }
        fclose($spec);
    }

    protected function read($pipe, $name)
    {
        if (feof($pipe)) {
            return;
        }
        $line = fgets($pipe);
        while ($line !== false) {
            $line = fgets($pipe);
        }
    }

    public function windowsLogin($password)
    {
        $command = $this->generateP4Command(' login -a');

        // TODO in v3 generate command as an array
        if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
            $process = Process::fromShellCommandline($command, null, null, $password);
        } else {
            $process = new Process($command, null, null, $password);
        }

        return $process->run();
    }

    public function p4Login()
    {
        $this->queryP4User();
        if (!$this->isLoggedIn()) {
            $password = $this->queryP4Password();
            if ($this->windowsFlag) {
                $this->windowsLogin($password);
            } else {
                $command = 'echo ' . ProcessExecutor::escape($password)  . ' | ' . $this->generateP4Command(' login -a', false);
                $exitCode = $this->executeCommand($command);
                $result = trim($this->commandResult);
                if ($exitCode) {
                    throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
                }
            }
        }
    }

    public function getComposerInformation($identifier)
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return;
        }

        return json_decode($composerFileContent, true);
    }

    public function getFileContent($file, $identifier)
    {
        $path = $this->getFilePath($file, $identifier);

        $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path));
        $this->executeCommand($command);
        $result = $this->commandResult;

        if (!trim($result)) {
            return null;
        }

        return $result;
    }

    public function getFilePath($file, $identifier)
    {
        $index = strpos($identifier, '@');
        if ($index === false) {
            $path = $identifier. '/' . $file;

            return $path;
        }

        $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index);
        $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $index2 = strpos($result, 'no such file(s).');
        if ($index2 === false) {
            $index3 = strpos($result, 'change');
            if ($index3 !== false) {
                $phrase = trim(substr($result, $index3));
                $fields = explode(' ', $phrase);

                return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1];
            }
        }

        return null;
    }

    public function getBranches()
    {
        $possibleBranches = array();
        if (!$this->isStream()) {
            $possibleBranches[$this->p4Branch] = $this->getStream();
        } else {
            $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...'));
            $this->executeCommand($command);
            $result = $this->commandResult;
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $resBits = explode(' ', $line);
                if (count($resBits) > 4) {
                    $branch = preg_replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
                    $possibleBranches[$branch] = $resBits[1];
                }
            }
        }
        $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $lastCommit = $resArray[0];
        $lastCommitArr = explode(' ', $lastCommit);
        $lastCommitNum = $lastCommitArr[1];

        $branches = array('master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum);

        return $branches;
    }

    public function getTags()
    {
        $command = $this->generateP4Command('labels');
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $tags = array();
        foreach ($resArray as $line) {
            if (strpos($line, 'Label') !== false) {
                $fields = explode(' ', $line);
                $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
            }
        }

        return $tags;
    }

    public function checkStream()
    {
        $command = $this->generateP4Command('depots', false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        foreach ($resArray as $line) {
            if (strpos($line, 'Depot') !== false) {
                $fields = explode(' ', $line);
                if (strcmp($this->p4Depot, $fields[1]) === 0) {
                    $this->p4DepotType = $fields[3];

                    return $this->isStream();
                }
            }
        }

        return false;
    }

    /**
     * @param string $reference
     * @return mixed|null
     */
    protected function getChangeList($reference)
    {
        $index = strpos($reference, '@');
        if ($index === false) {
            return null;
        }
        $label = substr($reference, $index);
        $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label));
        $this->executeCommand($command);
        $changes = $this->commandResult;
        if (strpos($changes, 'Change') !== 0) {
            return null;
        }
        $fields = explode(' ', $changes);

        return $fields[1];
    }

    /**
     * @param string $fromReference
     * @param string $toReference
     * @return mixed|null
     */
    public function getCommitLogs($fromReference, $toReference)
    {
        $fromChangeList = $this->getChangeList($fromReference);
        if ($fromChangeList === null) {
            return null;
        }
        $toChangeList = $this->getChangeList($toReference);
        if ($toChangeList === null) {
            return null;
        }
        $index = strpos($fromReference, '@');
        $main = substr($fromReference, 0, $index) . '/...';
        $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList));
        $this->executeCommand($command);

        return $this->commandResult;
    }

    public function getFilesystem()
    {
        if (empty($this->filesystem)) {
            $this->filesystem = new Filesystem($this->process);
        }

        return $this->filesystem;
    }

    public function setFilesystem(Filesystem $fs)
    {
        $this->filesystem = $fs;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Paul Wenke <wenke.paul@gmail.com>
 */
class Bitbucket
{
    private $io;
    private $config;
    private $process;
    private $remoteFilesystem;
    private $token = array();
    private $time;

    const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';

    /**
     * Constructor.
     *
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     * @param int              $time             Timestamp, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
        $this->time = $time;
    }

    /**
     * @return string
     */
    public function getToken()
    {
        if (!isset($this->token['access_token'])) {
            return '';
        }

        return $this->token['access_token'];
    }

    /**
     * Attempts to authorize a Bitbucket domain via OAuth
     *
     * @param  string $originUrl The host this Bitbucket instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth($originUrl)
    {
        if ($originUrl !== 'bitbucket.org') {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));

            return true;
        }

        return false;
    }

    /**
     * @param  string $originUrl
     * @return bool
     */
    private function requestAccessToken($originUrl)
    {
        try {
            $json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array(
                'retry-auth-failure' => false,
                'http' => array(
                    'method' => 'POST',
                    'content' => 'grant_type=client_credentials',
                ),
            ));

            $this->token = json_decode($json, true);
        } catch (TransportException $e) {
            if ($e->getCode() === 400) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('This can have two reasons:');
                $this->io->writeError('1. You are authenticating with a bitbucket username/password combination');
                $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url');

                return false;
            } elseif (in_array($e->getCode(), array(403, 401))) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

                return false;
            }

            throw $e;
        }

        return true;
    }

    /**
     * Authorizes a Bitbucket domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this Bitbucket instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively($originUrl, $message = null)
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/';
        $this->io->writeError(sprintf('Follow the instructions on %s', $url));
        $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)');

        $consumerKey = trim($this->io->askAndHideAnswer('Consumer Key (hidden): '));

        if (!$consumerKey) {
            $this->io->writeError('<warning>No consumer key given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $consumerSecret = trim($this->io->askAndHideAnswer('Consumer Secret (hidden): '));

        if (!$consumerSecret) {
            $this->io->writeError('<warning>No consumer secret given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);

        if (!$this->requestAccessToken($originUrl)) {
            return false;
        }

        // store value in user config
        $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret);

        // Remove conflicting basic auth credentials (if available)
        $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl);

        $this->io->writeError('<info>Consumer stored successfully.</info>');

        return true;
    }

    /**
     * Retrieves an access token from Bitbucket.
     *
     * @param  string $originUrl
     * @param  string $consumerKey
     * @param  string $consumerSecret
     * @return string
     */
    public function requestToken($originUrl, $consumerKey, $consumerSecret)
    {
        if (!empty($this->token) || $this->getTokenFromConfig($originUrl)) {
            return $this->token['access_token'];
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
        if (!$this->requestAccessToken($originUrl)) {
            return '';
        }

        $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret);

        return $this->token['access_token'];
    }

    /**
     * Store the new/updated credentials to the configuration
     * @param string $originUrl
     * @param string $consumerKey
     * @param string $consumerSecret
     */
    private function storeInAuthConfig($originUrl, $consumerKey, $consumerSecret)
    {
        $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);

        $time = null === $this->time ? time() : $this->time;
        $consumer = array(
            "consumer-key" => $consumerKey,
            "consumer-secret" => $consumerSecret,
            "access-token" => $this->token['access_token'],
            "access-token-expiration" => $time + $this->token['expires_in'],
        );

        $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
    }

    /**
     * @param  string $originUrl
     * @return bool
     */
    private function getTokenFromConfig($originUrl)
    {
        $authConfig = $this->config->get('bitbucket-oauth');

        if (
            !isset($authConfig[$originUrl]['access-token'])
            || !isset($authConfig[$originUrl]['access-token-expiration'])
            || time() > $authConfig[$originUrl]['access-token-expiration']
        ) {
            return false;
        }

        $this->token = array(
            'access_token' => $authConfig[$originUrl]['access-token'],
        );

        return true;
    }
}
<?php

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

namespace Composer\Util;

/**
 * Composer mirror utilities
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerMirror
{
    public static function processUrl($mirrorUrl, $packageName, $version, $reference, $type)
    {
        if ($reference) {
            $reference = preg_match('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : md5($reference);
        }
        $version = strpos($version, '/') === false ? $version : md5($version);

        return str_replace(
            array('%package%', '%version%', '%reference%', '%type%'),
            array($packageName, $version, $reference, $type),
            $mirrorUrl
        );
    }

    public static function processGitUrl($mirrorUrl, $packageName, $url, $type)
    {
        if (preg_match('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) {
            $url = 'gh-'.$match[1].'/'.$match[2];
        } elseif (preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) {
            $url = 'bb-'.$match[1].'/'.$match[2];
        } else {
            $url = preg_replace('{[^a-z0-9_.-]}i', '-', trim($url, '/'));
        }

        return str_replace(
            array('%package%', '%normalizedUrl%', '%type%'),
            array($packageName, $url, $type),
            $mirrorUrl
        );
    }

    public static function processHgUrl($mirrorUrl, $packageName, $url, $type)
    {
        return self::processGitUrl($mirrorUrl, $packageName, $url, $type);
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AuthHelper
{
    protected $io;
    protected $config;

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

    public function storeAuth($originUrl, $storeAuth)
    {
        $store = false;
        $configSource = $this->config->getAuthConfigSource();
        if ($storeAuth === true) {
            $store = $configSource;
        } elseif ($storeAuth === 'prompt') {
            $answer = $this->io->askAndValidate(
                'Do you want to store credentials for '.$originUrl.' in '.$configSource->getName().' ? [Yn] ',
                function ($value) {
                    $input = strtolower(substr(trim($value), 0, 1));
                    if (in_array($input, array('y','n'))) {
                        return $input;
                    }
                    throw new \RuntimeException('Please answer (y)es or (n)o');
                },
                null,
                'y'
            );

            if ($answer === 'y') {
                $store = $configSource;
            }
        }
        if ($store) {
            $store->addConfigSetting(
                'http-basic.'.$originUrl,
                $this->io->getAuthentication($originUrl)
            );
        }
    }
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Util\Platform;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessUtils;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class ProcessExecutor
{
    protected static $timeout = 300;

    protected $captureOutput;
    protected $errorOutput;
    protected $io;

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

    /**
     * runs a process on the commandline
     *
     * @param  string $command the command to execute
     * @param  mixed  $output  the output will be written into this var if passed by ref
     *                         if a callable is passed it will be used as output handler
     * @param  string $cwd     the working directory
     * @return int    statuscode
     */
    public function execute($command, &$output = null, $cwd = null)
    {
        if ($this->io && $this->io->isDebug()) {
            $safeCommand = preg_replace_callback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', function ($m) {
                // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that
                if (preg_match('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) {
                    return '://***:***@';
                }

                return '://'.$m['user'].':***@';
            }, $command);
            $safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
            $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
        }

        // make sure that null translate to the proper directory in case the dir is a symlink
        // and we call a git command, because msysgit does not handle symlinks properly
        if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) {
            $cwd = realpath(getcwd());
        }

        if (null !== $cwd && !is_dir($cwd)) {
            throw new \RuntimeException('The given CWD for the process does not exist: '.$cwd);
        }

        $this->captureOutput = func_num_args() > 1;
        $this->errorOutput = null;

        // TODO in v3, commands should be passed in as arrays of cmd + args
        if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
            $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout());
        } else {
            $process = new Process($command, $cwd, null, null, static::getTimeout());
        }

        $callback = is_callable($output) ? $output : array($this, 'outputHandler');
        $process->run($callback);

        if ($this->captureOutput && !is_callable($output)) {
            $output = $process->getOutput();
        }

        $this->errorOutput = $process->getErrorOutput();

        return $process->getExitCode();
    }

    public function splitLines($output)
    {
        $output = trim($output);

        return ((string) $output === '') ? array() : preg_split('{\r?\n}', $output);
    }

    /**
     * Get any error output from the last command
     *
     * @return string
     */
    public function getErrorOutput()
    {
        return $this->errorOutput;
    }

    public function outputHandler($type, $buffer)
    {
        if ($this->captureOutput) {
            return;
        }

        if (null === $this->io) {
            echo $buffer;

            return;
        }

        if (method_exists($this->io, 'writeRaw')) {
            if (Process::ERR === $type) {
                $this->io->writeErrorRaw($buffer, false);
            } else {
                $this->io->writeRaw($buffer, false);
            }
        } else {
            if (Process::ERR === $type) {
                $this->io->writeError($buffer, false);
            } else {
                $this->io->write($buffer, false);
            }
        }
    }

    public static function getTimeout()
    {
        return static::$timeout;
    }

    public static function setTimeout($timeout)
    {
        static::$timeout = $timeout;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    public static function escape($argument)
    {
        return self::escapeArgument($argument);
    }

    /**
     * Escapes a string to be used as a shell argument for Symfony Process.
     *
     * This method expects cmd.exe to be started with the /V:ON option, which
     * enables delayed environment variable expansion using ! as the delimiter.
     * If this is not the case, any escaped ^^!var^^! will be transformed to
     * ^!var^! and introduce two unintended carets.
     *
     * Modified from https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $argument
     *
     * @return string
     */
    private static function escapeArgument($argument)
    {
        if ('' === ($argument = (string) $argument)) {
            return escapeshellarg($argument);
        }

        if (!Platform::isWindows()) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }

        // New lines break cmd.exe command parsing
        $argument = strtr($argument, "\n", ' ');

        $quote = strpbrk($argument, " \t") !== false;
        $argument = preg_replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes);
        $meta = $dquotes || preg_match('/%[^%]+%|![^!]+!/', $argument);

        if (!$meta && !$quote) {
            $quote = strpbrk($argument, '^&|<>()') !== false;
        }

        if ($quote) {
            $argument = '"'.preg_replace('/(\\\\*)$/', '$1$1', $argument).'"';
        }

        if ($meta) {
            $argument = preg_replace('/(["^&|<>()%])/', '^$1', $argument);
            $argument = preg_replace('/(!)/', '^^$1', $argument);
        }

        return $argument;
    }
}
<?php

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

namespace Composer\Util;

/**
 * Temporarily suppress PHP error reporting, usually warnings and below.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Silencer
{
    /**
     * @var int[] Unpop stack
     */
    private static $stack = array();

    /**
     * Suppresses given mask or errors.
     *
     * @param  int|null $mask Error levels to suppress, default value NULL indicates all warnings and below.
     * @return int      The old error reporting level.
     */
    public static function suppress($mask = null)
    {
        if (!isset($mask)) {
            $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT;
        }
        $old = error_reporting();
        self::$stack[] = $old;
        error_reporting($old & ~$mask);

        return $old;
    }

    /**
     * Restores a single state.
     */
    public static function restore()
    {
        if (!empty(self::$stack)) {
            error_reporting(array_pop(self::$stack));
        }
    }

    /**
     * Calls a specified function while silencing warnings and below.
     *
     * Future improvement: when PHP requirements are raised add Callable type hint (5.4) and variadic parameters (5.6)
     *
     * @param  callable   $callable Function to execute.
     * @throws \Exception Any exceptions from the callback are rethrown.
     * @return mixed      Return value of the callback.
     */
    public static function call($callable /*, ...$parameters */)
    {
        try {
            self::suppress();
            $result = call_user_func_array($callable, array_slice(func_get_args(), 1));
            self::restore();

            return $result;
        } catch (\Exception $e) {
            // Use a finally block for this when requirements are raised to PHP 5.5
            self::restore();
            throw $e;
        }
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\Composer;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
use Psr\Log\LoggerInterface;

/**
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class RemoteFilesystem
{
    private $io;
    private $config;
    private $scheme;
    private $bytesMax;
    private $originUrl;
    private $fileUrl;
    private $fileName;
    private $retry;
    private $progress;
    private $lastProgress;
    private $options = array();
    private $peerCertificateMap = array();
    private $disableTls = false;
    private $retryAuthFailure;
    private $lastHeaders;
    private $storeAuth;
    private $degradedMode = false;
    private $redirects;
    private $maxRedirects = 20;
    private $displayedOriginAuthentications = array();

    /**
     * Constructor.
     *
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param array       $options    The options
     * @param bool        $disableTls
     */
    public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false)
    {
        $this->io = $io;

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = $this->getTlsDefaults($options);
        } else {
            $this->disableTls = true;
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;
    }

    /**
     * Copy the remote file in local.
     *
     * @param string $originUrl The origin URL
     * @param string $fileUrl   The file URL
     * @param string $fileName  the local filename
     * @param bool   $progress  Display the progression
     * @param array  $options   Additional context options
     *
     * @return bool true
     */
    public function copy($originUrl, $fileUrl, $fileName, $progress = true, $options = array())
    {
        return $this->get($originUrl, $fileUrl, $options, $fileName, $progress);
    }

    /**
     * Get the content.
     *
     * @param string $originUrl The origin URL
     * @param string $fileUrl   The file URL
     * @param bool   $progress  Display the progression
     * @param array  $options   Additional context options
     *
     * @return bool|string The content
     */
    public function getContents($originUrl, $fileUrl, $progress = true, $options = array())
    {
        return $this->get($originUrl, $fileUrl, $options, null, $progress);
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return array Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param array $options
     */
    public function setOptions(array $options)
    {
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * Check is disable TLS.
     *
     * @return bool
     */
    public function isTlsDisabled()
    {
        return $this->disableTls === true;
    }

    /**
     * Returns the headers of the last request
     *
     * @return array
     */
    public function getLastHeaders()
    {
        return $this->lastHeaders;
    }

    /**
     * @param  array       $headers array of returned headers like from getLastHeaders()
     * @param  string      $name    header name (case insensitive)
     * @return string|null
     */
    public function findHeaderValue(array $headers, $name)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) {
                $value = $match[1];
            } elseif (preg_match('{^HTTP/}i', $header)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we reset the flag when a new response is being parsed as we are only interested in the last response
                $value = null;
            }
        }

        return $value;
    }

    /**
     * @param  array    $headers array of returned headers like from getLastHeaders()
     * @return int|null
     */
    public function findStatusCode(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^HTTP/\S+ (\d+)}i', $header, $match)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = (int) $match[1];
            }
        }

        return $value;
    }

    /**
     * @param  array       $headers array of returned headers like from getLastHeaders()
     * @return string|null
     */
    public function findStatusMessage(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^HTTP/\S+ \d+}i', $header)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = $header;
            }
        }

        return $value;
    }

    /**
     * Get file content or copy action.
     *
     * @param string $originUrl         The origin URL
     * @param string $fileUrl           The file URL
     * @param array  $additionalOptions context options
     * @param string $fileName          the local filename
     * @param bool   $progress          Display the progression
     *
     * @throws TransportException|\Exception
     * @throws TransportException            When the file could not be downloaded
     *
     * @return bool|string
     */
    protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
    {
        if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
            $originUrl = 'github.com';
        }

        // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
        // is the host without the path, so we look for the registered gitlab-domains matching the host here
        if (
            $this->config
            && is_array($this->config->get('gitlab-domains'))
            && false === strpos($originUrl, '/')
            && !in_array($originUrl, $this->config->get('gitlab-domains'))
        ) {
            foreach ($this->config->get('gitlab-domains') as $gitlabDomain) {
                if (0 === strpos($gitlabDomain, $originUrl)) {
                    $originUrl = $gitlabDomain;
                    break;
                }
            }
            unset($gitlabDomain);
        }

        $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $this->retryAuthFailure = true;
        $this->lastHeaders = array();
        $this->redirects = 1; // The first request counts.

        // capture username/password from URL if there is one
        if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) {
            $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];

            unset($tempAdditionalOptions['retry-auth-failure']);
        }

        $isRedirect = false;
        if (isset($tempAdditionalOptions['redirects'])) {
            $this->redirects = $tempAdditionalOptions['redirects'];
            $isRedirect = true;

            unset($tempAdditionalOptions['redirects']);
        }

        $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
        unset($tempAdditionalOptions);

        $origFileUrl = $fileUrl;

        if (isset($options['github-token'])) {
            // only add the access_token if it is actually a github URL (in case we were redirected to S3)
            if (preg_match('{^https?://api\.github\.com/}', $fileUrl)) {
                $options['http']['header'][] = 'Authorization: token '.$options['github-token'];
            }
            unset($options['github-token']);
        }

        if (isset($options['gitlab-token'])) {
            $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
            unset($options['gitlab-token']);
        }

        if (isset($options['http'])) {
            $options['http']['ignore_errors'] = true;
        }

        if ($this->degradedMode && substr($fileUrl, 0, 26) === 'http://repo.packagist.org/') {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));

        $actualContextOptions = stream_context_get_options($ctx);
        $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $actualContextOptions);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) {
            $this->config->prohibitUrlByConfig($fileUrl, $this->io);
        }

        if ($this->progress && !$isRedirect) {
            $this->io->writeError("Downloading (<comment>connecting...</comment>)", false);
        }

        $errorMessage = '';
        $errorCode = 0;
        $result = false;
        set_error_handler(function ($code, $msg) use (&$errorMessage) {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);

            return true;
        });
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);

            if (!empty($http_response_header[0])) {
                $statusCode = $this->findStatusCode($http_response_header);
                if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    self::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), null, $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null;
            if ($contentLength && Platform::strlen($result) < $contentLength) {
                // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
                $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
                $e->setResponse($result);
                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG);

                throw $e;
            }

            if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) {
                // Emulate fingerprint validation on PHP < 5.6
                $params = stream_context_get_params($ctx);
                $expectedPeerFingerprint = $options['ssl']['peer_fingerprint'];
                $peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']);

                // Constant time compare??!
                if ($expectedPeerFingerprint !== $peerFingerprint) {
                    throw new TransportException('Peer fingerprint did not match');
                }
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && !empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
                $e->setStatusCode($this->findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($result);
            }
            $result = false;
        }
        if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
        }
        restore_error_handler();
        if (isset($e) && !$this->retry) {
            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        $statusCode = null;
        $contentType = null;
        $locationHeader = null;
        if (!empty($http_response_header[0])) {
            $statusCode = $this->findStatusCode($http_response_header);
            $contentType = $this->findHeaderValue($http_response_header, 'content-type');
            $locationHeader = $this->findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip')
            && $contentType && preg_match('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // handle 3xx redirects, 304 Not Modified is excluded
        $hasFollowedRedirect = false;
        if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
            $hasFollowedRedirect = true;
            $result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
        }

        // fail 4xx and 5xx responses and capture the response
        if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
            if (!$this->retry) {
                if ($this->progress && !$this->retry && !$isRedirect) {
                    $this->io->overwriteError("Downloading (<error>failed</error>)", false);
                }

                $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode);
                $e->setHeaders($http_response_header);
                $e->setResponse($result);
                $e->setStatusCode($statusCode);
                throw $e;
            }
            $result = false;
        }

        if ($this->progress && !$this->retry && !$isRedirect) {
            $this->io->overwriteError("Downloading (".($result === false ? '<error>failed</error>' : '<comment>100%</comment>').")", false);
        }

        // decode gzip
        if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
            $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                try {
                    if (PHP_VERSION_ID >= 50400) {
                        $result = zlib_decode($result);
                    } else {
                        // work around issue with gzuncompress & co that do not work with all gzip checksums
                        $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
                    }

                    if (!$result) {
                        throw new TransportException('Failed to decode zlib stream');
                    }
                } catch (\Exception $e) {
                    if ($this->degradedMode) {
                        throw $e;
                    }

                    $this->degradedMode = true;
                    $this->io->writeError(array(
                        '',
                        '<error>Failed to decode response: '.$e->getMessage().'</error>',
                        '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                    ));

                    return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
                }
            }
        }

        // handle copy command if download was successful
        if (false !== $result && null !== $fileName && !$isRedirect) {
            if ('' === $result) {
                throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
            }

            $errorMessage = '';
            set_error_handler(function ($code, $msg) use (&$errorMessage) {
                if ($errorMessage) {
                    $errorMessage .= "\n";
                }
                $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);

                return true;
            });
            $result = (bool) file_put_contents($fileName, $result);
            restore_error_handler();
            if (false === $result) {
                throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage);
            }
        }

        // Handle SSL cert match issues
        if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) {
            // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6
            // The procedure to handle sAN for older PHP's is:
            //
            // 1. Open socket to remote server and fetch certificate (disabling peer
            //    validation because PHP errors without giving up the certificate.)
            //
            // 2. Verifying the domain in the URL against the names in the sAN field.
            //    If there is a match record the authority [host/port], certificate
            //    common name, and certificate fingerprint.
            //
            // 3. Retry the original request but changing the CN_match parameter to
            //    the common name extracted from the certificate in step 2.
            //
            // 4. To prevent any attempt at being hoodwinked by switching the
            //    certificate between steps 2 and 3 the fingerprint of the certificate
            //    presented in step 3 is compared against the one recorded in step 2.
            if (CaBundle::isOpensslParseSafe()) {
                $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options);

                if ($certDetails) {
                    $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;

                    $this->retry = true;
                }
            } else {
                $this->io->writeError('');
                $this->io->writeError(sprintf(
                    '<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
                    PHP_VERSION
                ));
            }
        }

        if ($this->retry) {
            $this->retry = false;

            $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);

            if ($this->storeAuth && $this->config) {
                $authHelper = new AuthHelper($this->io, $this->config);
                $authHelper->storeAuth($this->originUrl, $this->storeAuth);
                $this->storeAuth = false;
            }

            return $result;
        }

        if (false === $result) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
            if (!empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
            }

            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError(array(
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ));

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        if (!empty($http_response_header[0])) {
            $this->lastHeaders = $http_response_header;
        }

        return $result;
    }

    /**
     * Get contents of remote URL.
     *
     * @param string   $originUrl The origin URL
     * @param string   $fileUrl   The file URL
     * @param resource $context   The stream context
     *
     * @return string|false The response contents or false on failure
     */
    protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null)
    {
        try {
            $e = null;
            $result = file_get_contents($fileUrl, false, $context);
        } catch (\Throwable $e) {
        } catch (\Exception $e) {
        }

        $responseHeaders = isset($http_response_header) ? $http_response_header : array();

        if (null !== $e) {
            throw $e;
        }

        return $result;
    }

    /**
     * Get notification action.
     *
     * @param  int                $notificationCode The notification code
     * @param  int                $severity         The severity level
     * @param  string             $message          The message
     * @param  int                $messageCode      The message code
     * @param  int                $bytesTransferred The loaded size
     * @param  int                $bytesMax         The total size
     * @throws TransportException
     */
    protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax)
    {
        switch ($notificationCode) {
            case STREAM_NOTIFY_FAILURE:
                if (400 === $messageCode) {
                    // This might happen if your host is secured by ssl client certificate authentication
                    // but you do not send an appropriate certificate
                    throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode);
                }
                break;

            case STREAM_NOTIFY_FILE_SIZE_IS:
                $this->bytesMax = $bytesMax;
                break;

            case STREAM_NOTIFY_PROGRESS:
                if ($this->bytesMax > 0 && $this->progress) {
                    $progression = min(100, round($bytesTransferred / $this->bytesMax * 100));

                    if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) {
                        $this->lastProgress = $progression;
                        $this->io->overwriteError("Downloading (<comment>$progression%</comment>)", false);
                    }
                }
                break;

            default:
                break;
        }
    }

    protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array())
    {
        if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
            $gitHubUtil = new GitHub($this->io, $this->config, null);
            $message = "\n";

            $rateLimited = $gitHubUtil->isRateLimited($headers);
            if ($rateLimited) {
                $rateLimit = $gitHubUtil->getRateLimit($headers);
                if ($this->io->hasAuthentication($this->originUrl)) {
                    $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
                } else {
                    $message = 'Create a GitHub OAuth token to go over the API rate limit.';
                }

                $message = sprintf(
                    'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.',
                    $rateLimit['limit'],
                    $rateLimit['reset']
                )."\n";
            } else {
                $message .= 'Could not fetch '.$this->fileUrl.', please ';
                if ($this->io->hasAuthentication($this->originUrl)) {
                    $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
                } else {
                    $message .= 'create a GitHub OAuth token to access private repos';
                }
            }

            if (!$gitHubUtil->authorizeOAuth($this->originUrl)
                && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
            }
        } elseif ($this->config && in_array($this->originUrl, $this->config->get('gitlab-domains'), true)) {
            $message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit');
            $gitLabUtil = new GitLab($this->io, $this->config, null);

            if ($this->io->hasAuthentication($this->originUrl) && ($auth = $this->io->getAuthentication($this->originUrl)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token', 'oauth2'), true)) {
                throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
            }

            if (!$gitLabUtil->authorizeOAuth($this->originUrl)
                && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
            }
        } elseif ($this->config && ($this->originUrl === 'bitbucket.org' || $this->originUrl === 'api.bitbucket.org')) {
            $askForOAuthToken = true;
            $this->originUrl = 'bitbucket.org';

            if ($this->io->hasAuthentication($this->originUrl)) {
                $auth = $this->io->getAuthentication($this->originUrl);
                if ($auth['username'] !== 'x-token-auth') {
                    $bitbucketUtil = new Bitbucket($this->io, $this->config);
                    $accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']);
                    if (!empty($accessToken)) {
                        $this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken);
                        $askForOAuthToken = false;
                    }
                } else {
                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
                }
            }

            if ($askForOAuthToken) {
                $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit');
                $bitBucketUtil = new Bitbucket($this->io, $this->config);
                if (! $bitBucketUtil->authorizeOAuth($this->originUrl)
                    && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message))
                ) {
                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
                }
            }
        } else {
            // 404s are only handled for github
            if ($httpStatus === 404) {
                return;
            }

            // fail if the console is not interactive
            if (!$this->io->isInteractive()) {
                if ($httpStatus === 401) {
                    $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console to authenticate";
                }
                if ($httpStatus === 403) {
                    $message = "The '" . $this->fileUrl . "' URL could not be accessed: " . $reason;
                }

                throw new TransportException($message, $httpStatus);
            }
            // fail if we already have auth
            if ($this->io->hasAuthentication($this->originUrl)) {
                throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
            }

            $this->io->writeError('    Authentication required (<info>'.$this->originUrl.'</info>):');
            $username = $this->io->ask('      Username: ');
            $password = $this->io->askAndHideAnswer('      Password: ');
            $this->io->setAuthentication($this->originUrl, $username, $password);
            $this->storeAuth = $this->config->get('store-auths');
        }

        $this->retry = true;
        throw new TransportException('RETRY');
    }

    protected function getOptionsForUrl($originUrl, $additionalOptions)
    {
        $tlsOptions = array();

        // Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
        if ($this->disableTls === false && PHP_VERSION_ID < 50600 && !stream_is_local($this->fileUrl)) {
            $host = parse_url($this->fileUrl, PHP_URL_HOST);

            if (PHP_VERSION_ID < 50304) {
                // PHP < 5.3.4 does not support follow_location, for those people
                // do some really nasty hard coded transformations. These will
                // still breakdown if the site redirects to a domain we don't
                // expect.

                if ($host === 'github.com' || $host === 'api.github.com') {
                    $host = '*.github.com';
                }
            }

            $tlsOptions['ssl']['CN_match'] = $host;
            $tlsOptions['ssl']['SNI_server_name'] = $host;

            $urlAuthority = $this->getUrlAuthority($this->fileUrl);

            if (isset($this->peerCertificateMap[$urlAuthority])) {
                // Handle subjectAltName on lesser PHP's.
                $certMap = $this->peerCertificateMap[$urlAuthority];

                $this->io->writeError('', true, IOInterface::DEBUG);
                $this->io->writeError(sprintf(
                    'Using <info>%s</info> as CN for subjectAltName enabled host <info>%s</info>',
                    $certMap['cn'],
                    $urlAuthority
                ), true, IOInterface::DEBUG);

                $tlsOptions['ssl']['CN_match'] = $certMap['cn'];
                $tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp'];
            } elseif (!CaBundle::isOpensslParseSafe() && $host === 'repo.packagist.org') {
                // handle subjectAltName for packagist.org's repo domain on very old PHPs
                $tlsOptions['ssl']['CN_match'] = 'packagist.org';
            }
        }

        $headers = array();

        if (extension_loaded('zlib')) {
            $headers[] = 'Accept-Encoding: gzip';
        }

        $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions);
        if (!$this->degradedMode) {
            // degraded mode disables HTTP/1.1 which causes issues with some bad
            // proxies/software due to the use of chunked encoding
            $options['http']['protocol_version'] = 1.1;
            $headers[] = 'Connection: close';
        }

        if ($this->io->hasAuthentication($originUrl)) {
            $authenticationDisplayMessage = null;
            $auth = $this->io->getAuthentication($originUrl);
            if ($auth['password'] === 'bearer') {
                $headers[] = 'Authorization: Bearer '.$auth['username'];
            } elseif ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
                $options['github-token'] = $auth['username'];
                $authenticationDisplayMessage = 'Using GitHub token authentication';
            } elseif ($this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)) {
                if ($auth['password'] === 'oauth2') {
                    $headers[] = 'Authorization: Bearer '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
                } elseif ($auth['password'] === 'private-token' || $auth['password'] === 'gitlab-ci-token') {
                    $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab private token authentication';
                }
            } elseif ('bitbucket.org' === $originUrl
                && $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username']
            ) {
                if (!$this->isPublicBitBucketDownload($this->fileUrl)) {
                    $headers[] = 'Authorization: Bearer ' . $auth['password'];
                    $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
                }
            } else {
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $headers[] = 'Authorization: Basic '.$authStr;
                $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
            }

            if ($authenticationDisplayMessage && !in_array($originUrl, $this->displayedOriginAuthentications, true)) {
                $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
                $this->displayedOriginAuthentications[] = $originUrl;
            }
        }

        $options['http']['follow_location'] = 0;

        if (isset($options['http']['header']) && !is_array($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n"));
        }
        foreach ($headers as $header) {
            $options['http']['header'][] = $header;
        }

        return $options;
    }

    private function handleRedirect(array $http_response_header, array $additionalOptions, $result)
    {
        if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = $this->scheme.':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($this->fileUrl, PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl);
            }
        }

        if (!empty($targetUrl)) {
            $this->redirects++;

            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);

            $additionalOptions['redirects'] = $this->redirects;

            return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress);
        }

        if (!$this->retry) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')');
            $e->setHeaders($http_response_header);
            $e->setResponse($result);

            throw $e;
        }

        return false;
    }

    /**
     * @param array $options
     *
     * @return array
     */
    private function getTlsDefaults(array $options)
    {
        $ciphers = implode(':', array(
            'ECDHE-RSA-AES128-GCM-SHA256',
            'ECDHE-ECDSA-AES128-GCM-SHA256',
            'ECDHE-RSA-AES256-GCM-SHA384',
            'ECDHE-ECDSA-AES256-GCM-SHA384',
            'DHE-RSA-AES128-GCM-SHA256',
            'DHE-DSS-AES128-GCM-SHA256',
            'kEDH+AESGCM',
            'ECDHE-RSA-AES128-SHA256',
            'ECDHE-ECDSA-AES128-SHA256',
            'ECDHE-RSA-AES128-SHA',
            'ECDHE-ECDSA-AES128-SHA',
            'ECDHE-RSA-AES256-SHA384',
            'ECDHE-ECDSA-AES256-SHA384',
            'ECDHE-RSA-AES256-SHA',
            'ECDHE-ECDSA-AES256-SHA',
            'DHE-RSA-AES128-SHA256',
            'DHE-RSA-AES128-SHA',
            'DHE-DSS-AES128-SHA256',
            'DHE-RSA-AES256-SHA256',
            'DHE-DSS-AES256-SHA',
            'DHE-RSA-AES256-SHA',
            'AES128-GCM-SHA256',
            'AES256-GCM-SHA384',
            'AES128-SHA256',
            'AES256-SHA256',
            'AES128-SHA',
            'AES256-SHA',
            'AES',
            'CAMELLIA',
            'DES-CBC3-SHA',
            '!aNULL',
            '!eNULL',
            '!EXPORT',
            '!DES',
            '!RC4',
            '!MD5',
            '!PSK',
            '!aECDH',
            '!EDH-DSS-DES-CBC3-SHA',
            '!EDH-RSA-DES-CBC3-SHA',
            '!KRB5-DES-CBC3-SHA',
        ));

        /**
         * CN_match and SNI_server_name are only known once a URL is passed.
         * They will be set in the getOptionsForUrl() method which receives a URL.
         *
         * cafile or capath can be overridden by passing in those options to constructor.
         */
        $defaults = array(
            'ssl' => array(
                'ciphers' => $ciphers,
                'verify_peer' => true,
                'verify_depth' => 7,
                'SNI_enabled' => true,
                'capture_peer_cert' => true,
            ),
        );

        if (isset($options['ssl'])) {
            $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
        }

        $caBundleLogger = $this->io instanceof LoggerInterface ? $this->io : null;

        /**
         * Attempt to find a local cafile or throw an exception if none pre-set
         * The user may go download one if this occurs.
         */
        if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
            $result = CaBundle::getSystemCaRootBundlePath($caBundleLogger);

            if (is_dir($result)) {
                $defaults['ssl']['capath'] = $result;
            } else {
                $defaults['ssl']['cafile'] = $result;
            }
        }

        if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $caBundleLogger))) {
            throw new TransportException('The configured cafile was not valid or could not be read.');
        }

        if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) {
            throw new TransportException('The configured capath was not valid or could not be read.');
        }

        /**
         * Disable TLS compression to prevent CRIME attacks where supported.
         */
        if (PHP_VERSION_ID >= 50413) {
            $defaults['ssl']['disable_compression'] = true;
        }

        return $defaults;
    }

    /**
     * Fetch certificate common name and fingerprint for validation of SAN.
     *
     * @todo Remove when PHP 5.6 is minimum supported version.
     */
    private function getCertificateCnAndFp($url, $options)
    {
        if (PHP_VERSION_ID >= 50600) {
            throw new \BadMethodCallException(sprintf(
                '%s must not be used on PHP >= 5.6',
                __METHOD__
            ));
        }

        $context = StreamContextFactory::getContext($url, $options, array('options' => array(
            'ssl' => array(
                'capture_peer_cert' => true,
                'verify_peer' => false, // Yes this is fucking insane! But PHP is lame.
            ), ),
        ));

        // Ideally this would just use stream_socket_client() to avoid sending a
        // HTTP request but that does not capture the certificate.
        if (false === $handle = @fopen($url, 'rb', false, $context)) {
            return;
        }

        // Close non authenticated connection without reading any content.
        fclose($handle);
        $handle = null;

        $params = stream_context_get_params($context);

        if (!empty($params['options']['ssl']['peer_certificate'])) {
            $peerCertificate = $params['options']['ssl']['peer_certificate'];

            if (TlsHelper::checkCertificateHost($peerCertificate, parse_url($url, PHP_URL_HOST), $commonName)) {
                return array(
                    'cn' => $commonName,
                    'fp' => TlsHelper::getCertificateFingerprint($peerCertificate),
                );
            }
        }
    }

    private function getUrlAuthority($url)
    {
        $defaultPorts = array(
            'ftp' => 21,
            'http' => 80,
            'https' => 443,
            'ssh2.sftp' => 22,
            'ssh2.scp' => 22,
        );

        $scheme = parse_url($url, PHP_URL_SCHEME);

        if (!isset($defaultPorts[$scheme])) {
            throw new \InvalidArgumentException(sprintf(
                'Could not get default port for unknown scheme: %s',
                $scheme
            ));
        }

        $defaultPort = $defaultPorts[$scheme];
        $port = parse_url($url, PHP_URL_PORT) ?: $defaultPort;

        return parse_url($url, PHP_URL_HOST).':'.$port;
    }

    /**
     * @link https://github.com/composer/composer/issues/5584
     *
     * @param string $urlToBitBucketFile URL to a file at bitbucket.org.
     *
     * @return bool Whether the given URL is a public BitBucket download which requires no authentication.
     */
    private function isPublicBitBucketDownload($urlToBitBucketFile)
    {
        $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
        if (strpos($domain, 'bitbucket.org') === false) {
            // Bitbucket downloads are hosted on amazonaws.
            // We do not need to authenticate there at all
            return true;
        }

        $path = parse_url($urlToBitBucketFile, PHP_URL_PATH);

        // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
        // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
        $pathParts = explode('/', $path);

        return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
    }

    public static function outputWarnings(IOInterface $io, $url, $data)
    {
        foreach (array('warning', 'info') as $type) {
            if (empty($data[$type])) {
                continue;
            }

            if (!empty($data[$type . '-versions'])) {
                $versionParser = new VersionParser();
                $constraint = $versionParser->parseConstraints($data[$type . '-versions']);
                $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion()));
                if (!$constraint->matches($composer)) {
                    continue;
                }
            }

            $io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].'</'.$type.'>');
        }
    }

    public static function getOrigin($urlOrPath)
    {
        $hostPort = parse_url($urlOrPath, PHP_URL_HOST);
        if (!$hostPort) {
            return $urlOrPath;
        }
        if (parse_url($urlOrPath, PHP_URL_PORT)) {
            $hostPort .= ':'.parse_url($urlOrPath, PHP_URL_PORT);
        }

        return $hostPort;
    }

    private function stripCredentialsFromUrl($url)
    {
        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
        return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
    }
}
<?php

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

namespace Composer\Util;

use Composer\XdebugHandler\XdebugHandler;

/**
 * Provides ini file location functions that work with and without a restart.
 * When the process has restarted it uses a tmp ini and stores the original
 * ini locations in an environment variable.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class IniHelper
{
    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return array
     */
    public static function getAll()
    {
        return XdebugHandler::getAllIniFiles();
    }

    /**
     * Describes the location of the loaded php.ini file(s)
     *
     * @return string
     */
    public static function getMessage()
    {
        $paths = self::getAll();

        if (empty($paths[0])) {
            array_shift($paths);
        }

        $ini = array_shift($paths);

        if (empty($ini)) {
            return 'A php.ini file does not exist. You will have to create one.';
        }

        if (!empty($paths)) {
            return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.';
        }

        return 'The php.ini used by your command-line PHP is: '.$ini;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHub
{
    protected $io;
    protected $config;
    protected $process;
    protected $remoteFilesystem;

    /**
     * Constructor.
     *
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
    }

    /**
     * Attempts to authorize a GitHub domain via OAuth
     *
     * @param  string $originUrl The host this GitHub instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth($originUrl)
    {
        if (!in_array($originUrl, $this->config->get('github-domains'))) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute('git config github.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitHub domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this GitHub instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively($originUrl, $message = null)
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $note = 'Composer';
        if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) {
            $note .= ' on ' . trim($output);
        }
        $note .= ' ' . date('Y-m-d Hi');

        $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note));
        $this->io->writeError(sprintf('Head to %s', $url));
        $this->io->writeError(sprintf('to retrieve a token. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));

        $token = trim($this->io->askAndHideAnswer('Token (hidden): '));

        if (!$token) {
            $this->io->writeError('<warning>No token given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic');

        try {
            $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/';

            $this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl, false, array(
                'retry-auth-failure' => false,
            ));
        } catch (TransportException $e) {
            if (in_array($e->getCode(), array(403, 401))) {
                $this->io->writeError('<error>Invalid token provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

                return false;
            }

            throw $e;
        }

        // store value in user config
        $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl);
        $this->config->getAuthConfigSource()->addConfigSetting('github-oauth.'.$originUrl, $token);

        $this->io->writeError('<info>Token stored successfully.</info>');

        return true;
    }

    /**
     * Extract ratelimit from response.
     *
     * @param array $headers Headers from Composer\Downloader\TransportException.
     *
     * @return array Associative array with the keys limit and reset.
     */
    public function getRateLimit(array $headers)
    {
        $rateLimit = array(
            'limit' => '?',
            'reset' => '?',
        );

        foreach ($headers as $header) {
            $header = trim($header);
            if (false === strpos($header, 'X-RateLimit-')) {
                continue;
            }
            list($type, $value) = explode(':', $header, 2);
            switch ($type) {
                case 'X-RateLimit-Limit':
                    $rateLimit['limit'] = (int) trim($value);
                    break;
                case 'X-RateLimit-Reset':
                    $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
                    break;
            }
        }

        return $rateLimit;
    }

    /**
     * Finds whether a request failed due to rate limiting
     *
     * @param array $headers Headers from Composer\Downloader\TransportException.
     *
     * @return bool
     */
    public function isRateLimited(array $headers)
    {
        foreach ($headers as $header) {
            if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
                return true;
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Url
{
    public static function updateDistReference(Config $config, $url, $ref)
    {
        $host = parse_url($url, PHP_URL_HOST);

        if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') {
            if (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) {
                // update legacy github archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) {
                // update current github web archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (preg_match('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) {
                // update api archives to the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            }
        } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') {
            if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) {
                // update Bitbucket archives to the proper reference
                $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4];
            }
        } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') {
            if (preg_match('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) {
                // update Gitlab archives to the proper reference
                $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref;
            }
        } elseif (in_array($host, $config->get('github-domains'), true)) {
            $url = preg_replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url);
        } elseif (in_array($host, $config->get('gitlab-domains'), true)) {
            $url = preg_replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url);
        }

        return $url;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Json\JsonValidationException;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Spdx\SpdxLicenses;

/**
 * Validates a composer configuration.
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigValidator
{
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * Validates the config, and returns the result.
     *
     * @param string $file                       The path to the file
     * @param int    $arrayLoaderValidationFlags Flags for ArrayLoader validation
     *
     * @return array a triple containing the errors, publishable errors, and warnings
     */
    public function validate($file, $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL)
    {
        $errors = array();
        $publishErrors = array();
        $warnings = array();

        // validate json schema
        $laxValid = false;
        try {
            $json = new JsonFile($file, null, $this->io);
            $manifest = $json->read();

            $json->validateSchema(JsonFile::LAX_SCHEMA);
            $laxValid = true;
            $json->validateSchema();
        } catch (JsonValidationException $e) {
            foreach ($e->getErrors() as $message) {
                if ($laxValid) {
                    $publishErrors[] = $message;
                } else {
                    $errors[] = $message;
                }
            }
        } catch (\Exception $e) {
            $errors[] = $e->getMessage();

            return array($errors, $publishErrors, $warnings);
        }

        // validate actual data
        if (empty($manifest['license'])) {
            $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.';
        } else {
            $licenses = (array) $manifest['license'];

            // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer
            foreach ($licenses as $key => $license) {
                if ('proprietary' === $license) {
                    unset($licenses[$key]);
                }
            }

            $licenseValidator = new SpdxLicenses();
            foreach ($licenses as $license) {
                $spdxLicense = $licenseValidator->getLicenseByIdentifier($license);
                if ($spdxLicense && $spdxLicense[3]) {
                    if (preg_match('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead',
                            $license
                        );
                    } elseif (preg_match('{^[AL]?GPL-[123](\.[01])?$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead',
                            $license
                        );
                    } else {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/',
                            $license
                        );
                    }
                }
            }
        }

        if (isset($manifest['version'])) {
            $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.';
        }

        if (!empty($manifest['name']) && preg_match('{[A-Z]}', $manifest['name'])) {
            $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']);
            $suggestName = strtolower($suggestName);

            $publishErrors[] = sprintf(
                'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.',
                $manifest['name'],
                $suggestName
            );
        }

        if (!empty($manifest['type']) && $manifest['type'] == 'composer-installer') {
            $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation.";
        }

        // check for require-dev overrides
        if (isset($manifest['require']) && isset($manifest['require-dev'])) {
            $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']);

            if (!empty($requireOverrides)) {
                $plural = (count($requireOverrides) > 1) ? 'are' : 'is';
                $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior";
            }
        }


        // check for meaningless provide/replace satisfying requirements
        foreach (array('provide', 'replace') as $linkType) {
            if (isset($manifest[$linkType])) {
                foreach (array('require', 'require-dev') as $requireType) {
                    if (isset($manifest[$requireType])) {
                        foreach ($manifest[$linkType] as $provide => $constraint) {
                            if (isset($manifest[$requireType][$provide])) {
                                $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.';
                            }
                        }
                    }
                }
            }
        }

        // check for commit references
        $require = isset($manifest['require']) ? $manifest['require'] : array();
        $requireDev = isset($manifest['require-dev']) ? $manifest['require-dev'] : array();
        $packages = array_merge($require, $requireDev);
        foreach ($packages as $package => $version) {
            if (preg_match('/#/', $version) === 1) {
                $warnings[] = sprintf(
                    'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.',
                    $package
                );
            }
        }

        // report scripts-descriptions for non-existent scripts
        $scriptsDescriptions = isset($manifest['scripts-descriptions']) ? $manifest['scripts-descriptions'] : array();
        $scripts = isset($manifest['scripts']) ? $manifest['scripts'] : array();
        foreach ($scriptsDescriptions as $scriptName => $scriptDescription) {
            if (!array_key_exists($scriptName, $scripts)) {
                $warnings[] = sprintf(
                    'Description for non-existent script "%s" found in "scripts-descriptions"',
                    $scriptName
                );
            }
        }

        // check for empty psr-0/psr-4 namespace prefixes
        if (isset($manifest['autoload']['psr-0'][''])) {
            $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance";
        }
        if (isset($manifest['autoload']['psr-4'][''])) {
            $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance";
        }

        try {
            $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags);
            if (!isset($manifest['version'])) {
                $manifest['version'] = '1.0.0';
            }
            if (!isset($manifest['name'])) {
                $manifest['name'] = 'dummy/dummy';
            }
            $loader->load($manifest);
        } catch (InvalidPackageException $e) {
            $errors = array_merge($errors, $e->getErrors());
        }

        $warnings = array_merge($warnings, $loader->getWarnings());

        return array($errors, $publishErrors, $warnings);
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jonas Renaudot <jonas.renaudot@gmail.com>
 */
class Hg
{
    /**
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * @var \Composer\Config
     */
    private $config;

    /**
     * @var \Composer\Util\ProcessExecutor
     */
    private $process;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
    }

    public function runCommand($commandCallable, $url, $cwd)
    {
        $this->config->prohibitUrlByConfig($url, $this->io);

        // Try as is
        $command = call_user_func($commandCallable, $url);

        if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
            return;
        }

        // Try with the authentication information available
        if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) {
            $auth = $this->io->getAuthentication($match[5]);
            $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6]) ? $match[6] : null);

            $command = call_user_func($commandCallable, $authenticatedUrl);

            if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                return;
            }

            $error = $this->process->getErrorOutput();
        } else {
            $error = 'The given URL (' . $url . ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)';
        }

        $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url);
    }

    public static function sanitizeUrl($message)
    {
        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
                return '://***:***@';
            }

            return '://' . $m[1] . ':***@';
        }, $message);
    }

    private function throwException($message, $url)
    {
        if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
            throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
        }

        throw new \RuntimeException(self::sanitizeUrl($message));
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Svn
{
    const MAX_QTY_AUTH_TRIES = 5;

    /**
     * @var array
     */
    protected $credentials;

    /**
     * @var bool
     */
    protected $hasAuth;

    /**
     * @var \Composer\IO\IOInterface
     */
    protected $io;

    /**
     * @var string
     */
    protected $url;

    /**
     * @var bool
     */
    protected $cacheCredentials = true;

    /**
     * @var ProcessExecutor
     */
    protected $process;

    /**
     * @var int
     */
    protected $qtyAuthTries = 0;

    /**
     * @var \Composer\Config
     */
    protected $config;

    /**
     * @var string|null
     */
    private static $version;

    /**
     * @param string                   $url
     * @param \Composer\IO\IOInterface $io
     * @param Config                   $config
     * @param ProcessExecutor          $process
     */
    public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null)
    {
        $this->url = $url;
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
    }

    public static function cleanEnv()
    {
        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        putenv("DYLD_LIBRARY_PATH");
        unset($_SERVER['DYLD_LIBRARY_PATH']);
    }

    /**
     * Execute an SVN remote command and try to fix up the process with credentials
     * if necessary.
     *
     * @param string $command SVN command to run
     * @param string $url     SVN url
     * @param string $cwd     Working directory
     * @param string $path    Target for a checkout
     * @param bool   $verbose Output all output to the user
     *
     * @throws \RuntimeException
     * @return string
     */
    public function execute($command, $url, $cwd = null, $path = null, $verbose = false)
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose);
    }

    /**
     * Execute an SVN local command and try to fix up the process with credentials
     * if necessary.
     *
     * @param string $command SVN command to run
     * @param string $path    Path argument passed thru to the command
     * @param string $cwd     Working directory
     * @param bool   $verbose Output all output to the user
     *
     * @throws \RuntimeException
     * @return string
     */
    public function executeLocal($command, $path, $cwd = null, $verbose = false)
    {
        // A local command has no remote url
        return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
    }

    private function executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose)
    {
        // Regenerate the command at each try, to use the newly user-provided credentials
        $command = $this->getCommand($svnCommand, $url, $path);

        $output = null;
        $io = $this->io;
        $handler = function ($type, $buffer) use (&$output, $io, $verbose) {
            if ($type !== 'out') {
                return;
            }
            if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
                return;
            }
            $output .= $buffer;
            if ($verbose) {
                $io->writeError($buffer, false);
            }
        };
        $status = $this->process->execute($command, $handler, $cwd);
        if (0 === $status) {
            return $output;
        }

        $errorOutput = $this->process->getErrorOutput();
        $fullOutput = implode("\n", array($output, $errorOutput));

        // the error is not auth-related
        if (false === stripos($fullOutput, 'Could not authenticate to server:')
            && false === stripos($fullOutput, 'authorization failed')
            && false === stripos($fullOutput, 'svn: E170001:')
            && false === stripos($fullOutput, 'svn: E215004:')) {
            throw new \RuntimeException($fullOutput);
        }

        if (!$this->hasAuth()) {
            $this->doAuthDance();
        }

        // try to authenticate if maximum quantity of tries not reached
        if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) {
            // restart the process
            return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose);
        }

        throw new \RuntimeException(
            'wrong credentials provided ('.$fullOutput.')'
        );
    }

    /**
     * @param bool $cacheCredentials
     */
    public function setCacheCredentials($cacheCredentials)
    {
        $this->cacheCredentials = $cacheCredentials;
    }

    /**
     * Repositories requests credentials, let's put them in.
     *
     * @throws \RuntimeException
     * @return \Composer\Util\Svn
     */
    protected function doAuthDance()
    {
        // cannot ask for credentials in non interactive mode
        if (!$this->io->isInteractive()) {
            throw new \RuntimeException(
                'can not ask for authentication in non interactive mode'
            );
        }

        $this->io->writeError("The Subversion server ({$this->url}) requested credentials:");

        $this->hasAuth = true;
        $this->credentials['username'] = $this->io->ask("Username: ");
        $this->credentials['password'] = $this->io->askAndHideAnswer("Password: ");

        $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true);

        return $this;
    }

    /**
     * A method to create the svn commands run.
     *
     * @param string $cmd  Usually 'svn ls' or something like that.
     * @param string $url  Repo URL.
     * @param string $path Target for a checkout
     *
     * @return string
     */
    protected function getCommand($cmd, $url, $path = null)
    {
        $cmd = sprintf(
            '%s %s%s -- %s',
            $cmd,
            '--non-interactive ',
            $this->getCredentialString(),
            ProcessExecutor::escape($url)
        );

        if ($path) {
            $cmd .= ' ' . ProcessExecutor::escape($path);
        }

        return $cmd;
    }

    /**
     * Return the credential string for the svn command.
     *
     * Adds --no-auth-cache when credentials are present.
     *
     * @return string
     */
    protected function getCredentialString()
    {
        if (!$this->hasAuth()) {
            return '';
        }

        return sprintf(
            ' %s--username %s --password %s ',
            $this->getAuthCache(),
            ProcessExecutor::escape($this->getUsername()),
            ProcessExecutor::escape($this->getPassword())
        );
    }

    /**
     * Get the password for the svn command. Can be empty.
     *
     * @throws \LogicException
     * @return string
     */
    protected function getPassword()
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return isset($this->credentials['password']) ? $this->credentials['password'] : '';
    }

    /**
     * Get the username for the svn command.
     *
     * @throws \LogicException
     * @return string
     */
    protected function getUsername()
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return $this->credentials['username'];
    }

    /**
     * Detect Svn Auth.
     *
     * @return bool
     */
    protected function hasAuth()
    {
        if (null !== $this->hasAuth) {
            return $this->hasAuth;
        }

        if (false === $this->createAuthFromConfig()) {
            $this->createAuthFromUrl();
        }

        return $this->hasAuth;
    }

    /**
     * Return the no-auth-cache switch.
     *
     * @return string
     */
    protected function getAuthCache()
    {
        return $this->cacheCredentials ? '' : '--no-auth-cache ';
    }

    /**
     * Create the auth params from the configuration file.
     *
     * @return bool
     */
    private function createAuthFromConfig()
    {
        if (!$this->config->has('http-basic')) {
            return $this->hasAuth = false;
        }

        $authConfig = $this->config->get('http-basic');

        $host = parse_url($this->url, PHP_URL_HOST);
        if (isset($authConfig[$host])) {
            $this->credentials['username'] = $authConfig[$host]['username'];
            $this->credentials['password'] = $authConfig[$host]['password'];

            return $this->hasAuth = true;
        }

        return $this->hasAuth = false;
    }

    /**
     * Create the auth params from the url
     *
     * @return bool
     */
    private function createAuthFromUrl()
    {
        $uri = parse_url($this->url);
        if (empty($uri['user'])) {
            return $this->hasAuth = false;
        }

        $this->credentials['username'] = $uri['user'];
        if (!empty($uri['pass'])) {
            $this->credentials['password'] = $uri['pass'];
        }

        return $this->hasAuth = true;
    }

    /**
     * Returns the version of the svn binary contained in PATH
     *
     * @return string|null
     */
    public function binaryVersion()
    {
        if (!self::$version) {
            if (0 === $this->process->execute('svn --version', $output)) {
                if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match)) {
                    self::$version = $match[1];
                }
            }
        }

        return self::$version;
    }
}
<?php

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

namespace Composer\Util;

/**
 * @author Andreas Schempp <andreas.schempp@terminal42.ch>
 */
class Zip
{
    /**
     * Gets content of the root composer.json inside a ZIP archive.
     *
     * @param string $pathToZip
     *
     * @return string|null
     */
    public static function getComposerJson($pathToZip)
    {
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
        }

        $zip = new \ZipArchive();
        if ($zip->open($pathToZip) !== true) {
            return null;
        }

        if (0 == $zip->numFiles) {
            $zip->close();

            return null;
        }

        $foundFileIndex = self::locateFile($zip, 'composer.json');
        if (false === $foundFileIndex) {
            $zip->close();

            return null;
        }

        $content = null;
        $configurationFileName = $zip->getNameIndex($foundFileIndex);
        $stream = $zip->getStream($configurationFileName);

        if (false !== $stream) {
            $content = stream_get_contents($stream);
        }

        $zip->close();

        return $content;
    }

    /**
     * Find a file by name, returning the one that has the shortest path.
     *
     * @param \ZipArchive $zip
     * @param string      $filename
     *
     * @return bool|int
     */
    private static function locateFile(\ZipArchive $zip, $filename)
    {
        $indexOfShortestMatch = false;
        $lengthOfShortestMatch = -1;

        for ($i = 0; $i < $zip->numFiles; $i++) {
            $stat = $zip->statIndex($i);
            if (strcmp(basename($stat['name']), $filename) === 0) {
                $directoryName = dirname($stat['name']);
                if ($directoryName === '.') {
                    //if composer.json is in root directory
                    //it has to be the one to use.
                    return $i;
                }

                if (strpos($directoryName, '\\') !== false ||
                    strpos($directoryName, '/') !== false) {
                    //composer.json files below first directory are rejected
                    continue;
                }

                $length = strlen($stat['name']);
                if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
                    //Check it's not a directory.
                    $contents = $zip->getFromIndex($i);
                    if ($contents !== false) {
                        $indexOfShortestMatch = $i;
                        $lengthOfShortestMatch = $length;
                    }
                }
            }
        }

        return $indexOfShortestMatch;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Composer;

/**
 * Allows the creation of a basic context supporting http proxy
 *
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Markus Tacker <m@coderbyheart.de>
 */
final class StreamContextFactory
{
    /**
     * Creates a context supporting HTTP proxies
     *
     * @param  string            $url            URL the context is to be used for
     * @param  array             $defaultOptions Options to merge with the default
     * @param  array             $defaultParams  Parameters to specify on the context
     * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
     * @return resource          Default context
     */
    public static function getContext($url, array $defaultOptions = array(), array $defaultParams = array())
    {
        $options = array('http' => array(
            // specify defaults again to try and work better with curlwrappers enabled
            'follow_location' => 1,
            'max_redirects' => 20,
        ));

        // Handle HTTP_PROXY/http_proxy on CLI only for security reasons
        if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
            $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
        }

        // Prefer CGI_HTTP_PROXY if available
        if (!empty($_SERVER['CGI_HTTP_PROXY'])) {
            $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']);
        }

        // Override with HTTPS proxy if present and URL is https
        if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
            $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
        }

        // Remove proxy if URL matches no_proxy directive
        if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
            $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']);
            if ($pattern->test($url)) {
                unset($proxy);
            }
        }

        if (!empty($proxy)) {
            $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
            $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';

            if (isset($proxy['port'])) {
                $proxyURL .= ":" . $proxy['port'];
            } elseif ('http://' == substr($proxyURL, 0, 7)) {
                $proxyURL .= ":80";
            } elseif ('https://' == substr($proxyURL, 0, 8)) {
                $proxyURL .= ":443";
            }

            // http(s):// is not supported in proxy
            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);

            if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
                throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
            }

            $options['http']['proxy'] = $proxyURL;

            // enabled request_fulluri unless it is explicitly disabled
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
                case 'https': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
            }

            // add SNI opts for https URLs
            if ('https' === parse_url($url, PHP_URL_SCHEME)) {
                $options['ssl']['SNI_enabled'] = true;
                if (PHP_VERSION_ID < 50600) {
                    $options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
                }
            }

            // handle proxy auth if present
            if (isset($proxy['user'])) {
                $auth = rawurldecode($proxy['user']);
                if (isset($proxy['pass'])) {
                    $auth .= ':' . rawurldecode($proxy['pass']);
                }
                $auth = base64_encode($auth);

                // Preserve headers if already set in default options
                if (isset($defaultOptions['http']['header'])) {
                    if (is_string($defaultOptions['http']['header'])) {
                        $defaultOptions['http']['header'] = array($defaultOptions['http']['header']);
                    }
                    $defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
                } else {
                    $options['http']['header'] = array("Proxy-Authorization: Basic {$auth}");
                }
            }
        }

        $options = array_replace_recursive($options, $defaultOptions);

        if (isset($options['http']['header'])) {
            $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
        }

        if (defined('HHVM_VERSION')) {
            $phpVersion = 'HHVM ' . HHVM_VERSION;
        } else {
            $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
        }

        if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) {
            $options['http']['header'][] = sprintf(
                'User-Agent: Composer/%s (%s; %s; %s%s)',
                Composer::getVersion(),
                function_exists('php_uname') ? php_uname('s') : 'Unknown',
                function_exists('php_uname') ? php_uname('r') : 'Unknown',
                $phpVersion,
                getenv('CI') ? '; CI' : ''
            );
        }

        return stream_context_create($options, $defaultParams);
    }

    /**
     * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and
     * NOT at the end of the array
     *
     * This method fixes the array by moving the content-type header to the end
     *
     * @link https://bugs.php.net/bug.php?id=61548
     * @param string|array $header
     * @return array
     */
    private static function fixHttpHeaderField($header)
    {
        if (!is_array($header)) {
            $header = explode("\r\n", $header);
        }
        uasort($header, function ($el) {
            return stripos($el, 'content-type') === 0 ? 1 : -1;
        });

        return $header;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Git
{
    private static $version = false;

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
        $this->filesystem = $fs;
    }

    public function runCommand($commandCallable, $url, $cwd, $initialClone = false)
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        if ($initialClone) {
            $origCwd = $cwd;
            $cwd = null;
        }

        if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
            throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
        }

        if (!$initialClone) {
            // capture username/password from URL if there is one and we have no auth configured yet
            $this->process->execute('git remote -v', $output, $cwd);
            if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
                $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
            }
        }

        $protocols = $this->config->get('github-protocols');
        if (!is_array($protocols)) {
            throw new \RuntimeException('Config value "github-protocols" must be an array, got ' . gettype($protocols));
        }
        // public github, autoswitch protocols
        if (preg_match('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
            $messages = array();
            foreach ($protocols as $protocol) {
                if ('ssh' === $protocol) {
                    $protoUrl = "git@" . $match[1] . ":" . $match[2];
                } else {
                    $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
                }

                if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) {
                    return;
                }
                $messages[] = '- ' . $protoUrl . "\n" . preg_replace('#^#m', '  ', $this->process->getErrorOutput());
                if ($initialClone) {
                    $this->filesystem->removeDirectory($origCwd);
                }
            }

            // failed to checkout, first check git accessibility
            if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) {
                $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
            }
        }

        // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
        $bypassSshForGitHub = preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);

        $command = call_user_func($commandCallable, $url);

        $auth = null;
        if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
            $errorMsg = $this->process->getErrorOutput();
            // private github repository without ssh key access, try https with auth
            if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
                || preg_match('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)
            ) {
                if (!$this->io->hasAuthentication($match[1])) {
                    $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                    $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';

                    if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                        $gitHubUtil->authorizeOAuthInteractively($match[1], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[1])) {
                    $auth = $this->io->getAuthentication($match[1]);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
                    $command = call_user_func($commandCallable, $authUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }

                    $errorMsg = $this->process->getErrorOutput();
                }
            } elseif (preg_match('{^https://(bitbucket\.org)/(.*)(\.git)?$}U', $url, $match)) { //bitbucket oauth
                $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process);

                if (!$this->io->hasAuthentication($match[1])) {
                    $message = 'Enter your Bitbucket credentials to access private repos';

                    if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                        $bitbucketUtil->authorizeOAuthInteractively($match[1], $message);
                        $accessToken = $bitbucketUtil->getToken();
                        $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken);
                    }
                } else { //We're authenticating with a locally stored consumer.
                    $auth = $this->io->getAuthentication($match[1]);

                    //We already have an access_token from a previous request.
                    if ($auth['username'] !== 'x-token-auth') {
                        $accessToken = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']);
                        if (! empty($accessToken)) {
                            $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken);
                        }
                    }
                }

                if ($this->io->hasAuthentication($match[1])) {
                    $auth = $this->io->getAuthentication($match[1]);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';

                    $command = call_user_func($commandCallable, $authUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }

                    $errorMsg = $this->process->getErrorOutput();
                } else { // Falling back to ssh
                    $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git';
                    $this->io->writeError('    No bitbucket authentication configured. Falling back to ssh.');
                    $command = call_user_func($commandCallable, $sshUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }

                    $errorMsg = $this->process->getErrorOutput();
                }
            } elseif (
                preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match)
                || preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)
            ) {
                if ($match[1] === 'git') {
                    $match[1] = 'https';
                }

                if (!$this->io->hasAuthentication($match[2])) {
                    $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                    $message = 'Cloning failed, enter your GitLab credentials to access private repos';

                    if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) {
                        $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                    if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
                        $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
                    } else {
                        $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
                    }

                    $command = call_user_func($commandCallable, $authUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }

                    $errorMsg = $this->process->getErrorOutput();
                }
            } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate
                if (strpos($match[2], '@')) {
                    list($authParts, $match[2]) = explode('@', $match[2], 2);
                }

                $storeAuth = false;
                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                } elseif ($this->io->isInteractive()) {
                    $defaultUsername = null;
                    if (isset($authParts) && $authParts) {
                        if (false !== strpos($authParts, ':')) {
                            list($defaultUsername, ) = explode(':', $authParts, 2);
                        } else {
                            $defaultUsername = $authParts;
                        }
                    }

                    $this->io->writeError('    Authentication required (<info>' . $match[2] . '</info>):');
                    $auth = array(
                        'username' => $this->io->ask('      Username: ', $defaultUsername),
                        'password' => $this->io->askAndHideAnswer('      Password: '),
                    );
                    $storeAuth = $this->config->get('store-auths');
                }

                if ($auth) {
                    $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3];

                    $command = call_user_func($commandCallable, $authUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        $this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
                        $authHelper = new AuthHelper($this->io, $this->config);
                        $authHelper->storeAuth($match[2], $storeAuth);

                        return;
                    }

                    $errorMsg = $this->process->getErrorOutput();
                }
            }

            if ($initialClone) {
                $this->filesystem->removeDirectory($origCwd);
            }

            $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
        }
    }

    public function syncMirror($url, $dir)
    {
        // update the repo if it is a valid git repository
        if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
            try {
                $commandCallable = function ($url) {
                    $sanitizedUrl = preg_replace('{://([^@]+?):(.+?)@}', '://', $url);

                    return sprintf('git remote set-url origin -- %s && git remote update --prune origin && git remote set-url origin -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
                };
                $this->runCommand($commandCallable, $url, $dir);
            } catch (\Exception $e) {
                $this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);

                return false;
            }

            return true;
        }

        // clean up directory and do a fresh clone into it
        $this->filesystem->removeDirectory($dir);

        $commandCallable = function ($url) use ($dir) {
            return sprintf('git clone --mirror -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($dir));
        };

        $this->runCommand($commandCallable, $url, $dir, true);

        return true;
    }

    public function fetchRefOrSyncMirror($url, $dir, $ref)
    {
        if ($this->checkRefIsInMirror($url, $dir, $ref)) {
            return true;
        }

        if ($this->syncMirror($url, $dir)) {
            return $this->checkRefIsInMirror($url, $dir, $ref);
        }

        return false;
    }

    public static function getNoShowSignatureFlag(ProcessExecutor $process)
    {
        $gitVersion = self::getVersion($process);
        if ($gitVersion && version_compare($gitVersion, '2.10.0-rc0', '>=')) {
            return ' --no-show-signature';
        }

        return '';
    }

    private function checkRefIsInMirror($url, $dir, $ref)
    {
        if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
            $escapedRef = ProcessExecutor::escape($ref.'^{commit}');
            $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir);
            if ($exitCode === 0) {
                return true;
            }
        }

        return false;
    }

    private function isAuthenticationFailure($url, &$match)
    {
        if (!preg_match('{^(https?://)([^/]+)(.*)$}i', $url, $match)) {
            return false;
        }

        $authFailures = array(
            'fatal: Authentication failed',
            'remote error: Invalid username or password.',
            'error: 401 Unauthorized',
            'fatal: unable to access',
            'fatal: could not read Username',
        );

        $errorOutput = $this->process->getErrorOutput();
        foreach ($authFailures as $authFailure) {
            if (strpos($errorOutput, $authFailure) !== false) {
                return true;
            }
        }

        return false;
    }

    public static function cleanEnv()
    {
        if (PHP_VERSION_ID < 50400 && ini_get('safe_mode') && false === strpos(ini_get('safe_mode_allowed_env_vars'), 'GIT_ASKPASS')) {
            throw new \RuntimeException('safe_mode is enabled and safe_mode_allowed_env_vars does not contain GIT_ASKPASS, can not set env var. You can disable safe_mode with "-dsafe_mode=0" when running composer');
        }

        // added in git 1.7.1, prevents prompting the user for username/password
        if (getenv('GIT_ASKPASS') !== 'echo') {
            putenv('GIT_ASKPASS=echo');
            $_SERVER['GIT_ASKPASS'] = 'echo';
        }

        // clean up rogue git env vars in case this is running in a git hook
        if (getenv('GIT_DIR')) {
            putenv('GIT_DIR');
            unset($_SERVER['GIT_DIR']);
        }
        if (getenv('GIT_WORK_TREE')) {
            putenv('GIT_WORK_TREE');
            unset($_SERVER['GIT_WORK_TREE']);
        }

        // Run processes with predictable LANGUAGE
        if (getenv('LANGUAGE') !== 'C') {
            putenv('LANGUAGE=C');
            $_SERVER['LANGUAGE'] = 'C';
        }

        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        putenv("DYLD_LIBRARY_PATH");
        unset($_SERVER['DYLD_LIBRARY_PATH']);
    }

    public static function getGitHubDomainsRegex(Config $config)
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
    }

    public static function getGitLabDomainsRegex(Config $config)
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
    }

    public static function sanitizeUrl($message)
    {
        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
                return '://***:***@';
            }

            return '://' . $m[1] . ':***@';
        }, $message);
    }

    private function throwException($message, $url)
    {
        // git might delete a directory when it fails and php will not know
        clearstatcache();

        if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
            throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
        }

        throw new \RuntimeException(self::sanitizeUrl($message));
    }

    /**
     * Retrieves the current git version.
     *
     * @return string|null The git version number.
     */
    public static function getVersion(ProcessExecutor $process = null)
    {
        if (false === self::$version) {
            self::$version = null;
            if (!$process) {
                $process = new ProcessExecutor;
            }
            if (0 === $process->execute('git --version', $output) && preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
                self::$version = $matches[1];
            }
        }

        return self::$version;
    }
}
<?php

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

namespace Composer\Util;

use stdClass;

/**
 * Tests URLs against NO_PROXY patterns
 */
class NoProxyPattern
{
    /**
     * @var string[]
     */
    protected $hostNames = array();

    /**
     * @var object[]
     */
    protected $rules = array();

    /**
     * @var bool
     */
    protected $noproxy;

    /**
     * @param string $pattern NO_PROXY pattern
     */
    public function __construct($pattern)
    {
        $this->hostNames = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY);
        $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0];
    }

    /**
     * Returns true if a URL matches the NO_PROXY pattern
     *
     * @param string $url
     *
     * @return bool
     */
    public function test($url)
    {
        if ($this->noproxy) {
            return true;
        }

        if (!$urlData = $this->getUrlData($url)) {
            return false;
        }

        foreach ($this->hostNames as $index => $hostName) {
            if ($this->match($index, $hostName, $urlData)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns false is the url cannot be parsed, otherwise a data object
     *
     * @param string $url
     *
     * @return bool|stdclass
     */
    protected function getUrlData($url)
    {
        if (!$host = parse_url($url, PHP_URL_HOST)) {
            return false;
        }

        $port = parse_url($url, PHP_URL_PORT);

        if (empty($port)) {
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http':
                    $port = 80;
                    break;
                case 'https':
                    $port = 443;
                    break;
            }
        }

        $hostName = $host . ($port ? ':' . $port : '');
        list($host, $port, $err) = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata)) {
            return false;
        }

        return $this->makeData($host, $port, $ipdata);
    }

    /**
     * Returns true if the url is matched by a rule
     *
     * @param int $index
     * @param string $hostName
     * @param string $url
     *
     * @return bool
     */
    protected function match($index, $hostName, $url)
    {
        if (!$rule = $this->getRule($index, $hostName)) {
            // Data must have been misformatted
            return false;
        }

        if ($rule->ipdata) {
            // Match ipdata first
            if (!$url->ipdata) {
                return false;
            }

            if ($rule->ipdata->netmask) {
                return $this->matchRange($rule->ipdata, $url->ipdata);
            }

            $match = $rule->ipdata->ip === $url->ipdata->ip;
        } else {
            // Match host and port
            $haystack = substr($url->name, - strlen($rule->name));
            $match = stripos($haystack, $rule->name) === 0;
        }

        if ($match && $rule->port) {
            $match = $rule->port === $url->port;
        }

        return $match;
    }

    /**
     * Returns true if the target ip is in the network range
     *
     * @param stdClass $network
     * @param stdClass $target
     *
     * @return bool
     */
    protected function matchRange(stdClass $network, stdClass $target)
    {
        $net = unpack('C*', $network->ip);
        $mask = unpack('C*', $network->netmask);
        $ip = unpack('C*', $target->ip);

        for ($i = 1; $i < 17; ++$i) {
            if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Finds or creates rule data for a hostname
     *
     * @param int $index
     * @param string $hostName
     *
     * @return {null|stdClass} Null if the hostname is invalid
     */
    private function getRule($index, $hostName)
    {
        if (array_key_exists($index, $this->rules)) {
            return $this->rules[$index];
        }

        $this->rules[$index] = null;
        list($host, $port, $err) = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata, true)) {
            return null;
        }

        $this->rules[$index] = $this->makeData($host, $port, $ipdata);

        return $this->rules[$index];
    }

    /**
     * Creates an object containing IP data if the host is an IP address
     *
     * @param string $host
     * @param null|stdclass $ipdata Set by method if IP address found
     * @param bool $allowPrefix Whether a CIDR prefix-length is expected
     *
     * @return bool False if the host contains invalid data
     */
    private function ipCheckData($host, &$ipdata, $allowPrefix = false)
    {
        $ipdata = null;
        $netmask = null;
        $prefix = null;
        $modified = false;

        // Check for a CIDR prefix-length
        if (strpos($host, '/') !== false) {
            list($host, $prefix) = explode('/', $host);

            if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) {
                return false;
            }
            $prefix = (int) $prefix;
            $modified = true;
        }

        // See if this is an ip address
        if (!filter_var($host, FILTER_VALIDATE_IP)) {
            return !$modified;
        }

        list($ip, $size) = $this->ipGetAddr($host);

        if ($prefix !== null) {
            // Check for a valid prefix
            if ($prefix > $size * 8) {
                return false;
            }

            list($ip, $netmask) = $this->ipGetNetwork($ip, $size, $prefix);
        }

        $ipdata = $this->makeIpData($ip, $size, $netmask);

        return true;
    }

    /**
     * Returns an array of the IP in_addr and its byte size
     *
     * IPv4 addresses are always mapped to IPv6, which simplifies handling
     * and comparison.
     *
     * @param string $host
     *
     * @return mixed[] in_addr, size
     */
    private function ipGetAddr($host)
    {
        $ip = inet_pton($host);
        $size = strlen($ip);
        $mapped = $this->ipMapTo6($ip, $size);

        return array($mapped, $size);
    }

    /**
     * Returns the binary network mask mapped to IPv6
     *
     * @param string $prefix CIDR prefix-length
     * @param int $size Byte size of in_addr
     *
     * @return string
     */
    private function ipGetMask($prefix, $size)
    {
        $mask = '';

        if ($ones = floor($prefix / 8)) {
            $mask = str_repeat(chr(255), $ones);
        }

        if ($remainder = $prefix % 8) {
            $mask .= chr(0xff ^ (0xff >> $remainder));
        }

        $mask = str_pad($mask, $size, chr(0));

        return $this->ipMapTo6($mask, $size);
    }

    /**
     * Calculates and returns the network and mask
     *
     * @param string $rangeIp IP in_addr
     * @param int $size Byte size of in_addr
     * @param string $prefix CIDR prefix-length
     *
     * @return string[] network in_addr, binary mask
     */
    private function ipGetNetwork($rangeIp, $size, $prefix)
    {
        $netmask = $this->ipGetMask($prefix, $size);

        // Get the network from the address and mask
        $mask = unpack('C*', $netmask);
        $ip = unpack('C*', $rangeIp);
        $net = '';

        for ($i = 1; $i < 17; ++$i) {
            $net .= chr($ip[$i] & $mask[$i]);
        }

        return array($net, $netmask);
    }

    /**
     * Maps an IPv4 address to IPv6
     *
     * @param string $binary in_addr
     * @param int $size Byte size of in_addr
     *
     * @return string Mapped or existing in_addr
     */
    private function ipMapTo6($binary, $size)
    {
        if ($size === 4) {
            $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2);
            $binary = $prefix . $binary;
        }

        return $binary;
    }

    /**
     * Creates a rule data object
     *
     * @param string $host
     * @param int $port
     * @param null|stdclass $ipdata
     *
     * @return stdclass
     */
    private function makeData($host, $port, $ipdata)
    {
        return (object) array(
            'host' => $host,
            'name' => '.' . ltrim($host, '.'),
            'port' => $port,
            'ipdata' => $ipdata,
        );
    }

    /**
     * Creates an ip data object
     *
     * @param string $ip in_addr
     * @param int $size Byte size of in_addr
     * @param null|string $netmask Network mask
     *
     * @return stdclass
     */
    private function makeIpData($ip, $size, $netmask)
    {
        return (object) array(
            'ip' => $ip,
            'size' => $size,
            'netmask' => $netmask,
        );
    }

    /**
     * Splits the hostname into host and port components
     *
     * @param string $hostName
     *
     * @return mixed[] host, port, if there was error
     */
    private function splitHostPort($hostName)
    {
        // host, port, err
        $error = array('', '', true);
        $port = 0;
        $ip6 = '';

        // Check for square-bracket notation
        if ($hostName[0] === '[') {
            $index = strpos($hostName, ']');

            // The smallest ip6 address is ::
            if (false === $index || $index < 3) {
                return $error;
            }

            $ip6 = substr($hostName, 1, $index - 1);
            $hostName = substr($hostName, $index + 1);

            if (strpbrk($hostName, '[]') !== false
                || substr_count($hostName, ':') > 1) {
                return $error;
            }
        }

        if (substr_count($hostName, ':') === 1) {
            $index = strpos($hostName, ':');
            $port = substr($hostName, $index + 1);
            $hostName = substr($hostName, 0, $index);

            if (!$this->validateInt($port, 1, 65535)) {
                return $error;
            }

            $port = (int) $port;
        }

        $host = $ip6 . $hostName;

        return array($host, $port, false);
    }

    /**
     * Wrapper around filter_var FILTER_VALIDATE_INT
     *
     * @param string $int
     * @param int $min
     * @param int $max
     */
    private function validateInt($int, $min, $max)
    {
        $options = array(
            'options' => array(
                'min_range' => $min,
                'max_range' => $max)
        );

        return false !== filter_var($int, FILTER_VALIDATE_INT, $options);
    }
}
<?php

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

namespace Composer\Util;

use Composer\Spdx\SpdxLicenses;

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

/**
 * @deprecated use Composer\Spdx\SpdxLicenses instead
 */
class SpdxLicense extends SpdxLicenses
{
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;

/**
 * Convert PHP errors into exceptions
 *
 * @author Artem Lopata <biozshock@gmail.com>
 */
class ErrorHandler
{
    private static $io;

    /**
     * Error handler
     *
     * @param int    $level   Level of the error raised
     * @param string $message Error message
     * @param string $file    Filename that the error was raised in
     * @param int    $line    Line number the error was raised at
     *
     * @static
     * @throws \ErrorException
     * @return bool
     */
    public static function handle($level, $message, $file, $line)
    {
        // error code is not included in error_reporting
        if (!(error_reporting() & $level)) {
            return;
        }

        if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) {
            $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be".
            "\na legitimately suppressed error that you were not supposed to see.";
        }

        if ($level !== E_DEPRECATED && $level !== E_USER_DEPRECATED) {
            throw new \ErrorException($message, 0, $level, $file, $line);
        }

        if (self::$io) {
            // ignore symfony/* deprecation warnings about return types
            // also ignore them from the Composer namespace, as 1.x won't get all that fixed anymore
            if (preg_match('{^Return type of (Symfony|Composer)\\\\.*ReturnTypeWillChange}is', $message)) {
                return true;
            }
            if (strpos(strtr($file, '\\', '/'), 'vendor/symfony/') !== false) {
                return true;
            }

            self::$io->writeError('<warning>Deprecation Notice: '.$message.' in '.$file.':'.$line.'</warning>');
            if (self::$io->isVerbose()) {
                self::$io->writeError('<warning>Stack trace:</warning>');
                self::$io->writeError(array_filter(array_map(function ($a) {
                    if (isset($a['line'], $a['file'])) {
                        return '<warning> '.$a['file'].':'.$a['line'].'</warning>';
                    }

                    return null;
                }, array_slice(debug_backtrace(), 2))));
            }
        }

        return true;
    }

    /**
     * Register error handler.
     *
     * @param IOInterface|null $io
     */
    public static function register(IOInterface $io = null)
    {
        set_error_handler(array(__CLASS__, 'handle'));
        error_reporting(E_ALL | E_STRICT);
        self::$io = $io;
    }
}
<?php

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

namespace Composer\Util;

/**
 * Platform helper for uniform platform-specific tests.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Platform
{
    /**
     * Parses tildes and environment variables in paths.
     *
     * @param  string $path
     * @return string
     */
    public static function expandPath($path)
    {
        if (preg_match('#^~[\\/]#', $path)) {
            return self::getUserDirectory() . substr($path, 1);
        }

        return preg_replace_callback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', function ($matches) {
            // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
            if (Platform::isWindows() && $matches['var'] == 'HOME') {
                return (getenv('HOME') ?: getenv('USERPROFILE')) . $matches['path'];
            }

            return getenv($matches['var']) . $matches['path'];
        }, $path);
    }

    /**
     * @throws \RuntimeException If the user home could not reliably be determined
     * @return string            The formal user home as detected from environment parameters
     */
    public static function getUserDirectory()
    {
        if (false !== ($home = getenv('HOME'))) {
            return $home;
        }

        if (self::isWindows() && false !== ($home = getenv('USERPROFILE'))) {
            return $home;
        }

        if (function_exists('posix_getuid') && function_exists('posix_getpwuid')) {
            $info = posix_getpwuid(posix_getuid());

            return $info['dir'];
        }

        throw new \RuntimeException('Could not determine user directory');
    }

    /**
     * @return bool Whether the host machine is running a Windows OS
     */
    public static function isWindows()
    {
        return defined('PHP_WINDOWS_VERSION_BUILD');
    }

    /**
     * @param  string $str
     * @return int    return a guaranteed binary length of the string, regardless of silly mbstring configs
     */
    public static function strlen($str)
    {
        static $useMbString = null;
        if (null === $useMbString) {
            $useMbString = function_exists('mb_strlen') && ini_get('mbstring.func_overload');
        }

        if ($useMbString) {
            return mb_strlen($str, '8bit');
        }

        return strlen($str);
    }
}
<?php

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

namespace Composer\Util;

use Composer\CaBundle\CaBundle;

/**
 * @author Chris Smith <chris@cs278.org>
 */
final class TlsHelper
{
    /**
     * Match hostname against a certificate.
     *
     * @param mixed  $certificate X.509 certificate
     * @param string $hostname    Hostname in the URL
     * @param string $cn          Set to the common name of the certificate iff match found
     *
     * @return bool
     */
    public static function checkCertificateHost($certificate, $hostname, &$cn = null)
    {
        $names = self::getCertificateNames($certificate);

        if (empty($names)) {
            return false;
        }

        $combinedNames = array_merge($names['san'], array($names['cn']));
        $hostname = strtolower($hostname);

        foreach ($combinedNames as $certName) {
            $matcher = self::certNameMatcher($certName);

            if ($matcher && $matcher($hostname)) {
                $cn = $names['cn'];

                return true;
            }
        }

        return false;
    }

    /**
     * Extract DNS names out of an X.509 certificate.
     *
     * @param mixed $certificate X.509 certificate
     *
     * @return array|null
     */
    public static function getCertificateNames($certificate)
    {
        if (is_array($certificate)) {
            $info = $certificate;
        } elseif (CaBundle::isOpensslParseSafe()) {
            $info = openssl_x509_parse($certificate, false);
        }

        if (!isset($info['subject']['commonName'])) {
            return null;
        }

        $commonName = strtolower($info['subject']['commonName']);
        $subjectAltNames = array();

        if (isset($info['extensions']['subjectAltName'])) {
            $subjectAltNames = preg_split('{\s*,\s*}', $info['extensions']['subjectAltName']);
            $subjectAltNames = array_filter(array_map(function ($name) {
                if (0 === strpos($name, 'DNS:')) {
                    return strtolower(ltrim(substr($name, 4)));
                }

                return null;
            }, $subjectAltNames));
            $subjectAltNames = array_values($subjectAltNames);
        }

        return array(
            'cn' => $commonName,
            'san' => $subjectAltNames,
        );
    }

    /**
     * Get the certificate pin.
     *
     * By Kevin McArthur of StormTide Digital Studios Inc.
     * @KevinSMcArthur / https://github.com/StormTide
     *
     * See http://tools.ietf.org/html/draft-ietf-websec-key-pinning-02
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     *     * Redistributions of source code must retain the above copyright notice,
     *       this list of conditions and the following disclaimer.
     *
     *     * Redistributions in binary form must reproduce the above copyright notice,
     *       this list of conditions and the following disclaimer in the documentation
     *       and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    public static function getCertificateFingerprint($certificate)
    {
        $pubkeydetails = openssl_pkey_get_details(openssl_get_publickey($certificate));
        $pubkeypem = $pubkeydetails['key'];
        //Convert PEM to DER before SHA1'ing
        $start = '-----BEGIN PUBLIC KEY-----';
        $end = '-----END PUBLIC KEY-----';
        $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1));
        $der = base64_decode($pemtrim);

        return sha1($der);
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     *
     * @return bool
     */
    public static function isOpensslParseSafe()
    {
        return CaBundle::isOpensslParseSafe();
    }

    /**
     * Convert certificate name into matching function.
     *
     * @param string $certName CN/SAN
     *
     * @return callable|void
     */
    private static function certNameMatcher($certName)
    {
        $wildcards = substr_count($certName, '*');

        if (0 === $wildcards) {
            // Literal match.
            return function ($hostname) use ($certName) {
                return $hostname === $certName;
            };
        }

        if (1 === $wildcards) {
            $components = explode('.', $certName);

            if (3 > count($components)) {
                // Must have 3+ components
                return;
            }

            $firstComponent = $components[0];

            // Wildcard must be the last character.
            if ('*' !== $firstComponent[strlen($firstComponent) - 1]) {
                return;
            }

            $wildcardRegex = preg_quote($certName);
            $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex);
            $wildcardRegex = "{^{$wildcardRegex}$}";

            return function ($hostname) use ($wildcardRegex) {
                return 1 === preg_match($wildcardRegex, $hostname);
            };
        }
    }
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;

/**
 * @author Roshan Gautam <roshan.gautam@hotmail.com>
 */
class GitLab
{
    protected $io;
    protected $config;
    protected $process;
    protected $remoteFilesystem;

    /**
     * Constructor.
     *
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
    }

    /**
     * Attempts to authorize a GitLab domain via OAuth.
     *
     * @param string $originUrl The host this GitLab instance is located at
     *
     * @return bool true on success
     */
    public function authorizeOAuth($originUrl)
    {
        // before composer 1.9, origin URLs had no port number in them
        $bcOriginUrl = preg_replace('{:\d+}', '', $originUrl);

        if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'oauth2');

            return true;
        }

        // if available use token from composer config
        $authTokens = $this->config->get('gitlab-token');

        if (isset($authTokens[$originUrl])) {
            $this->io->setAuthentication($originUrl, $authTokens[$originUrl], 'private-token');

            return true;
        }

        if (isset($authTokens[$bcOriginUrl])) {
            $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token');

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitLab domain interactively via OAuth.
     *
     * @param string $scheme    Scheme used in the origin URL
     * @param string $originUrl The host this GitLab instance is located at
     * @param string $message   The reason this authorization is required
     *
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     *
     * @return bool true on success
     */
    public function authorizeOAuthInteractively($scheme, $originUrl, $message = null)
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/profile/applications');

        $attemptCounter = 0;

        while ($attemptCounter++ < 5) {
            try {
                $response = $this->createToken($scheme, $originUrl);
            } catch (TransportException $e) {
                // 401 is bad credentials,
                // 403 is max login attempts exceeded
                if (in_array($e->getCode(), array(403, 401))) {
                    if (401 === $e->getCode()) {
                        $response = json_decode($e->getResponse(), true);
                        if (isset($response['error']) && $response['error'] === 'invalid_grant') {
                            $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token');
                        } else {
                            $this->io->writeError('Bad credentials.');
                        }
                    } else {
                        $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
                    }

                    $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens');
                    $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' <token>"');

                    continue;
                }

                throw $e;
            }

            $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');

            // store value in user config in auth file
            $this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']);

            return true;
        }

        throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
    }

    private function createToken($scheme, $originUrl)
    {
        $username = $this->io->ask('Username: ');
        $password = $this->io->askAndHideAnswer('Password: ');

        $headers = array('Content-Type: application/x-www-form-urlencoded');

        $apiUrl = $originUrl;
        $data = http_build_query(array(
            'username' => $username,
            'password' => $password,
            'grant_type' => 'password',
        ), null, '&');
        $options = array(
            'retry-auth-failure' => false,
            'http' => array(
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ),
        );

        $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options);

        $this->io->writeError('Token successfully created');

        return JsonFile::parseJson($json);
    }
}
<?php

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

namespace Composer\EventDispatcher;

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

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

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

    /**
     * Dispatch an event
     *
     * @param  string $eventName An event name
     * @param  Event  $event
     * @return int    return code of the executed script if any, for php scripts a false return
     *                          value is changed to 1, anything else to 0
     */
    public function dispatch($eventName, Event $event = null)
    {
        if (null === $event) {
            $event = new Event($eventName);
        }

        return $this->doDispatch($event);
    }

    /**
     * Dispatch a script event.
     *
     * @param  string $eventName      The constant in ScriptEvents
     * @param  bool   $devMode
     * @param  array  $additionalArgs Arguments passed by the user
     * @param  array  $flags          Optional flags to pass data not as argument
     * @return int    return code of the executed script if any, for php scripts a false return
     *                               value is changed to 1, anything else to 0
     */
    public function dispatchScript($eventName, $devMode = false, $additionalArgs = array(), $flags = array())
    {
        return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
    }

    /**
     * Dispatch a package event.
     *
     * @param string              $eventName     The constant in PackageEvents
     * @param bool                $devMode       Whether or not we are in dev mode
     * @param PolicyInterface     $policy        The policy
     * @param Pool                $pool          The pool
     * @param CompositeRepository $installedRepo The installed repository
     * @param Request             $request       The request
     * @param array               $operations    The list of operations
     * @param OperationInterface  $operation     The package being installed/updated/removed
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
    {
        return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations, $operation));
    }

    /**
     * Dispatch a installer event.
     *
     * @param string              $eventName     The constant in InstallerEvents
     * @param bool                $devMode       Whether or not we are in dev mode
     * @param PolicyInterface     $policy        The policy
     * @param Pool                $pool          The pool
     * @param CompositeRepository $installedRepo The installed repository
     * @param Request             $request       The request
     * @param array               $operations    The list of operations
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
    {
        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations));
    }

    /**
     * Triggers the listeners of an event.
     *
     * @param  Event                        $event The event object to pass to the event handlers/listeners.
     * @throws \RuntimeException|\Exception
     * @return int                          return code of the executed script if any, for php scripts a false return
     *                                            value is changed to 1, anything else to 0
     */
    protected function doDispatch(Event $event)
    {
        $listeners = $this->getListeners($event);

        $this->pushEvent($event);

        $return = 0;
        foreach ($listeners as $callable) {

            $this->ensureBinDirIsInPath();

            if (!is_string($callable)) {
                if (!is_callable($callable)) {
                    $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];

                    throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                }
                $event = $this->checkListenerExpectedEvent($callable, $event);
                $return = false === call_user_func($callable, $event) ? 1 : 0;
            } elseif ($this->isComposerScript($callable)) {
                $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE);

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

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

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

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

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

                try {
                    $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
                } catch (\Exception $e) {
                    $message = "Script %s handling the %s event terminated with an exception";
                    $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                    throw $e;
                }
            } else {
                $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments()));
                $exec = $callable . ($args === '' ? '' : ' '.$args);
                if ($this->io->isVerbose()) {
                    $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec));
                } else {
                    $this->io->writeError(sprintf('> %s', $exec));
                }

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

                if (substr($exec, 0, 8) === '@putenv ') {
                    putenv(substr($exec, 8));
                    list($var, $value) = explode('=', substr($exec, 8), 2);
                    $_SERVER[$var] = $value;

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

                // if composer is being executed, make sure it runs the expected composer from current path
                // resolution, even if bin-dir contains composer too because the project requires composer/composer
                // see https://github.com/composer/composer/issues/8748
                if (substr($exec, 0, 9) === 'composer ') {
                    $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($exec, 8);
                }

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

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

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

        $this->popEvent();

        return $return;
    }

    protected function getPhpExecCommand()
    {
        $finder = new PhpExecutableFinder();
        $phpPath = $finder->find(false);
        if (!$phpPath) {
            throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath);
        }
        $phpArgs = $finder->findArguments();
        $phpArgs = $phpArgs ? ' ' . implode(' ', $phpArgs) : '';
        $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen'));
        $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions'));
        $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'));

        return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag;
    }

    /**
     * @param string $className
     * @param string $methodName
     * @param Event  $event      Event invoking the PHP callable
     */
    protected function executeEventPhpScript($className, $methodName, Event $event)
    {
        $event = $this->checkListenerExpectedEvent(array($className, $methodName), $event);

        if ($this->io->isVerbose()) {
            $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName));
        } else {
            $this->io->writeError(sprintf('> %s::%s', $className, $methodName));
        }

        return $className::$methodName($event);
    }

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

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

        $expected = null;
        $isClass = false;
        if (\PHP_VERSION_ID >= 70000) {
            $reflectionType = $reflected->getType();
            if ($reflectionType) {
                $expected = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string)$reflectionType;
                $isClass = !$reflectionType->isBuiltin();
            }
        } else {
            $expected = $reflected->getClass() ? $reflected->getClass()->getName() : null;
            $isClass = null !== $expected;
        }

        if (!$isClass) {
            return $event;
        }

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

        return $event;
    }

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

        return var_export($cb, true);
    }

    /**
     * Add a listener for a particular event
     *
     * @param string   $eventName The event name - typically a constant
     * @param callable $listener  A callable expecting an event argument
     * @param int      $priority  A higher value represents a higher priority
     */
    public function addListener($eventName, $listener, $priority = 0)
    {
        $this->listeners[$eventName][$priority][] = $listener;
    }

    /**
     * Adds object methods as listeners for the events in getSubscribedEvents
     *
     * @see EventSubscriberInterface
     *
     * @param EventSubscriberInterface $subscriber
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (is_string($params)) {
                $this->addListener($eventName, array($subscriber, $params));
            } elseif (is_string($params[0])) {
                $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
                }
            }
        }
    }

    /**
     * Retrieves all listeners for a given event
     *
     * @param  Event $event
     * @return array All listeners: callables and scripts
     */
    protected function getListeners(Event $event)
    {
        $scriptListeners = $this->getScriptListeners($event);

        if (!isset($this->listeners[$event->getName()][0])) {
            $this->listeners[$event->getName()][0] = array();
        }
        krsort($this->listeners[$event->getName()]);

        $listeners = $this->listeners;
        $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);

        return call_user_func_array('array_merge', $listeners[$event->getName()]);
    }

    /**
     * Checks if an event has listeners registered
     *
     * @param  Event $event
     * @return bool
     */
    public function hasEventListeners(Event $event)
    {
        $listeners = $this->getListeners($event);

        return count($listeners) > 0;
    }

    /**
     * Finds all listeners defined as scripts in the package
     *
     * @param  Event $event Event object
     * @return array Listeners
     */
    protected function getScriptListeners(Event $event)
    {
        $package = $this->composer->getPackage();
        $scripts = $package->getScripts();

        if (empty($scripts[$event->getName()])) {
            return array();
        }

        if ($this->loader) {
            $this->loader->unregister();
        }

        $generator = $this->composer->getAutoloadGenerator();
        if ($event instanceof ScriptEvent) {
            $generator->setDevMode($event->isDevMode());
        }

        $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
        $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages);
        $map = $generator->parseAutoloads($packageMap, $package);
        $this->loader = $generator->createLoader($map);
        $this->loader->register();

        return $scripts[$event->getName()];
    }

    /**
     * Checks if string given references a class path and method
     *
     * @param  string $callable
     * @return bool
     */
    protected function isPhpScript($callable)
    {
        return false === strpos($callable, ' ') && false !== strpos($callable, '::');
    }

    /**
     * Checks if string given references a composer run-script
     *
     * @param  string $callable
     * @return bool
     */
    protected function isComposerScript($callable)
    {
        return '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5) && '@putenv ' !== substr($callable, 0, 8);
    }

    /**
     * Push an event to the stack of active event
     *
     * @param  Event             $event
     * @throws \RuntimeException
     * @return number
     */
    protected function pushEvent(Event $event)
    {
        $eventName = $event->getName();
        if (in_array($eventName, $this->eventStack)) {
            throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName));
        }

        return array_push($this->eventStack, $eventName);
    }

    /**
     * Pops the active event from the stack
     *
     * @return mixed
     */
    protected function popEvent()
    {
        return array_pop($this->eventStack);
    }

    private function ensureBinDirIsInPath()
    {
        $pathStr = 'PATH';
        if (!isset($_SERVER[$pathStr]) && isset($_SERVER['Path'])) {
            $pathStr = 'Path';
        }

        // add the bin dir to the PATH to make local binaries of deps usable in scripts
        $binDir = $this->composer->getConfig()->get('bin-dir');
        if (is_dir($binDir)) {
            $binDir = realpath($binDir);
            if (isset($_SERVER[$pathStr]) && !preg_match('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $_SERVER[$pathStr])) {
                $_SERVER[$pathStr] = $binDir.PATH_SEPARATOR.getenv($pathStr);
                putenv($pathStr.'='.$_SERVER[$pathStr]);
            }
        }
    }
}
<?php

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

namespace Composer\EventDispatcher;

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

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

    /**
     * @var array Flags usable in PHP script handlers
     */
    protected $flags;

    /**
     * @var bool Whether the event should not be passed to more listeners
     */
    private $propagationStopped = false;

    /**
     * Constructor.
     *
     * @param string $name  The event name
     * @param array  $args  Arguments passed by the user
     * @param array  $flags Optional flags to pass data not as argument
     */
    public function __construct($name, array $args = array(), array $flags = array())
    {
        $this->name = $name;
        $this->args = $args;
        $this->flags = $flags;
    }

    /**
     * Returns the event's name.
     *
     * @return string The event name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns the event's arguments.
     *
     * @return array The event arguments
     */
    public function getArguments()
    {
        return $this->args;
    }

    /**
     * Returns the event's flags.
     *
     * @return array The event flags
     */
    public function getFlags()
    {
        return $this->flags;
    }

    /**
     * Checks if stopPropagation has been called
     *
     * @return bool Whether propagation has been stopped
     */
    public function isPropagationStopped()
    {
        return $this->propagationStopped;
    }

    /**
     * Prevents the event from being passed to further listeners
     */
    public function stopPropagation()
    {
        $this->propagationStopped = true;
    }
}
<?php

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

namespace Composer\EventDispatcher;

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

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

namespace Composer\EventDispatcher;

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

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

namespace Composer\Json;

use Composer\Repository\PlatformRepository;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonManipulator
{
    private static $DEFINES = '(?(DEFINE)
       (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
       (?<boolean>   true | false | null )
       (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " )
       (?<array>     \[  (?:  (?&json) \s* (?: , (?&json) \s* )*  )?  \s* \] )
       (?<pair>      \s* (?&string) \s* : (?&json) \s* )
       (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* \} )
       (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )
    )';

    private $contents;
    private $newline;
    private $indent;

    public function __construct($contents)
    {
        $contents = trim($contents);
        if ($contents === '') {
            $contents = '{}';
        }
        if (!$this->pregMatch('#^\{(.*)\}$#s', $contents)) {
            throw new \InvalidArgumentException('The json file must be an object ({})');
        }
        $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n";
        $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents;
        $this->detectIndenting();
    }

    public function getContents()
    {
        return $this->contents . $this->newline;
    }

    public function addLink($type, $package, $constraint, $sortPackages = false)
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no link of that type yet
        if (!isset($decoded[$type])) {
            return $this->addMainKey($type, array($package => $constraint));
        }

        $regex = '{'.self::$DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<property>'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P<value>(?&json))(?P<end>.*)}sx';
        if (!$this->pregMatch($regex, $this->contents, $matches)) {
            return false;
        }

        $links = $matches['value'];

        // try to find existing link
        $packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
        $regex = '{'.self::$DEFINES.'"(?P<package>'.$packageRegex.')"(\s*:\s*)(?&string)}ix';
        if ($this->pregMatch($regex, $links, $packageMatches)) {
            // update existing link
            $existingPackage = $packageMatches['package'];
            $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage));
            $links = preg_replace_callback('{'.self::$DEFINES.'"'.$packageRegex.'"(?P<separator>\s*:\s*)(?&string)}ix', function ($m) use ($existingPackage, $constraint) {
                return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"';
            }, $links);
        } else {
            if ($this->pregMatch('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) {
                // link missing but non empty links
                $links = preg_replace(
                    '{'.preg_quote($match[1]).'$}',
                    // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588
                    addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'),
                    $links
                );
            } else {
                // links empty
                $links = '{' . $this->newline .
                    $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline .
                    $this->indent . '}';
            }
        }

        if (true === $sortPackages) {
            $requirements = json_decode($links, true);
            $this->sortPackages($requirements);
            $links = $this->format($requirements);
        }

        $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end'];

        return true;
    }

    /**
     * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically.
     *
     * @link https://getcomposer.org/doc/02-libraries.md#platform-packages
     *
     * @param array $packages
     */
    private function sortPackages(array &$packages = array())
    {
        $prefix = function ($requirement) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $requirement)) {
                return preg_replace(
                    array(
                        '/^php/',
                        '/^hhvm/',
                        '/^ext/',
                        '/^lib/',
                        '/^\D/',
                    ),
                    array(
                        '0-$0',
                        '1-$0',
                        '2-$0',
                        '3-$0',
                        '4-$0',
                    ),
                    $requirement
                );
            }

            return '5-'.$requirement;
        };

        uksort($packages, function ($a, $b) use ($prefix) {
            return strnatcmp($prefix($a), $prefix($b));
        });
    }

    public function addRepository($name, $config)
    {
        return $this->addSubNode('repositories', $name, $config);
    }

    public function removeRepository($name)
    {
        return $this->removeSubNode('repositories', $name);
    }

    public function addConfigSetting($name, $value)
    {
        return $this->addSubNode('config', $name, $value);
    }

    public function removeConfigSetting($name)
    {
        return $this->removeSubNode('config', $name);
    }

    public function addProperty($name, $value)
    {
        if (substr($name, 0, 8) === 'suggest.') {
            return $this->addSubNode('suggest', substr($name, 8), $value);
        }

        if (substr($name, 0, 6) === 'extra.') {
            return $this->addSubNode('extra', substr($name, 6), $value);
        }

        if (substr($name, 0, 8) === 'scripts.') {
            return $this->addSubNode('scripts', substr($name, 8), $value);
        }

        return $this->addMainKey($name, $value);
    }

    public function removeProperty($name)
    {
        if (substr($name, 0, 8) === 'suggest.') {
            return $this->removeSubNode('suggest', substr($name, 8));
        }

        if (substr($name, 0, 6) === 'extra.') {
            return $this->removeSubNode('extra', substr($name, 6));
        }

        if (substr($name, 0, 8) === 'scripts.') {
            return $this->removeSubNode('scripts', substr($name, 8));
        }

        return $this->removeMainKey($name);
    }

    public function addSubNode($mainNode, $name, $value)
    {
        $decoded = JsonFile::parseJson($this->contents);

        $subName = null;
        if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) {
            list($name, $subName) = explode('.', $name, 2);
        }

        // no main node yet
        if (!isset($decoded[$mainNode])) {
            if ($subName !== null) {
                $this->addMainKey($mainNode, array($name => array($subName => $value)));
            } else {
                $this->addMainKey($mainNode, array($name => $value));
            }

            return true;
        }

        // main node content not match-able
        $nodeRegex = '{'.self::$DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';

        try {
            if (!$this->pregMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        $children = $match['content'];
        // invalid match due to un-regexable content, abort
        if (!@json_decode($children)) {
            return false;
        }

        $that = $this;

        // child exists
        $childRegex = '{'.self::$DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x';
        if ($this->pregMatch($childRegex, $children, $matches)) {
            $children = preg_replace_callback($childRegex, function ($matches) use ($subName, $value, $that) {
                if ($subName !== null) {
                    $curVal = json_decode($matches['content'], true);
                    if (!is_array($curVal)) {
                        $curVal = array();
                    }
                    $curVal[$subName] = $value;
                    $value = $curVal;
                }

                return $matches['start'] . $that->format($value, 1) . $matches['end'];
            }, $children);
        } else {
            $this->pregMatch('#^{ \s*? (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx', $children, $match);

            $whitespace = '';
            if (!empty($match['trailingspace'])) {
                $whitespace = $match['trailingspace'];
            }

            if (!empty($match['content'])) {
                if ($subName !== null) {
                    $value = array($subName => $value);
                }

                // child missing but non empty children
                $children = preg_replace(
                    '#'.$whitespace.'}$#',
                    addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'),
                    $children
                );
            } else {
                if ($subName !== null) {
                    $value = array($subName => $value);
                }

                // children present but empty
                $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}';
            }
        }

        $this->contents = preg_replace_callback($nodeRegex, function ($m) use ($children) {
            return $m['start'] . $children . $m['end'];
        }, $this->contents);

        return true;
    }

    public function removeSubNode($mainNode, $name)
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no node or empty node
        if (empty($decoded[$mainNode])) {
            return true;
        }

        // no node content match-able
        $nodeRegex = '{'.self::$DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';
        try {
            if (!$this->pregMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        $children = $match['content'];

        // invalid match due to un-regexable content, abort
        if (!@json_decode($children, true)) {
            return false;
        }

        $subName = null;
        if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) {
            list($name, $subName) = explode('.', $name, 2);
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) {
            return true;
        }

        // try and find a match for the subkey
        $keyRegex = str_replace('/', '\\\\?/', preg_quote($name));
        if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) {
            // find best match for the value of "name"
            if (preg_match_all('{'.self::$DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) {
                $bestMatch = '';
                foreach ($matches[0] as $match) {
                    if (strlen($bestMatch) < strlen($match)) {
                        $bestMatch = $match;
                    }
                }
                $childrenClean = preg_replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count);
                if (1 !== $count) {
                    $childrenClean = preg_replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count);
                    if (1 !== $count) {
                        return false;
                    }
                }
            }
        } else {
            $childrenClean = $children;
        }

        // no child data left, $name was the only key in
        $this->pregMatch('#^{ \s*? (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx', $childrenClean, $match);
        if (empty($match['content'])) {
            $newline = $this->newline;
            $indent = $this->indent;

            $this->contents = preg_replace_callback($nodeRegex, function ($matches) use ($indent, $newline) {
                return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end'];
            }, $this->contents);

            // we have a subname, so we restore the rest of $name
            if ($subName !== null) {
                $curVal = json_decode($children, true);
                unset($curVal[$name][$subName]);
                $this->addSubNode($mainNode, $name, $curVal[$name]);
            }

            return true;
        }

        $that = $this;
        $this->contents = preg_replace_callback($nodeRegex, function ($matches) use ($that, $name, $subName, $childrenClean) {
            if ($subName !== null) {
                $curVal = json_decode($matches['content'], true);
                unset($curVal[$name][$subName]);
                $childrenClean = $that->format($curVal, 0);
            }

            return $matches['start'] . $childrenClean . $matches['end'];
        }, $this->contents);

        return true;
    }

    public function addMainKey($key, $content)
    {
        $decoded = JsonFile::parseJson($this->contents);
        $content = $this->format($content);

        // key exists already
        $regex = '{'.self::$DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<key>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P<end>.*)}sx';
        if (isset($decoded[$key]) && $this->pregMatch($regex, $this->contents, $matches)) {
            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['key'].'}')) {
                return false;
            }

            $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end'];

            return true;
        }

        // append at the end of the file and keep whitespace
        if ($this->pregMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) {
            $this->contents = preg_replace(
                '#'.$match[1].'\}$#',
                addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'),
                $this->contents
            );

            return true;
        }

        // append at the end of the file
        $this->contents = preg_replace(
            '#\}$#',
            addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'),
            $this->contents
        );

        return true;
    }

    public function removeMainKey($key)
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        // key exists already
        $regex = '{'.self::$DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<removal>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P<end>.*)}sx';
        if ($this->pregMatch($regex, $this->contents, $matches)) {
            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['removal'].'}')) {
                return false;
            }

            // check that we are not leaving a dangling comma on the previous line if the last line was removed
            if (preg_match('#,\s*$#', $matches['start']) && preg_match('#^\}$#', $matches['end'])) {
                $matches['start'] = rtrim(preg_replace('#,(\s*)$#', '$1', $matches['start']), $this->indent);
            }

            $this->contents = $matches['start'] . $matches['end'];
            if (preg_match('#^\{\s*\}\s*$#', $this->contents)) {
                $this->contents = "{\n}";
            }

            return true;
        }

        return false;
    }

    public function format($data, $depth = 0)
    {
        if (is_array($data)) {
            reset($data);

            if (is_numeric(key($data))) {
                foreach ($data as $key => $val) {
                    $data[$key] = $this->format($val, $depth + 1);
                }

                return '['.implode(', ', $data).']';
            }

            $out = '{' . $this->newline;
            $elems = array();
            foreach ($data as $key => $val) {
                $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode($key). ': '.$this->format($val, $depth + 1);
            }

            return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}';
        }

        return JsonFile::encode($data);
    }

    protected function detectIndenting()
    {
        if ($this->pregMatch('{^([ \t]+)"}m', $this->contents, $match)) {
            $this->indent = $match[1];
        } else {
            $this->indent = '    ';
        }
    }

    protected function pregMatch($re, $str, &$matches = array())
    {
        $count = preg_match($re, $str, $matches);

        if ($count === false) {
            switch (preg_last_error()) {
                case PREG_NO_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_NO_ERROR', PREG_NO_ERROR);
                case PREG_INTERNAL_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_INTERNAL_ERROR', PREG_INTERNAL_ERROR);
                case PREG_BACKTRACK_LIMIT_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_BACKTRACK_LIMIT_ERROR', PREG_BACKTRACK_LIMIT_ERROR);
                case PREG_RECURSION_LIMIT_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_RECURSION_LIMIT_ERROR', PREG_RECURSION_LIMIT_ERROR);
                case PREG_BAD_UTF8_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_BAD_UTF8_ERROR', PREG_BAD_UTF8_ERROR);
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    throw new \RuntimeException('Failed to execute regex: PREG_BAD_UTF8_OFFSET_ERROR', PREG_BAD_UTF8_OFFSET_ERROR);
                case 6: // PREG_JIT_STACKLIMIT_ERROR
                    if (PHP_VERSION_ID > 70000) {
                        throw new \RuntimeException('Failed to execute regex: PREG_JIT_STACKLIMIT_ERROR', 6);
                    }
                    // no break
                default:
                    throw new \RuntimeException('Failed to execute regex: Unknown error');
            }
        }

        return $count;
    }
}
<?php

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

namespace Composer\Json;

use Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonValidationException extends Exception
{
    protected $errors;

    public function __construct($message, $errors = array(), Exception $previous = null)
    {
        $this->errors = $errors;
        parent::__construct($message, 0, $previous);
    }

    public function getErrors()
    {
        return $this->errors;
    }
}
<?php

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

namespace Composer\Json;

/**
 * Formats json strings used for php < 5.4 because the json_encode doesn't
 * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
 * in these versions
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFormatter
{
    /**
     * This code is based on the function found at:
     *  http://recursive-design.com/blog/2008/03/11/format-json-with-php/
     *
     * Originally licensed under MIT by Dave Perrett <mail@recursive-design.com>
     *
     *
     * @param  string $json
     * @param  bool   $unescapeUnicode Un escape unicode
     * @param  bool   $unescapeSlashes Un escape slashes
     * @return string
     */
    public static function format($json, $unescapeUnicode, $unescapeSlashes)
    {
        $result = '';
        $pos = 0;
        $strLen = strlen($json);
        $indentStr = '    ';
        $newLine = "\n";
        $outOfQuotes = true;
        $buffer = '';
        $noescape = true;

        for ($i = 0; $i < $strLen; $i++) {
            // Grab the next character in the string
            $char = substr($json, $i, 1);

            // Are we inside a quoted string?
            if ('"' === $char && $noescape) {
                $outOfQuotes = !$outOfQuotes;
            }

            if (!$outOfQuotes) {
                $buffer .= $char;
                $noescape = '\\' === $char ? !$noescape : true;
                continue;
            } elseif ('' !== $buffer) {
                if ($unescapeSlashes) {
                    $buffer = str_replace('\\/', '/', $buffer);
                }

                if ($unescapeUnicode && function_exists('mb_convert_encoding')) {
                    // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
                    $buffer = preg_replace_callback('/(\\\\+)u([0-9a-f]{4})/i', function ($match) {
                        $l = strlen($match[1]);

                        if ($l % 2) {
                            $code = hexdec($match[2]);
                            // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped
                            // see https://github.com/composer/composer/issues/7510
                            if (0xD800 <= $code && 0xDFFF >= $code) {
                                return $match[0];
                            }

                            return str_repeat('\\', $l - 1) . mb_convert_encoding(
                                pack('H*', $match[2]),
                                'UTF-8',
                                'UCS-2BE'
                            );
                        }

                        return $match[0];
                    }, $buffer);
                }

                $result .= $buffer.$char;
                $buffer = '';
                continue;
            }

            if (':' === $char) {
                // Add a space after the : character
                $char .= ' ';
            } elseif ('}' === $char || ']' === $char) {
                $pos--;
                $prevChar = substr($json, $i - 1, 1);

                if ('{' !== $prevChar && '[' !== $prevChar) {
                    // If this character is the end of an element,
                    // output a new line and indent the next line
                    $result .= $newLine;
                    for ($j = 0; $j < $pos; $j++) {
                        $result .= $indentStr;
                    }
                } else {
                    // Collapse empty {} and []
                    $result = rtrim($result);
                }
            }

            $result .= $char;

            // If the last character was the beginning of an element,
            // output a new line and indent the next line
            if (',' === $char || '{' === $char || '[' === $char) {
                $result .= $newLine;

                if ('{' === $char || '[' === $char) {
                    $pos++;
                }

                for ($j = 0; $j < $pos; $j++) {
                    $result .= $indentStr;
                }
            }
        }

        return $result;
    }
}
<?php

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

namespace Composer\Json;

use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * Reads/writes json files.
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFile
{
    const LAX_SCHEMA = 1;
    const STRICT_SCHEMA = 2;

    const JSON_UNESCAPED_SLASHES = 64;
    const JSON_PRETTY_PRINT = 128;
    const JSON_UNESCAPED_UNICODE = 256;

    const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json';

    private $path;
    private $rfs;
    private $io;

    /**
     * Initializes json file reader/parser.
     *
     * @param  string                    $path path to a lockfile
     * @param  RemoteFilesystem          $rfs  required for loading http/https json files
     * @param  IOInterface               $io
     * @throws \InvalidArgumentException
     */
    public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
    {
        $this->path = $path;

        if (null === $rfs && preg_match('{^https?://}i', $path)) {
            throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
        }
        $this->rfs = $rfs;
        $this->io = $io;
    }

    /**
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Checks whether json file exists.
     *
     * @return bool
     */
    public function exists()
    {
        return is_file($this->path);
    }

    /**
     * Reads json file.
     *
     * @throws \RuntimeException
     * @return mixed
     */
    public function read()
    {
        try {
            if ($this->rfs) {
                $json = $this->rfs->getContents($this->path, $this->path, false);
            } else {
                if ($this->io && $this->io->isDebug()) {
                    $this->io->writeError('Reading ' . $this->path);
                }
                $json = file_get_contents($this->path);
            }
        } catch (TransportException $e) {
            throw new \RuntimeException($e->getMessage(), 0, $e);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage());
        }

        return static::parseJson($json, $this->path);
    }

    /**
     * Writes json file.
     *
     * @param  array                                $hash    writes hash into json file
     * @param  int                                  $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @throws \UnexpectedValueException|\Exception
     */
    public function write(array $hash, $options = 448)
    {
        $dir = dirname($this->path);
        if (!is_dir($dir)) {
            if (file_exists($dir)) {
                throw new \UnexpectedValueException(
                    $dir.' exists and is not a directory.'
                );
            }
            if (!@mkdir($dir, 0777, true)) {
                throw new \UnexpectedValueException(
                    $dir.' does not exist and could not be created.'
                );
            }
        }

        $retries = 3;
        while ($retries--) {
            try {
                $this->filePutContentsIfModified($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : ''));
                break;
            } catch (\Exception $e) {
                if ($retries) {
                    usleep(500000);
                    continue;
                }

                throw $e;
            }
        }
    }

    /**
     * modify file properties only if content modified
     */
    private function filePutContentsIfModified($path, $content)
    {
        $currentContent = @file_get_contents($path);
        if (!$currentContent || ($currentContent != $content)) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  int                     $schema a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @return bool                    true on success
     */
    public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null)
    {
        $content = file_get_contents($this->path);
        $data = json_decode($content);

        if (null === $data && 'null' !== $content) {
            self::validateSyntax($content, $this->path);
        }

        if (null === $schemaFile) {
            $schemaFile = __DIR__ . self::COMPOSER_SCHEMA_PATH;
        }

        // Prepend with file:// only when not using a special schema already (e.g. in the phar)
        if (false === strpos($schemaFile, '://')) {
            $schemaFile = 'file://' . $schemaFile;
        }

        $schemaData = (object) array('$ref' => $schemaFile);

        if ($schema === self::LAX_SCHEMA) {
            $schemaData->additionalProperties = true;
            $schemaData->required = array();
        }

        $validator = new Validator();
        $validator->check($data, $schemaData);

        // TODO add more validation like check version constraints and such, perhaps build that into the arrayloader?

        if (!$validator->isValid()) {
            $errors = array();
            foreach ((array) $validator->getErrors() as $error) {
                $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
            }
            throw new JsonValidationException('"'.$this->path.'" does not match the expected JSON schema', $errors);
        }

        return true;
    }

    /**
     * Encodes an array into (optionally pretty-printed) JSON
     *
     * @param  mixed  $data    Data to encode into a formatted JSON string
     * @param  int    $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @return string Encoded json
     */
    public static function encode($data, $options = 448)
    {
        if (PHP_VERSION_ID >= 50400) {
            $json = json_encode($data, $options);
            if (false === $json) {
                self::throwEncodeError(json_last_error());
            }

            //  compact brackets to follow recent php versions
            if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512) || (defined('JSON_C_VERSION') && version_compare(phpversion('json'), '1.3.6', '<'))) {
                $json = preg_replace('/\[\s+\]/', '[]', $json);
                $json = preg_replace('/\{\s+\}/', '{}', $json);
            }

            return $json;
        }

        $json = json_encode($data);
        if (false === $json) {
            self::throwEncodeError(json_last_error());
        }

        $prettyPrint = (bool) ($options & self::JSON_PRETTY_PRINT);
        $unescapeUnicode = (bool) ($options & self::JSON_UNESCAPED_UNICODE);
        $unescapeSlashes = (bool) ($options & self::JSON_UNESCAPED_SLASHES);

        if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) {
            return $json;
        }

        return JsonFormatter::format($json, $unescapeUnicode, $unescapeSlashes);
    }

    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @throws \RuntimeException
     */
    private static function throwEncodeError($code)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }

        throw new \RuntimeException('JSON encoding failed: '.$msg);
    }

    /**
     * Parses json string and returns hash.
     *
     * @param string $json json string
     * @param string $file the json file
     *
     * @return mixed
     */
    public static function parseJson($json, $file = null)
    {
        if (null === $json) {
            return;
        }
        $data = json_decode($json, true);
        if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
            self::validateSyntax($json, $file);
        }

        return $data;
    }

    /**
     * Validates the syntax of a JSON string
     *
     * @param  string                    $json
     * @param  string                    $file
     * @throws \UnexpectedValueException
     * @throws ParsingException
     * @return bool                      true on success
     */
    protected static function validateSyntax($json, $file = null)
    {
        $parser = new JsonParser();
        $result = $parser->lint($json);
        if (null === $result) {
            if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
                throw new \UnexpectedValueException('"'.$file.'" is not UTF-8, could not parse as JSON');
            }

            return true;
        }

        throw new ParsingException('"'.$file.'" does not contain valid JSON'."\n".$result->getMessage(), $result->getDetails());
    }
}
<?php

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

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

namespace Composer\Autoload;

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

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

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

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

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param \Iterator|string $path         The path to search in or an iterator
     * @param string           $excluded     Regex that matches file paths to be excluded from the classmap
     * @param IOInterface      $io           IO object
     * @param string           $namespace    Optional namespace prefix to filter by
     * @param string           $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     * @return array             A class map array
     */
    public static function createMap($path, $excluded = null, IOInterface $io = null, $namespace = null, $autoloadType = null, &$scannedFiles = array())
    {
        if (is_string($path)) {
            $basePath = $path;
            if (is_file($path)) {
                $path = array(new \SplFileInfo($path));
            } elseif (is_dir($path)) {
                $path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path);
            } else {
                throw new \RuntimeException(
                    'Could not scan for classes inside "'.$path.
                    '" which does not appear to be a file nor a folder'
                );
            }
        } elseif (null !== $autoloadType) {
            throw new \RuntimeException('Path must be a string when specifying an autoload type');
        }

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

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

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

            $realPath = realpath($filePath);

            // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings
            // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already
            if (isset($scannedFiles[$realPath])) {
                continue;
            }

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

            $classes = self::findClasses($filePath);
            if (null !== $autoloadType) {
                list($classes, $validClasses) = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);

                // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
                if ($validClasses) {
                    $scannedFiles[$realPath] = true;
                }
            } else {
                // classmap autoload rules always collect all classes so for these we definitely do not want to scan again
                $scannedFiles[$realPath] = true;
            }

            foreach ($classes as $class) {
                // skip classes not within the given namespace prefix
                // TODO enable in Composer v1.11 or 2.0 whichever comes first
                if (/* null === $autoloadType && */ null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
                    continue;
                }

                if (!isset($map[$class])) {
                    $map[$class] = $filePath;
                } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class].' '.$filePath, '\\', '/'))) {
                    $io->writeError(
                        '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        ' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.</warning>'
                    );
                }
            }
        }

        return $map;
    }

    /**
     * Remove classes which could not have been loaded by namespace autoloaders
     *
     * @param array       $classes       found classes in given file
     * @param string      $filePath      current file
     * @param string      $baseNamespace prefix of given autoload mapping
     * @param string      $namespaceType psr-0|psr-4
     * @param string      $basePath      root directory of given autoload mapping
     * @param IOInterface $io            IO object
     * @return array      valid classes
     */
    private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
    {
        $validClasses = array();
        $rejectedClasses = array();

        $realSubPath = substr($filePath, strlen($basePath) + 1);
        $realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));

        foreach ($classes as $class) {
            // silently skip if ns doesn't have common root
            if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
                continue;
            }
            // transform class name to file path and validate
            if ('psr-0' === $namespaceType) {
                $namespaceLength = strrpos($class, '\\');
                if (false !== $namespaceLength) {
                    $namespace = substr($class, 0, $namespaceLength + 1);
                    $className = substr($class, $namespaceLength + 1);
                    $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
                        . str_replace('_', DIRECTORY_SEPARATOR, $className);
                }
                else {
                    $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
                }
            } elseif ('psr-4' === $namespaceType) {
                $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
                $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
            } else {
                throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
            }
            if ($subPath === $realSubPath) {
                $validClasses[] = $class;
            } else {
                $rejectedClasses[] = $class;
            }
        }
        // warn only if no valid classes, else silently skip invalid
        if (empty($validClasses)) {
            foreach ($rejectedClasses as $class) {
                trigger_error(
                    "Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v2.0.",
                    E_USER_DEPRECATED
                );
            }

            // TODO enable in Composer 2.0
            //return array();
        }

        // TODO enable in Composer 2.0 & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
        //return $validClasses;
        return array($classes, $validClasses);
    }

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

        // Use @ here instead of Silencer to actively suppress 'unhelpful' output
        // @link https://github.com/composer/composer/pull/4886
        $contents = @php_strip_whitespace($path);
        if (!$contents) {
            if (!file_exists($path)) {
                $message = 'File at "%s" does not exist, check your classmap definitions';
            } elseif (!is_readable($path)) {
                $message = 'File at "%s" is not readable, check its permissions';
            } elseif ('' === trim(file_get_contents($path))) {
                // The input file was really empty and thus contains no classes
                return array();
            } else {
                $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
            }
            $error = error_get_last();
            if (isset($error['message'])) {
                $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
            }
            throw new \RuntimeException(sprintf($message, $path));
        }

        // return early if there is no chance of matching anything in this file
        if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) {
            return array();
        }

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

        preg_match_all('{
            (?:
                 \b(?<![\$:>])(?P<type>class|interface'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
               | \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
            )
        }ix', $contents, $matches);

        $classes = array();
        $namespace = '';

        for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
            if (!empty($matches['ns'][$i])) {
                $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
            } else {
                $name = $matches['name'][$i];
                // skip anon classes extending/implementing
                if ($name === 'extends' || $name === 'implements') {
                    continue;
                }
                if ($name[0] === ':') {
                    // This is an XHP class, https://github.com/facebook/xhp
                    $name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
                } elseif ($matches['type'][$i] === 'enum') {
                    // In Hack, something like:
                    //   enum Foo: int { HERP = '123'; }
                    // The regex above captures the colon, which isn't part of
                    // the class name.
                    $name = rtrim($name, ':');
                }
                $classes[] = ltrim($namespace . $name, '\\');
            }
        }

        return $classes;
    }
}
<?php

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

namespace Composer\Autoload;

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

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

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

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

        return array();
    }

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

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

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

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

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

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

            return;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return true;
        }
    }

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

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

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

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

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

        return $file;
    }

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

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

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

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

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

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

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

        return false;
    }
}

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

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

namespace Composer\Autoload;

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

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

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

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

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

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

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

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

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

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

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

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

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

        $filesystem = new Filesystem();
        $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
        // Do not remove double realpath() calls.
        // Fixes failing Windows realpath() implementation.
        // See https://bugs.php.net/bug.php?id=72738
        $basePath = $filesystem->normalizePath(realpath(realpath(getcwd())));
        $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
        $useGlobalIncludePath = (bool) $config->get('use-include-path');
        $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
        $targetDir = $vendorPath.'/'.$targetDir;
        $filesystem->ensureDirectoryExists($targetDir);

        $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
        $vendorPathCode52 = str_replace('__DIR__', 'dirname(__FILE__)', $vendorPathCode);
        $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);

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

        $namespacesFile = <<<EOF
<?php

// autoload_namespaces.php @generated by Composer

\$vendorDir = $vendorPathCode52;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        $psr4File = <<<EOF
<?php

// autoload_psr4.php @generated by Composer

\$vendorDir = $vendorPathCode52;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        // Collect information from all packages.
        $packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages());
        $autoloads = $this->parseAutoloads($packageMap, $mainPackage, $this->devMode === false);

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

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

        $classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = $vendorPathCode52;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        // add custom psr-0 autoloading if the root package has a target dir
        $targetDirLoader = null;
        $mainAutoload = $mainPackage->getAutoload();
        if ($mainPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) {
            $levels = substr_count($filesystem->normalizePath($mainPackage->getTargetDir()), '/') + 1;
            $prefixes = implode(', ', array_map(function ($prefix) {
                return var_export($prefix, true);
            }, array_keys($mainAutoload['psr-0'])));
            $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true);

            $targetDirLoader = <<<EOF

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

            return true;
        }
    }

EOF;
        }

        $excluded = null;
        if (!empty($autoloads['exclude-from-classmap'])) {
            $excluded = $autoloads['exclude-from-classmap'];
        }

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

        if ($scanPsrPackages) {
            $namespacesToScan = array();

            // Scan the PSR-0/4 directories for class files, and add them to the class map
            foreach (array('psr-4', 'psr-0') as $psrType) {
                foreach ($autoloads[$psrType] as $namespace => $paths) {
                    $namespacesToScan[$namespace][] = array('paths' => $paths, 'type' => $psrType);
                }
            }

            krsort($namespacesToScan);

            foreach ($namespacesToScan as $namespace => $groups) {
                foreach ($groups as $group) {
                    foreach ($group['paths'] as $dir) {
                        $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir);
                        if (!is_dir($dir)) {
                            continue;
                        }

                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $excluded, $namespace, $group['type'], $classMap, $ambiguousClasses, $scannedFiles);
                    }
                }
            }
        }

        foreach ($ambiguousClasses as $className => $ambigiousPaths) {
            $cleanPath = str_replace(array('$vendorDir . \'', '$baseDir . \'', "',\n"), array($vendorPath, $basePath, ''), $classMap[$className]);

            $this->io->writeError(
                '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                ' was found '. (count($ambigiousPaths) + 1) .'x: in "'.$cleanPath.'" and "'. implode('", "', $ambigiousPaths) .'", the first will be used.</warning>'
            );
        }

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

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

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

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

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

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

        return count($classMap);
    }

    private function filePutContentsIfModified($path, $content)
    {
        $currentContent = @file_get_contents($path);
        if (!$currentContent || ($currentContent != $content)) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $excluded, $namespaceFilter, $autoloadType, array $classMap, array &$ambiguousClasses, array &$scannedFiles)
    {
        foreach ($this->generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, true, $scannedFiles) as $class => $path) {
            $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
            if (!isset($classMap[$class])) {
                $classMap[$class] = $pathCode;
            } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) {
                $ambiguousClasses[$class][] = $path;
            }
        }

        return $classMap;
    }

    /**
     * @param ?array $excluded
     */
    private function generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, $showAmbiguousWarning, array &$scannedFiles)
    {
        if ($excluded) {
            // filter excluded patterns here to only use those matching $dir
            // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
            // if $dir does not exist, it should anyway not find anything there so no trouble
            if (file_exists($dir)) {
                // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
                $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
                foreach ($excluded as $index => $pattern) {
                    // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
                    $pattern = preg_replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
                    // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
                    if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) {
                        unset($excluded[$index]);
                    }
                }
            }

            $excluded = $excluded ? '{(' . implode('|', $excluded) . ')}' : null;
        }

        return ClassMapGenerator::createMap($dir, $excluded, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles);
    }

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

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

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

        return $packageMap;
    }

    /**
     * @param PackageInterface $package
     *
     * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings.
     */
    protected function validatePackage(PackageInterface $package)
    {
        $autoload = $package->getAutoload();
        if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) {
            $name = $package->getName();
            $package->getTargetDir();
            throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'.");
        }
        if (!empty($autoload['psr-4'])) {
            foreach ($autoload['psr-4'] as $namespace => $dirs) {
                if ($namespace !== '' && '\\' !== substr($namespace, -1)) {
                    throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'.");
                }
            }
        }
    }

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

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

        krsort($psr0);
        krsort($psr4);

        return array(
            'psr-0' => $psr0,
            'psr-4' => $psr4,
            'classmap' => $classmap,
            'files' => $files,
            'exclude-from-classmap' => $exclude,
        );
    }

    /**
     * Registers an autoloader based on an autoload map returned by parseAutoloads
     *
     * @param  array       $autoloads see parseAutoloads return value
     * @return ClassLoader
     */
    public function createLoader(array $autoloads)
    {
        $loader = new ClassLoader();

        if (isset($autoloads['psr-0'])) {
            foreach ($autoloads['psr-0'] as $namespace => $path) {
                $loader->add($namespace, $path);
            }
        }

        if (isset($autoloads['psr-4'])) {
            foreach ($autoloads['psr-4'] as $namespace => $path) {
                $loader->addPsr4($namespace, $path);
            }
        }

        if (isset($autoloads['classmap'])) {
            $excluded = null;
            if (!empty($autoloads['exclude-from-classmap'])) {
                $excluded = $autoloads['exclude-from-classmap'];
            }

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

        return $loader;
    }

    protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
    {
        $includePaths = array();

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

            if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

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

        if (!$includePaths) {
            return;
        }

        $includePathsCode = '';
        foreach ($includePaths as $path) {
            $includePathsCode .= "    " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n";
        }

        return <<<EOF
<?php

// include_paths.php @generated by Composer

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

return array(
$includePathsCode);

EOF;
    }

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

        if (!$filesCode) {
            return false;
        }

        return <<<EOF
<?php

// autoload_files.php @generated by Composer

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

return array(
$filesCode);

EOF;
    }

    protected function getPathCode(Filesystem $filesystem, $basePath, $vendorPath, $path)
    {
        if (!$filesystem->isAbsolutePath($path)) {
            $path = $basePath . '/' . $path;
        }
        $path = $filesystem->normalizePath($path);

        $baseDir = '';
        if (strpos($path.'/', $vendorPath.'/') === 0) {
            $path = substr($path, strlen($vendorPath));
            $baseDir = '$vendorDir';

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

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

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

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

        return <<<AUTOLOAD
<?php

// autoload.php @generated by Composer

require_once $vendorPathToTargetDirCode;

return ComposerAutoloaderInit$suffix::getLoader();

AUTOLOAD;
    }

    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion = 70000)
    {
        $file = <<<HEADER
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit$suffix
{
    private static \$loader;

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

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

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


HEADER;

        if ($useIncludePath) {
            $file .= <<<'INCLUDE_PATH'
        $includePaths = require __DIR__ . '/include_paths.php';
        $includePaths[] = get_include_path();
        set_include_path(implode(PATH_SEPARATOR, $includePaths));


INCLUDE_PATH;
        }

        $file .= <<<STATIC_INIT
        \$useStaticLoader = PHP_VERSION_ID >= $staticPhpVersion && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if (\$useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit$suffix::getInitializer(\$loader));
        } else {

STATIC_INIT;

        if (!$this->classMapAuthoritative) {
            $file .= <<<'PSR04'
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }


PSR04;
        }

        if ($useClassMap) {
            $file .= <<<'CLASSMAP'
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }

CLASSMAP;
        }

        $file .= "        }\n\n";

        if ($this->classMapAuthoritative) {
            $file .= <<<'CLASSMAPAUTHORITATIVE'
        $loader->setClassMapAuthoritative(true);

CLASSMAPAUTHORITATIVE;
        }

        if ($this->apcu) {
            $apcuPrefix = substr(base64_encode(md5(uniqid('', true), true)), 0, -3);
            $file .= <<<APCU
        \$loader->setApcuPrefix('$apcuPrefix');

APCU;
        }

        if ($useGlobalIncludePath) {
            $file .= <<<'INCLUDEPATH'
        $loader->setUseIncludePath(true);

INCLUDEPATH;
        }

        if ($targetDirLoader) {
            $file .= <<<REGISTER_TARGET_DIR_AUTOLOAD
        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'autoload'), true, true);


REGISTER_TARGET_DIR_AUTOLOAD;
        }

        $file .= <<<REGISTER_LOADER
        \$loader->register($prependAutoloader);


REGISTER_LOADER;

        if ($useIncludeFiles) {
            $file .= <<<INCLUDE_FILES
        if (\$useStaticLoader) {
            \$includeFiles = Composer\Autoload\ComposerStaticInit$suffix::\$files;
        } else {
            \$includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach (\$includeFiles as \$fileIdentifier => \$file) {
            composerRequire$suffix(\$fileIdentifier, \$file);
        }


INCLUDE_FILES;
        }

        $file .= <<<METHOD_FOOTER
        return \$loader;
    }

METHOD_FOOTER;

        $file .= $targetDirLoader;

        if ($useIncludeFiles) {
            return $file . <<<FOOTER
}

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

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

FOOTER;
        }

        return $file . <<<FOOTER
}

FOOTER;
    }

    protected function getStaticFile($suffix, $targetDir, $vendorPath, $basePath, &$staticPhpVersion)
    {
        $staticPhpVersion = 50600;

        $file = <<<HEADER
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit$suffix
{

HEADER;

        $loader = new ClassLoader();

        $map = require $targetDir . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

        $map = require $targetDir . '/autoload_psr4.php';
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

        $classMap = require $targetDir . '/autoload_classmap.php';
        if ($classMap) {
            $loader->addClassMap($classMap);
        }

        $filesystem = new Filesystem();

        $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
        $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";

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

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

        foreach ((array) $loader as $prop => $value) {
            if ($value && 0 === strpos($prop, $prefix)) {
                $maps[substr($prop, $prefixLen)] = $value;
            }
        }

        foreach ($maps as $prop => $value) {
            if (count($value) > 32767) {
                // Static arrays are limited to 32767 values on PHP 5.6
                // See https://bugs.php.net/68057
                $staticPhpVersion = 70000;
            }
            $value = strtr(
                var_export($value, true),
                array(
                    $absoluteVendorPathCode => $vendorPathCode,
                    $absoluteVendorPharPathCode => $vendorPharPathCode,
                    $absoluteAppBaseDirCode => $appBaseDirCode,
                    $absoluteAppBaseDirPharCode => $appBaseDirPharCode,
                )
            );
            $value = ltrim(preg_replace('/^ */m', '    $0$0', $value));

            $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
            if ('files' !== $prop) {
                $initializer .= "            \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n";
            }
        }

        return $file . <<<INITIALIZER
    public static function getInitializer(ClassLoader \$loader)
    {
        return \Closure::bind(function () use (\$loader) {
$initializer
        }, null, ClassLoader::class);
    }
}

INITIALIZER;
    }

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

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

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

            // skip misconfigured packages
            if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
                continue;
            }
            if (null !== $package->getTargetDir() && $package !== $mainPackage) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($autoload[$type] as $namespace => $paths) {
                foreach ((array) $paths as $path) {
                    if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
                        // remove target-dir from file paths of the root package
                        if ($package === $mainPackage) {
                            $targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $package->getTargetDir())));
                            $path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
                        } else {
                            // add target-dir from file paths that don't have it
                            $path = $package->getTargetDir() . '/' . $path;
                        }
                    }

                    if ($type === 'exclude-from-classmap') {
                        // first escape user input
                        $path = preg_replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/')));

                        // add support for wildcards * and **
                        $path = str_replace('\\*\\*', '.+?', $path);
                        $path = str_replace('\\*', '[^/]+?', $path);

                        // add support for up-level relative paths
                        $updir = null;
                        $path = preg_replace_callback(
                            '{^((?:(?:\\\\\\.){1,2}+/)+)}',
                            function ($matches) use (&$updir) {
                                if (isset($matches[1])) {
                                    // undo preg_quote for the matched string
                                    $updir = str_replace('\\.', '.', $matches[1]);
                                }

                                return '';
                            },
                            $path
                        );
                        if (empty($installPath)) {
                            $installPath = strtr(getcwd(), '\\', '/');
                        }

                        $resolvedPath = realpath($installPath . '/' . $updir);
                        $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)';
                        continue;
                    }

                    $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path;

                    if ($type === 'files') {
                        $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath;
                        continue;
                    } elseif ($type === 'classmap') {
                        $autoloads[] = $relativePath;
                        continue;
                    }

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

        return $autoloads;
    }

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

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

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

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

        return array_filter(
            $packageMap,
            function ($item) use ($include) {
                $package = $item[0];
                foreach ($package->getNames() as $name) {
                    if (isset($include[$name])) {
                        return true;
                    }
                }

                return false;
            }
        );
    }

    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight retain the original order
     *
     * @param  array $packageMap
     * @return array
     */
    protected function sortPackageMap(array $packageMap)
    {
        $packages = array();
        $paths = array();

        foreach ($packageMap as $item) {
            list($package, $path) = $item;
            $name = $package->getName();
            $packages[$name] = $package;
            $paths[$name] = $path;
        }

        $sortedPackages = PackageSorter::sortPackages($packages);

        $sortedPackageMap = array();

        foreach ($sortedPackages as $package) {
            $name = $package->getName();
            $sortedPackageMap[] = array($packages[$name], $paths[$name]);
        }

        return $sortedPackageMap;
    }

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

            stream_copy_to_stream($source, $target);
            fclose($source);
            fclose($target);
        }
    }

    /**
     * compare 2 files
     * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files
     */
    private function filesAreEqual($a, $b)
    {
        // Check if filesize is different
        if (filesize($a) !== filesize($b)) {
            return false;
        }

        // Check if content is different
        $ah = fopen($a, 'rb');
        $bh = fopen($b, 'rb');

        $result = true;
        while (!feof($ah)) {
            if (fread($ah, 8192) != fread($bh, 8192)) {
                $result = false;
                break;
            }
        }

        fclose($ah);
        fclose($bh);

        return $result;
    }
}
<?php

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

namespace Composer;

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

/**
 * Reads/writes to a filesystem cache
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Cache
{
    private static $cacheCollected = false;
    private $io;
    private $root;
    private $enabled = true;
    private $allowList;
    private $filesystem;

    /**
     * @param IOInterface $io
     * @param string      $cacheDir   location of the cache
     * @param string      $allowList  List of characters that are allowed in path names (used in a regex character class)
     * @param Filesystem  $filesystem optional filesystem instance
     */
    public function __construct(IOInterface $io, $cacheDir, $allowList = 'a-z0-9.', Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->root = rtrim($cacheDir, '/\\') . '/';
        $this->allowList = $allowList;
        $this->filesystem = $filesystem ?: new Filesystem();

        if (!self::isUsable($cacheDir)) {
            $this->enabled = false;

            return;
        }

        if (
            (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true))
            || !is_writable($this->root)
        ) {
            $this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache</warning>');
            $this->enabled = false;
        }
    }

    public static function isUsable($path)
    {
        return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
    }

    public function isEnabled()
    {
        return $this->enabled;
    }

    public function getRoot()
    {
        return $this->root;
    }

    public function read($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return file_get_contents($this->root . $file);
            }
        }

        return false;
    }

    public function write($file, $contents)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);

            $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);

            try {
                return file_put_contents($this->root . $file, $contents);
            } catch (\ErrorException $e) {
                $this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
                if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
                    // Remove partial file.
                    unlink($this->root . $file);

                    $message = sprintf(
                        '<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available</warning>',
                        $this->root . $file,
                        $m[1],
                        $m[2],
                        @disk_free_space($this->root . dirname($file))
                    );

                    $this->io->writeError($message);

                    return false;
                }

                throw $e;
            }
        }

        return false;
    }

    /**
     * Copy a file into the cache
     */
    public function copyFrom($file, $source)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            $this->filesystem->ensureDirectoryExists(dirname($this->root . $file));

            if (!file_exists($source)) {
                $this->io->writeError('<error>'.$source.' does not exist, can not write into cache</error>');
            } elseif ($this->io->isDebug()) {
                $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source);
            }

            return copy($source, $this->root . $file);
        }

        return false;
    }

    /**
     * Copy a file out of the cache
     */
    public function copyTo($file, $target)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                try {
                    touch($this->root . $file, filemtime($this->root . $file), time());
                } catch (\ErrorException $e) {
                    // fallback in case the above failed due to incorrect ownership
                    // see https://github.com/composer/composer/issues/4070
                    Silencer::call('touch', $this->root . $file);
                }

                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return copy($this->root . $file, $target);
            }
        }

        return false;
    }

    public function gcIsNecessary()
    {
        return (!self::$cacheCollected && !mt_rand(0, 50));
    }

    public function remove($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return $this->filesystem->unlink($this->root . $file);
            }
        }

        return false;
    }

    public function clear()
    {
        if ($this->enabled) {
            $this->filesystem->emptyDirectory($this->root);
            return true;
        }

        return false;
    }

    public function gc($ttl, $maxSize)
    {
        if ($this->enabled) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->unlink($file->getPathname());
            }

            $totalSize = $this->filesystem->size($this->root);
            if ($totalSize > $maxSize) {
                $iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
                while ($totalSize > $maxSize && $iterator->valid()) {
                    $filepath = $iterator->current()->getPathname();
                    $totalSize -= $this->filesystem->size($filepath);
                    $this->filesystem->unlink($filepath);
                    $iterator->next();
                }
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    public function sha1($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return sha1_file($this->root . $file);
            }
        }

        return false;
    }

    public function sha256($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->allowList.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha256', $this->root . $file);
            }
        }

        return false;
    }

    protected function getFinder()
    {
        return Finder::create()->in($this->root)->files();
    }
}
<?php

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

namespace Composer;

use Composer\Json\JsonFile;
use Composer\Spdx\SpdxLicenses;
use Composer\CaBundle\CaBundle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use Seld\PharUtils\Timestamps;
use Seld\PharUtils\Linter;

/**
 * The Compiler class compiles composer into a phar
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Compiler
{
    private $version;
    private $branchAliasVersion = '';
    private $versionDate;

    /**
     * Compiles composer into a single phar file
     *
     * @param  string            $pharFile The full path to the file to create
     * @throws \RuntimeException
     */
    public function compile($pharFile = 'composer.phar')
    {
        if (file_exists($pharFile)) {
            unlink($pharFile);
        }

        $process = new Process('git log --pretty="%H" -n1 HEAD', __DIR__);
        if ($process->run() != 0) {
            throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }
        $this->version = trim($process->getOutput());

        $process = new Process('git log -n1 --pretty=%ci HEAD', __DIR__);
        if ($process->run() != 0) {
            throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }

        $this->versionDate = new \DateTime(trim($process->getOutput()));
        $this->versionDate->setTimezone(new \DateTimeZone('UTC'));

        $process = new Process('git describe --tags --exact-match HEAD');
        if ($process->run() == 0) {
            $this->version = trim($process->getOutput());
        } else {
            // get branch-alias defined in composer.json for dev-master (if any)
            $localConfig = __DIR__.'/../../composer.json';
            $file = new JsonFile($localConfig);
            $localConfig = $file->read();
            if (isset($localConfig['extra']['branch-alias']['dev-master'])) {
                $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-master'];
            }
        }

        $phar = new \Phar($pharFile, 0, 'composer.phar');
        $phar->setSignatureAlgorithm(\Phar::SHA1);

        $phar->startBuffering();

        $finderSort = function ($a, $b) {
            return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
        };

        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->notName('Compiler.php')
            ->notName('ClassLoader.php')
            ->in(__DIR__.'/..')
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false);

        $finder = new Finder();
        $finder->files()
            ->name('*.json')
            ->in(__DIR__.'/../../res')
            ->in(SpdxLicenses::getResourcesDir())
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file, false);
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe'), false);
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8'), false);

        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->name('LICENSE')
            ->exclude('Tests')
            ->exclude('tests')
            ->exclude('docs')
            ->in(__DIR__.'/../../vendor/symfony/')
            ->in(__DIR__.'/../../vendor/seld/jsonlint/')
            ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/')
            ->in(__DIR__.'/../../vendor/composer/spdx-licenses/')
            ->in(__DIR__.'/../../vendor/composer/semver/')
            ->in(__DIR__.'/../../vendor/composer/ca-bundle/')
            ->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
            ->in(__DIR__.'/../../vendor/psr/')
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }

        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_psr4.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_files.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_real.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_static.php'));
        if (file_exists(__DIR__.'/../../vendor/composer/include_paths.php')) {
            $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/include_paths.php'));
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php'));

        $this->addFile($phar, new \SplFileInfo(CaBundle::getBundledCaBundlePath()), false);

        $this->addComposerBin($phar);

        // Stubs
        $phar->setStub($this->getStub());

        $phar->stopBuffering();

        // disabled for interoperability with systems without gzip ext
        // $phar->compressFiles(\Phar::GZ);

        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false);

        unset($phar);

        // re-sign the phar with reproducible timestamp / signature
        $util = new Timestamps($pharFile);
        $util->updateTimestamps($this->versionDate);
        $util->save($pharFile, \Phar::SHA1);

        Linter::lint($pharFile);
    }

    /**
     * @param  \SplFileInfo $file
     * @return string
     */
    private function getRelativeFilePath($file)
    {
        $realPath = $file->getRealPath();
        $pathPrefix = dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR;

        $pos = strpos($realPath, $pathPrefix);
        $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath;

        return strtr($relativePath, '\\', '/');
    }

    private function addFile($phar, $file, $strip = true)
    {
        $path = $this->getRelativeFilePath($file);
        $content = file_get_contents($file);
        if ($strip) {
            $content = $this->stripWhitespace($content);
        } elseif ('LICENSE' === basename($file)) {
            $content = "\n".$content."\n";
        }

        if ($path === 'src/Composer/Composer.php') {
            $content = str_replace('@package_version@', $this->version, $content);
            $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
            $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content);
            $content = preg_replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
        }

        $phar->addFromString($path, $content);
    }

    private function addComposerBin($phar)
    {
        $content = file_get_contents(__DIR__.'/../../bin/composer');
        $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content);
        $phar->addFromString('bin/composer', $content);
    }

    /**
     * Removes whitespace from a PHP source string while preserving line numbers.
     *
     * @param  string $source A PHP string
     * @return string The PHP string with the whitespace removed
     */
    private function stripWhitespace($source)
    {
        if (!function_exists('token_get_all')) {
            return $source;
        }

        $output = '';
        foreach (token_get_all($source) as $token) {
            if (is_string($token)) {
                $output .= $token;
            } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
                $output .= str_repeat("\n", substr_count($token[1], "\n"));
            } elseif (T_WHITESPACE === $token[0]) {
                // reduce wide spaces
                $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
                // normalize newlines to \n
                $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
                // trim leading spaces
                $whitespace = preg_replace('{\n +}', "\n", $whitespace);
                $output .= $whitespace;
            } else {
                $output .= $token[1];
            }
        }

        return $output;
    }

    private function getStub()
    {
        $stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view
 * the license that is located at the bottom of this file.
 */

// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
    if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
        ini_set('apc.cache_by_default', 0);
    } else {
        fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
        fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
    }
}

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

EOF;

        // add warning once the phar is older than 60 days
        if (preg_match('{^[a-f0-9]+$}', $this->version)) {
            $warningTime = $this->versionDate->format('U') + 60 * 86400;
            $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n";
        }

        return $stub . <<<'EOF'
require 'phar://composer.phar/bin/composer';

__HALT_COMPILER();
EOF;
    }
}
<?php

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

namespace Composer;

use Composer\Package\RootPackageInterface;
use Composer\Package\Locker;
use Composer\Repository\RepositoryManager;
use Composer\Installer\InstallationManager;
use Composer\Plugin\PluginManager;
use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Archiver\ArchiveManager;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Composer
{
    /*
     * Examples of the following constants in the various configurations they can be in
     *
     * releases (phar):
     * const VERSION = '1.8.2';
     * const BRANCH_ALIAS_VERSION = '';
     * const RELEASE_DATE = '2019-01-29 15:00:53';
     * const SOURCE_VERSION = '';
     *
     * snapshot builds (phar):
     * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7';
     * const BRANCH_ALIAS_VERSION = '1.9-dev';
     * const RELEASE_DATE = '2019-02-20 07:43:56';
     * const SOURCE_VERSION = '';
     *
     * source (git clone):
     * const VERSION = '@package_version@';
     * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     * const RELEASE_DATE = '@release_date@';
     * const SOURCE_VERSION = '1.8-dev+source';
     */
    const VERSION = '1.10.24';
    const BRANCH_ALIAS_VERSION = '';
    const RELEASE_DATE = '2021-12-09 20:06:33';
    const SOURCE_VERSION = '';

    /**
     * Version number of the internal composer-runtime-api package
     *
     * This is used to version features available to projects at runtime
     * like the platform-check file, the Composer\InstalledVersions class
     * and possibly others in the future.
     *
     * @var string
     */
    const RUNTIME_API_VERSION = '1.0.0';

    public static function getVersion()
    {
        // no replacement done, this must be a source checkout
        if (self::VERSION === '@package_version'.'@') {
            return self::SOURCE_VERSION;
        }

        // we have a branch alias and version is a commit id, this must be a snapshot build
        if (self::BRANCH_ALIAS_VERSION !== '' && preg_match('{^[a-f0-9]{40}$}', self::VERSION)) {
            return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
        }

        return self::VERSION;
    }

    /**
     * @var Package\RootPackageInterface
     */
    private $package;

    /**
     * @var Locker
     */
    private $locker;

    /**
     * @var Repository\RepositoryManager
     */
    private $repositoryManager;

    /**
     * @var Downloader\DownloadManager
     */
    private $downloadManager;

    /**
     * @var Installer\InstallationManager
     */
    private $installationManager;

    /**
     * @var Plugin\PluginManager
     */
    private $pluginManager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var Autoload\AutoloadGenerator
     */
    private $autoloadGenerator;

    /**
     * @var ArchiveManager
     */
    private $archiveManager;

    /**
     * @param  Package\RootPackageInterface $package
     * @return void
     */
    public function setPackage(RootPackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * @return Package\RootPackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * @param Config $config
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @return Config
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * @param Package\Locker $locker
     */
    public function setLocker(Locker $locker)
    {
        $this->locker = $locker;
    }

    /**
     * @return Package\Locker
     */
    public function getLocker()
    {
        return $this->locker;
    }

    /**
     * @param Repository\RepositoryManager $manager
     */
    public function setRepositoryManager(RepositoryManager $manager)
    {
        $this->repositoryManager = $manager;
    }

    /**
     * @return Repository\RepositoryManager
     */
    public function getRepositoryManager()
    {
        return $this->repositoryManager;
    }

    /**
     * @param Downloader\DownloadManager $manager
     */
    public function setDownloadManager(DownloadManager $manager)
    {
        $this->downloadManager = $manager;
    }

    /**
     * @return Downloader\DownloadManager
     */
    public function getDownloadManager()
    {
        return $this->downloadManager;
    }

    /**
     * @param ArchiveManager $manager
     */
    public function setArchiveManager(ArchiveManager $manager)
    {
        $this->archiveManager = $manager;
    }

    /**
     * @return ArchiveManager
     */
    public function getArchiveManager()
    {
        return $this->archiveManager;
    }

    /**
     * @param Installer\InstallationManager $manager
     */
    public function setInstallationManager(InstallationManager $manager)
    {
        $this->installationManager = $manager;
    }

    /**
     * @return Installer\InstallationManager
     */
    public function getInstallationManager()
    {
        return $this->installationManager;
    }

    /**
     * @param Plugin\PluginManager $manager
     */
    public function setPluginManager(PluginManager $manager)
    {
        $this->pluginManager = $manager;
    }

    /**
     * @return Plugin\PluginManager
     */
    public function getPluginManager()
    {
        return $this->pluginManager;
    }

    /**
     * @param EventDispatcher $eventDispatcher
     */
    public function setEventDispatcher(EventDispatcher $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @return EventDispatcher
     */
    public function getEventDispatcher()
    {
        return $this->eventDispatcher;
    }

    /**
     * @param Autoload\AutoloadGenerator $autoloadGenerator
     */
    public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator)
    {
        $this->autoloadGenerator = $autoloadGenerator;
    }

    /**
     * @return Autoload\AutoloadGenerator
     */
    public function getAutoloadGenerator()
    {
        return $this->autoloadGenerator;
    }
}
<?php

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

namespace Composer\Script;

use Composer\Installer\PackageEvent as BasePackageEvent;

/**
 * The Package Event.
 *
 * @deprecated Use Composer\Installer\PackageEvent instead
 */
class PackageEvent extends BasePackageEvent
{
}
<?php

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

namespace Composer\Script;

/**
 * The Script Events.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptEvents
{
    /**
     * The PRE_INSTALL_CMD event occurs before the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const PRE_INSTALL_CMD = 'pre-install-cmd';

    /**
     * The POST_INSTALL_CMD event occurs after the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_INSTALL_CMD = 'post-install-cmd';

    /**
     * The PRE_UPDATE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const PRE_UPDATE_CMD = 'pre-update-cmd';

    /**
     * The POST_UPDATE_CMD event occurs after the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_UPDATE_CMD = 'post-update-cmd';

    /**
     * The PRE_STATUS_CMD event occurs before the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const PRE_STATUS_CMD = 'pre-status-cmd';

    /**
     * The POST_STATUS_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_STATUS_CMD = 'post-status-cmd';

    /**
     * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump';

    /**
     * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_AUTOLOAD_DUMP = 'post-autoload-dump';

    /**
     * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install';

    /**
     * The POST_CREATE_PROJECT event occurs after the create-project command has been executed.
     * Note: Event occurs after POST_INSTALL_CMD
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd';

    /**
     * The PRE_ARCHIVE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const PRE_ARCHIVE_CMD = 'pre-archive-cmd';

    /**
     * The POST_ARCHIVE_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    const POST_ARCHIVE_CMD = 'post-archive-cmd';

    /** Deprecated constants below */

    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_INSTALL instead.
     * @var string
     */
    const PRE_PACKAGE_INSTALL = 'pre-package-install';

    /**
     * The POST_PACKAGE_INSTALL event occurs after a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_INSTALL instead.
     * @var string
     */
    const POST_PACKAGE_INSTALL = 'post-package-install';

    /**
     * The PRE_PACKAGE_UPDATE event occurs before a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UPDATE instead.
     * @var string
     */
    const PRE_PACKAGE_UPDATE = 'pre-package-update';

    /**
     * The POST_PACKAGE_UPDATE event occurs after a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UPDATE instead.
     * @var string
     */
    const POST_PACKAGE_UPDATE = 'post-package-update';

    /**
     * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UNINSTALL instead.
     * @var string
     */
    const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall';

    /**
     * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UNINSTALL instead.
     * @var string
     */
    const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
}
<?php

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

namespace Composer\Script;

/**
 * The Command Event.
 *
 * @deprecated use Composer\Script\Event instead
 */
class CommandEvent extends Event
{
}
<?php

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

namespace Composer\Script;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\EventDispatcher\Event as BaseEvent;

/**
 * The script event class
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event extends BaseEvent
{
    /**
     * @var Composer The composer instance
     */
    private $composer;

    /**
     * @var IOInterface The IO instance
     */
    private $io;

    /**
     * @var bool Dev mode flag
     */
    private $devMode;

    /**
     * @var BaseEvent
     */
    private $originatingEvent;

    /**
     * Constructor.
     *
     * @param string      $name     The event name
     * @param Composer    $composer The composer object
     * @param IOInterface $io       The IOInterface object
     * @param bool        $devMode  Whether or not we are in dev mode
     * @param array       $args     Arguments passed by the user
     * @param array       $flags    Optional flags to pass data not as argument
     */
    public function __construct($name, Composer $composer, IOInterface $io, $devMode = false, array $args = array(), array $flags = array())
    {
        parent::__construct($name, $args, $flags);
        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->originatingEvent = null;
    }

    /**
     * Returns the composer instance.
     *
     * @return Composer
     */
    public function getComposer()
    {
        return $this->composer;
    }

    /**
     * Returns the IO instance.
     *
     * @return IOInterface
     */
    public function getIO()
    {
        return $this->io;
    }

    /**
     * Return the dev mode flag
     *
     * @return bool
     */
    public function isDevMode()
    {
        return $this->devMode;
    }

    /**
     * Set the originating event.
     *
     * @return \Composer\EventDispatcher\Event|null
     */
    public function getOriginatingEvent()
    {
        return $this->originatingEvent;
    }

    /**
     * Set the originating event.
     *
     * @param \Composer\EventDispatcher\Event $event
     * @return $this
     */
    public function setOriginatingEvent(BaseEvent $event)
    {
        $this->originatingEvent = $this->calculateOriginatingEvent($event);

        return $this;
    }

    /**
     * Returns the upper-most event in chain.
     *
     * @param \Composer\EventDispatcher\Event $event
     * @return \Composer\EventDispatcher\Event
     */
    private function calculateOriginatingEvent(BaseEvent $event)
    {
        if ($event instanceof Event && $event->getOriginatingEvent()) {
            return $this->calculateOriginatingEvent($event->getOriginatingEvent());
        }

        return $event;
    }
}
<?php

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

namespace Composer\Question;

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

/**
 * Represents a yes/no question
 * Enforces strict responses rather than non-standard answers counting as default
 * Based on Symfony\Component\Console\Question\ConfirmationQuestion
 *
 * @author Theo Tonge <theo@theotonge.co.uk>
 */
class StrictConfirmationQuestion extends Question
{
    private $trueAnswerRegex;
    private $falseAnswerRegex;

    /**
     * Constructor.s
     *
     * @param string $question         The question to ask to the user
     * @param bool   $default          The default answer to return, true or false
     * @param string $trueAnswerRegex  A regex to match the "yes" answer
     * @param string $falseAnswerRegex A regex to match the "no" answer
     */
    public function __construct($question, $default = true, $trueAnswerRegex = '/^y(?:es)?$/i', $falseAnswerRegex = '/^no?$/i')
    {
        parent::__construct($question, (bool) $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->falseAnswerRegex = $falseAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
        $this->setValidator($this->getDefaultValidator());
    }

    /**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    private function getDefaultNormalizer()
    {
        $default = $this->getDefault();
        $trueRegex = $this->trueAnswerRegex;
        $falseRegex = $this->falseAnswerRegex;

        return function ($answer) use ($default, $trueRegex, $falseRegex) {
            if (is_bool($answer)) {
                return $answer;
            }
            if (empty($answer) && !empty($default)) {
                return $default;
            }

            if (preg_match($trueRegex, $answer)) {
                return true;
            }

            if (preg_match($falseRegex, $answer)) {
                return false;
            }

            return null;
        };
    }

    /**
     * Returns the default answer validator.
     *
     * @return callable
     */
    private function getDefaultValidator()
    {
        return function ($answer) {
            if (!is_bool($answer)) {
                throw new InvalidArgumentException('Please answer yes, y, no, or n.');
            }

            return $answer;
        };
    }
}
<?php

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

namespace Composer\Package\Dumper;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackageInterface;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayDumper
{
    public function dump(PackageInterface $package)
    {
        $keys = array(
            'binaries' => 'bin',
            'type',
            'extra',
            'installationSource' => 'installation-source',
            'autoload',
            'devAutoload' => 'autoload-dev',
            'notificationUrl' => 'notification-url',
            'includePaths' => 'include-path',
        );

        $data = array();
        $data['name'] = $package->getPrettyName();
        $data['version'] = $package->getPrettyVersion();
        $data['version_normalized'] = $package->getVersion();

        if ($package->getTargetDir()) {
            $data['target-dir'] = $package->getTargetDir();
        }

        if ($package->getSourceType()) {
            $data['source']['type'] = $package->getSourceType();
            $data['source']['url'] = $package->getSourceUrl();
            if (null !== ($value = $package->getSourceReference())) {
                $data['source']['reference'] = $value;
            }
            if ($mirrors = $package->getSourceMirrors()) {
                $data['source']['mirrors'] = $mirrors;
            }
        }

        if ($package->getDistType()) {
            $data['dist']['type'] = $package->getDistType();
            $data['dist']['url'] = $package->getDistUrl();
            if (null !== ($value = $package->getDistReference())) {
                $data['dist']['reference'] = $value;
            }
            if (null !== ($value = $package->getDistSha1Checksum())) {
                $data['dist']['shasum'] = $value;
            }
            if ($mirrors = $package->getDistMirrors()) {
                $data['dist']['mirrors'] = $mirrors;
            }
        }

        if ($package->getArchiveExcludes()) {
            $data['archive']['exclude'] = $package->getArchiveExcludes();
        }

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            if ($links = $package->{'get'.ucfirst($opts['method'])}()) {
                foreach ($links as $link) {
                    $data[$type][$link->getTarget()] = $link->getPrettyConstraint();
                }
                ksort($data[$type]);
            }
        }

        if ($packages = $package->getSuggests()) {
            ksort($packages);
            $data['suggest'] = $packages;
        }

        if ($package->getReleaseDate()) {
            $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339);
        }

        $data = $this->dumpValues($package, $keys, $data);

        if ($package instanceof CompletePackageInterface) {
            $keys = array(
                'scripts',
                'license',
                'authors',
                'description',
                'homepage',
                'keywords',
                'repositories',
                'support',
                'funding',
            );

            $data = $this->dumpValues($package, $keys, $data);

            if (isset($data['keywords']) && is_array($data['keywords'])) {
                sort($data['keywords']);
            }

            if ($package->isAbandoned()) {
                $data['abandoned'] = $package->getReplacementPackage() ?: true;
            }
        }

        if ($package instanceof RootPackageInterface) {
            $minimumStability = $package->getMinimumStability();
            if ($minimumStability) {
                $data['minimum-stability'] = $minimumStability;
            }
        }

        if (count($package->getTransportOptions()) > 0) {
            $data['transport-options'] = $package->getTransportOptions();
        }

        return $data;
    }

    private function dumpValues(PackageInterface $package, array $keys, array $data)
    {
        foreach ($keys as $method => $key) {
            if (is_numeric($method)) {
                $method = $key;
            }

            $getter = 'get'.ucfirst($method);
            $value = $package->$getter();

            if (null !== $value && !(is_array($value) && 0 === count($value))) {
                $data[$key] = $value;
            }
        }

        return $data;
    }
}
<?php

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

namespace Composer\Package;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Represents a link between two packages, represented by their names
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Link
{
    /**
     * @var string
     */
    protected $source;

    /**
     * @var string
     */
    protected $target;

    /**
     * @var ConstraintInterface|null
     */
    protected $constraint;

    /**
     * @var string
     */
    protected $description;

    /**
     * @var string|null
     */
    protected $prettyConstraint;

    /**
     * Creates a new package link.
     *
     * @param string                   $source
     * @param string                   $target
     * @param ConstraintInterface|null $constraint       Constraint applying to the target of this link
     * @param string                   $description      Used to create a descriptive string representation
     * @param string|null              $prettyConstraint
     */
    public function __construct($source, $target, ConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null)
    {
        $this->source = strtolower($source);
        $this->target = strtolower($target);
        $this->constraint = $constraint;
        $this->description = $description;
        $this->prettyConstraint = $prettyConstraint;
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @return string
     */
    public function getSource()
    {
        return $this->source;
    }

    /**
     * @return string
     */
    public function getTarget()
    {
        return $this->target;
    }

    /**
     * @return ConstraintInterface|null
     */
    public function getConstraint()
    {
        return $this->constraint;
    }

    /**
     * @throws \UnexpectedValueException If no pretty constraint was provided
     * @return string
     */
    public function getPrettyConstraint()
    {
        if (null === $this->prettyConstraint) {
            throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this));
        }

        return $this->prettyConstraint;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')';
    }

    /**
     * @param  PackageInterface $sourcePackage
     * @return string
     */
    public function getPrettyString(PackageInterface $sourcePackage)
    {
        return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString().'';
    }
}
<?php

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

namespace Composer\Package\Comparer;

/**
 * class Comparer
 *
 * @author Hector Prats <hectorpratsortega@gmail.com>
 */
class Comparer
{
    private $source;
    private $update;
    private $changed;

    public function setSource($source)
    {
        $this->source = $source;
    }

    public function setUpdate($update)
    {
        $this->update = $update;
    }

    public function getChanged($toString = false, $explicated = false)
    {
        $changed = $this->changed;
        if (!count($changed)) {
            return false;
        }
        if ($explicated) {
            foreach ($changed as $sectionKey => $itemSection) {
                foreach ($itemSection as $itemKey => $item) {
                    $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')';
                }
            }
        }

        if ($toString) {
            foreach ($changed as $sectionKey => $itemSection) {
                foreach ($itemSection as $itemKey => $item) {
                    $changed['string'][] = $item."\r\n";
                }
            }
            $changed = implode("\r\n", $changed['string']);
        }

        return $changed;
    }

    public function doCompare()
    {
        $source = array();
        $destination = array();
        $this->changed = array();
        $currentDirectory = getcwd();
        chdir($this->source);
        $source = $this->doTree('.', $source);
        if (!is_array($source)) {
            return;
        }
        chdir($currentDirectory);
        chdir($this->update);
        $destination = $this->doTree('.', $destination);
        if (!is_array($destination)) {
            exit;
        }
        chdir($currentDirectory);
        foreach ($source as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (isset($destination[$dir][$file])) {
                    if ($hash !== $destination[$dir][$file]) {
                        $this->changed['changed'][] = $dir.'/'.$file;
                    }
                } else {
                    $this->changed['removed'][] = $dir.'/'.$file;
                }
            }
        }
        foreach ($destination as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (!isset($source[$dir][$file])) {
                    $this->changed['added'][] = $dir.'/'.$file;
                }
            }
        }
    }

    private function doTree($dir, &$array)
    {
        if ($dh = opendir($dir)) {
            while ($file = readdir($dh)) {
                if ($file !== '.' && $file !== '..') {
                    if (is_link($dir.'/'.$file)) {
                        $array[$dir][$file] = readlink($dir.'/'.$file);
                    } elseif (is_dir($dir.'/'.$file)) {
                        if (!count($array)) {
                            $array[0] = 'Temp';
                        }
                        if (!$this->doTree($dir.'/'.$file, $array)) {
                            return false;
                        }
                    } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) {
                        set_time_limit(30);
                        $array[$dir][$file] = md5_file($dir.'/'.$file);
                    }
                }
            }
            if (count($array) > 1 && isset($array['0'])) {
                unset($array['0']);
            }

            return $array;
        }

        return false;
    }
}
<?php

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

namespace Composer\Package;

/**
 * Defines additional fields that are only needed for the root package
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RootPackageInterface extends CompletePackageInterface
{
    /**
     * Returns a set of package names and their aliases
     *
     * @return array
     */
    public function getAliases();

    /**
     * Returns the minimum stability of the package
     *
     * @return string
     */
    public function getMinimumStability();

    /**
     * Returns the stability flags to apply to dependencies
     *
     * array('foo/bar' => 'dev')
     *
     * @return array
     */
    public function getStabilityFlags();

    /**
     * Returns a set of package names and source references that must be enforced on them
     *
     * array('foo/bar' => 'abcd1234')
     *
     * @return array
     */
    public function getReferences();

    /**
     * Returns true if the root package prefers picking stable packages over unstable ones
     *
     * @return bool
     */
    public function getPreferStable();

    /**
     * Returns the root package's configuration
     *
     * @return array
     */
    public function getConfig();

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires);

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires);

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts);

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides);

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces);

    /**
     * Set the repositories
     *
     * @param array $repositories
     */
    public function setRepositories($repositories);

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     */
    public function setAutoload(array $autoload);

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     */
    public function setDevAutoload(array $devAutoload);

    /**
     * Set the stabilityFlags
     *
     * @param array $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags);

    /**
     * Set the suggested packages
     *
     * @param array $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests);

    /**
     * @param array $extra
     */
    public function setExtra(array $extra);
}
<?php

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

namespace Composer\Package;

use Composer\Json\JsonFile;
use Composer\Installer\InstallationManager;
use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor;
use Composer\Repository\ArrayRepository;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Loader\ArrayLoader;
use Composer\Plugin\PluginInterface;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Seld\JsonLint\ParsingException;

/**
 * Reads/writes project lockfile (composer.lock).
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Locker
{
    private $lockFile;
    private $repositoryManager;
    private $installationManager;
    private $hash;
    private $contentHash;
    private $loader;
    private $dumper;
    private $process;
    private $lockDataCache;

    /**
     * Initializes packages locker.
     *
     * @param IOInterface         $io
     * @param JsonFile            $lockFile             lockfile loader
     * @param RepositoryManager   $repositoryManager    repository manager instance
     * @param InstallationManager $installationManager  installation manager instance
     * @param string              $composerFileContents The contents of the composer file
     */
    public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents)
    {
        $this->lockFile = $lockFile;
        $this->repositoryManager = $repositoryManager;
        $this->installationManager = $installationManager;
        $this->hash = md5($composerFileContents);
        $this->contentHash = self::getContentHash($composerFileContents);
        $this->loader = new ArrayLoader(null, true);
        $this->dumper = new ArrayDumper();
        $this->process = new ProcessExecutor($io);
    }

    /**
     * Returns the md5 hash of the sorted content of the composer file.
     *
     * @param string $composerFileContents The contents of the composer file.
     *
     * @return string
     */
    public static function getContentHash($composerFileContents)
    {
        $content = json_decode($composerFileContents, true);

        $relevantKeys = array(
            'name',
            'version',
            'require',
            'require-dev',
            'conflict',
            'replace',
            'provide',
            'minimum-stability',
            'prefer-stable',
            'repositories',
            'extra',
        );

        $relevantContent = array();

        foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
            $relevantContent[$key] = $content[$key];
        }
        if (isset($content['config']['platform'])) {
            $relevantContent['config']['platform'] = $content['config']['platform'];
        }

        ksort($relevantContent);

        return md5(json_encode($relevantContent));
    }

    /**
     * Checks whether locker has been locked (lockfile found).
     *
     * @return bool
     */
    public function isLocked()
    {
        if (!$this->lockFile->exists()) {
            return false;
        }

        $data = $this->getLockData();

        return isset($data['packages']);
    }

    /**
     * Checks whether the lock file is still up to date with the current hash
     *
     * @return bool
     */
    public function isFresh()
    {
        $lock = $this->lockFile->read();

        if (!empty($lock['content-hash'])) {
            // There is a content hash key, use that instead of the file hash
            return $this->contentHash === $lock['content-hash'];
        }

        // BC support for old lock files without content-hash
        if (!empty($lock['hash'])) {
            return $this->hash === $lock['hash'];
        }

        // should not be reached unless the lock file is corrupted, so assume it's out of date
        return false;
    }

    /**
     * Searches and returns an array of locked packages, retrieved from registered repositories.
     *
     * @param  bool                                     $withDevReqs true to retrieve the locked dev packages
     * @throws \RuntimeException
     * @return \Composer\Repository\RepositoryInterface
     */
    public function getLockedRepository($withDevReqs = false)
    {
        $lockData = $this->getLockData();
        $packages = new ArrayRepository();

        $lockedPackages = $lockData['packages'];
        if ($withDevReqs) {
            if (isset($lockData['packages-dev'])) {
                $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']);
            } else {
                throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or run update to install those packages.');
            }
        }

        if (empty($lockedPackages)) {
            return $packages;
        }

        if (isset($lockedPackages[0]['name'])) {
            foreach ($lockedPackages as $info) {
                $packages->addPackage($this->loader->load($info));
            }

            return $packages;
        }

        throw new \RuntimeException('Your composer.lock was created before 2012-09-15, and is not supported anymore. Run "composer update" to generate a new one.');
    }

    /**
     * Returns the platform requirements stored in the lock file
     *
     * @param  bool                     $withDevReqs if true, the platform requirements from the require-dev block are also returned
     * @return \Composer\Package\Link[]
     */
    public function getPlatformRequirements($withDevReqs = false)
    {
        $lockData = $this->getLockData();
        $requirements = array();

        if (!empty($lockData['platform'])) {
            $requirements = $this->loader->parseLinks(
                '__ROOT__',
                '1.0.0',
                'requires',
                isset($lockData['platform']) ? $lockData['platform'] : array()
            );
        }

        if ($withDevReqs && !empty($lockData['platform-dev'])) {
            $devRequirements = $this->loader->parseLinks(
                '__ROOT__',
                '1.0.0',
                'requires',
                isset($lockData['platform-dev']) ? $lockData['platform-dev'] : array()
            );

            $requirements = array_merge($requirements, $devRequirements);
        }

        return $requirements;
    }

    public function getMinimumStability()
    {
        $lockData = $this->getLockData();

        return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'stable';
    }

    public function getStabilityFlags()
    {
        $lockData = $this->getLockData();

        return isset($lockData['stability-flags']) ? $lockData['stability-flags'] : array();
    }

    public function getPreferStable()
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-stable
        return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
    }

    public function getPreferLowest()
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-lowest
        return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
    }

    public function getPlatformOverrides()
    {
        $lockData = $this->getLockData();

        return isset($lockData['platform-overrides']) ? $lockData['platform-overrides'] : array();
    }

    public function getAliases()
    {
        $lockData = $this->getLockData();

        if (!isset($lockData['aliases'])) {
            return array();
        }

        // forward compatibility with Composer 2 lock files created
        // before https://github.com/composer/composer/issues/9337 was fixed
        foreach ($lockData['aliases'] as $index => $alias) {
            if (in_array($alias['version'], array('dev-master', 'dev-default', 'dev-trunk'), true)) {
                $lockData['aliases'][$index]['version'] = '9999999-dev';
            }
        }

        return $lockData['aliases'];
    }

    public function getLockData()
    {
        if (null !== $this->lockDataCache) {
            return $this->lockDataCache;
        }

        if (!$this->lockFile->exists()) {
            throw new \LogicException('No lockfile found. Unable to read locked packages');
        }

        return $this->lockDataCache = $this->lockFile->read();
    }

    /**
     * Locks provided data into lockfile.
     *
     * @param array  $packages          array of packages
     * @param mixed  $devPackages       array of dev packages or null if installed without --dev
     * @param array  $platformReqs      array of package name => constraint for required platform packages
     * @param mixed  $platformDevReqs   array of package name => constraint for dev-required platform packages
     * @param array  $aliases           array of aliases
     * @param string $minimumStability
     * @param array  $stabilityFlags
     * @param bool   $preferStable
     * @param bool   $preferLowest
     * @param array  $platformOverrides
     *
     * @return bool
     */
    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides)
    {
        $lock = array(
            '_readme' => array('This file locks the dependencies of your project to a known state',
                               'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies',
                               'This file is @gener'.'ated automatically', ),
            'content-hash' => $this->contentHash,
            'packages' => null,
            'packages-dev' => null,
            'aliases' => array(),
            'minimum-stability' => $minimumStability,
            'stability-flags' => $stabilityFlags,
            'prefer-stable' => $preferStable,
            'prefer-lowest' => $preferLowest,
        );

        foreach ($aliases as $package => $versions) {
            foreach ($versions as $version => $alias) {
                $lock['aliases'][] = array(
                    'alias' => $alias['alias'],
                    'alias_normalized' => $alias['alias_normalized'],
                    'version' => $version,
                    'package' => $package,
                );
            }
        }

        $lock['packages'] = $this->lockPackages($packages);
        if (null !== $devPackages) {
            $lock['packages-dev'] = $this->lockPackages($devPackages);
        }

        $lock['platform'] = $platformReqs;
        $lock['platform-dev'] = $platformDevReqs;
        if ($platformOverrides) {
            $lock['platform-overrides'] = $platformOverrides;
        }
        $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;

        if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) {
            if ($this->lockFile->exists()) {
                unlink($this->lockFile->getPath());
            }

            return false;
        }

        try {
            $isLocked = $this->isLocked();
        } catch (ParsingException $e) {
            $isLocked = false;
        }
        if (!$isLocked || $lock !== $this->getLockData()) {
            $this->lockFile->write($lock);
            $this->lockDataCache = null;

            return true;
        }

        return false;
    }

    private function lockPackages(array $packages)
    {
        $locked = array();

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            $name = $package->getPrettyName();
            $version = $package->getPrettyVersion();

            if (!$name || !$version) {
                throw new \LogicException(sprintf(
                    'Package "%s" has no version or name and can not be locked',
                    $package
                ));
            }

            $spec = $this->dumper->dump($package);
            unset($spec['version_normalized']);

            // always move time to the end of the package definition
            $time = isset($spec['time']) ? $spec['time'] : null;
            unset($spec['time']);
            if ($package->isDev() && $package->getInstallationSource() === 'source') {
                // use the exact commit time of the current reference if it's a dev package
                $time = $this->getPackageTime($package) ?: $time;
            }
            if (null !== $time) {
                $spec['time'] = $time;
            }

            unset($spec['installation-source']);

            $locked[] = $spec;
        }

        usort($locked, function ($a, $b) {
            $comparison = strcmp($a['name'], $b['name']);

            if (0 !== $comparison) {
                return $comparison;
            }

            // If it is the same package, compare the versions to make the order deterministic
            return strcmp($a['version'], $b['version']);
        });

        return $locked;
    }

    /**
     * Returns the packages's datetime for its source reference.
     *
     * @param  PackageInterface $package The package to scan.
     * @return string|null      The formatted datetime or null if none was found.
     */
    private function getPackageTime(PackageInterface $package)
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $path = realpath($this->installationManager->getInstallPath($package));
        $sourceType = $package->getSourceType();
        $datetime = null;

        if ($path && in_array($sourceType, array('git', 'hg'))) {
            $sourceRef = $package->getSourceReference() ?: $package->getDistReference();
            switch ($sourceType) {
                case 'git':
                    GitUtil::cleanEnv();

                    if (0 === $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef).GitUtil::getNoShowSignatureFlag($this->process), $output, $path) && preg_match('{^\s*\d+\s*$}', $output)) {
                        $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                    }
                    break;

                case 'hg':
                    if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($sourceRef), $output, $path) && preg_match('{^\s*(\d+)\s*}', $output, $match)) {
                        $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC'));
                    }
                    break;
            }
        }

        return $datetime ? $datetime->format(DATE_RFC3339) : null;
    }
}
<?php

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

namespace Composer\Package;

/**
 * Package containing additional metadata that is not used by the solver
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CompletePackage extends Package implements CompletePackageInterface
{
    protected $repositories;
    protected $license = array();
    protected $keywords;
    protected $authors;
    protected $description;
    protected $homepage;
    protected $scripts = array();
    protected $support = array();
    protected $funding = array();
    protected $abandoned = false;

    /**
     * @param array $scripts
     */
    public function setScripts(array $scripts)
    {
        $this->scripts = $scripts;
    }

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

    /**
     * Set the repositories
     *
     * @param array $repositories
     */
    public function setRepositories($repositories)
    {
        $this->repositories = $repositories;
    }

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

    /**
     * Set the license
     *
     * @param array $license
     */
    public function setLicense(array $license)
    {
        $this->license = $license;
    }

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

    /**
     * Set the keywords
     *
     * @param array $keywords
     */
    public function setKeywords(array $keywords)
    {
        $this->keywords = $keywords;
    }

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

    /**
     * Set the authors
     *
     * @param array $authors
     */
    public function setAuthors(array $authors)
    {
        $this->authors = $authors;
    }

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

    /**
     * Set the description
     *
     * @param string $description
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

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

    /**
     * Set the homepage
     *
     * @param string $homepage
     */
    public function setHomepage($homepage)
    {
        $this->homepage = $homepage;
    }

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

    /**
     * Set the support information
     *
     * @param array $support
     */
    public function setSupport(array $support)
    {
        $this->support = $support;
    }

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

    /**
     * Set the funding
     *
     * @param array $funding
     */
    public function setFunding(array $funding)
    {
        $this->funding = $funding;
    }

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

    /**
     * @return bool
     */
    public function isAbandoned()
    {
        return (bool) $this->abandoned;
    }

    /**
     * @param bool|string $abandoned
     */
    public function setAbandoned($abandoned)
    {
        $this->abandoned = $abandoned;
    }

    /**
     * If the package is abandoned and has a suggested replacement, this method returns it
     *
     * @return string|null
     */
    public function getReplacementPackage()
    {
        return is_string($this->abandoned) ? $this->abandoned : null;
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Json\JsonFile;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 */
class JsonLoader
{
    private $loader;

    public function __construct(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    /**
     * @param  string|JsonFile                    $json A filename, json string or JsonFile instance to load the package from
     * @return \Composer\Package\PackageInterface
     */
    public function load($json)
    {
        if ($json instanceof JsonFile) {
            $config = $json->read();
        } elseif (file_exists($json)) {
            $config = JsonFile::parseJson(file_get_contents($json), $json);
        } elseif (is_string($json)) {
            $config = JsonFile::parseJson($json);
        }

        return $this->loader->load($config);
    }
}
<?php

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

namespace Composer\Package\Loader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidPackageException extends \Exception
{
    private $errors;
    private $warnings;
    private $data;

    public function __construct(array $errors, array $warnings, array $data)
    {
        $this->errors = $errors;
        $this->warnings = $warnings;
        $this->data = $data;
        parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings)));
    }

    public function getData()
    {
        return $this->data;
    }

    public function getErrors()
    {
        return $this->errors;
    }

    public function getWarnings()
    {
        return $this->warnings;
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\RepositoryFactory;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor;

/**
 * ArrayLoader built for the sole purpose of loading the root package
 *
 * Sets additional defaults and loads repositories
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageLoader extends ArrayLoader
{
    /**
     * @var RepositoryManager
     */
    private $manager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

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

    public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null, IOInterface $io = null)
    {
        parent::__construct($parser);

        $this->manager = $manager;
        $this->config = $config;
        $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser);
        $this->io = $io;
    }

    /**
     * @param  array                $config package data
     * @param  string               $class  FQCN to be instantiated
     * @param  string               $cwd    cwd of the root package to be used to guess the version if it is not provided
     * @return RootPackageInterface
     */
    public function load(array $config, $class = 'Composer\Package\RootPackage', $cwd = null)
    {
        if (!isset($config['name'])) {
            $config['name'] = '__root__';
        } elseif ($this->io) {
            if ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
                $this->io->writeError('<warning>Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
            }
        }
        $autoVersioned = false;
        if (!isset($config['version'])) {
            $commit = null;

            // override with env var if available
            if (getenv('COMPOSER_ROOT_VERSION')) {
                $config['version'] = getenv('COMPOSER_ROOT_VERSION');
            } else {
                $versionData = $this->versionGuesser->guessVersion($config, $cwd ?: getcwd());
                if ($versionData) {
                    $config['version'] = $versionData['pretty_version'];
                    $config['version_normalized'] = $versionData['version'];
                    $commit = $versionData['commit'];
                }
            }

            if (!isset($config['version'])) {
                $config['version'] = '1.0.0';
                $autoVersioned = true;
            }

            if ($commit) {
                $config['source'] = array(
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                );
                $config['dist'] = array(
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                );
            }
        }

        $realPackage = $package = parent::load($config, $class);
        if ($realPackage instanceof AliasPackage) {
            $realPackage = $package->getAliasOf();
        }

        if ($autoVersioned) {
            $realPackage->replaceVersion($realPackage->getVersion(), 'No version set (parsed as 1.0.0)');
        }

        if (isset($config['minimum-stability'])) {
            $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
        }

        $aliases = array();
        $stabilityFlags = array();
        $references = array();
        foreach (array('require', 'require-dev') as $linkType) {
            if (isset($config[$linkType])) {
                $linkInfo = BasePackage::$supportedLinkTypes[$linkType];
                $method = 'get'.ucfirst($linkInfo['method']);
                $links = array();
                foreach ($realPackage->$method() as $link) {
                    $links[$link->getTarget()] = $link->getConstraint()->getPrettyString();
                }
                $aliases = $this->extractAliases($links, $aliases);
                $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags, $realPackage->getMinimumStability());
                $references = $this->extractReferences($links, $references);

                if (isset($links[$config['name']])) {
                    throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
                                'Did you accidentally name your root package after an external package?', $config['name']));
                }
            }
        }

        if ($this->io) {
            foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
                if (isset($config[$linkType])) {
                    foreach ($config[$linkType] as $linkName => $constraint) {
                        if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
                            $this->io->writeError('<warning>Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
                        }
                    }
                }
            }
        }

        $realPackage->setAliases($aliases);
        $realPackage->setStabilityFlags($stabilityFlags);
        $realPackage->setReferences($references);

        if (isset($config['prefer-stable'])) {
            $realPackage->setPreferStable((bool) $config['prefer-stable']);
        }

        if (isset($config['config'])) {
            $realPackage->setConfig($config['config']);
        }

        $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager);
        foreach ($repos as $repo) {
            $this->manager->addRepository($repo);
        }
        $realPackage->setRepositories($this->config->getRepositories());

        return $package;
    }

    private function extractAliases(array $requires, array $aliases)
    {
        foreach ($requires as $reqName => $reqVersion) {
            if (preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $reqVersion, $match)) {
                $aliases[] = array(
                    'package' => strtolower($reqName),
                    'version' => $this->versionParser->normalize($match[1], $reqVersion),
                    'alias' => $match[2],
                    'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion),
                );
            } elseif (strpos($reqVersion, ' as ') !== false) {
                throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".');
            }
        }

        return $aliases;
    }

    private function extractStabilityFlags(array $requires, array $stabilityFlags, $minimumStability)
    {
        $stabilities = BasePackage::$stabilities;
        $minimumStability = $stabilities[$minimumStability];
        foreach ($requires as $reqName => $reqVersion) {
            $constraints = array();

            // extract all sub-constraints in case it is an OR/AND multi-constraint
            $orSplit = preg_split('{\s*\|\|?\s*}', trim($reqVersion));
            foreach ($orSplit as $orConstraint) {
                $andSplit = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
                foreach ($andSplit as $andConstraint) {
                    $constraints[] = $andConstraint;
                }
            }

            // parse explicit stability flags to the most unstable
            $match = false;
            foreach ($constraints as $constraint) {
                if (preg_match('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $constraint, $match)) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[VersionParser::normalizeStability($match[1])];

                    if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                    $match = true;
                }
            }

            if ($match) {
                continue;
            }

            foreach ($constraints as $constraint) {
                // infer flags for requirements that have an explicit -dev or -beta version specified but only
                // for those that are more unstable than the minimumStability or existing flags
                $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $constraint);
                if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[$stabilityName];
                    if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                }
            }
        }

        return $stabilityFlags;
    }

    private function extractReferences(array $requires, array $references)
    {
        foreach ($requires as $reqName => $reqVersion) {
            $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
            if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) {
                $name = strtolower($reqName);
                $references[$name] = $match[1];
            }
        }

        return $references;
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Package;
use Composer\Package\AliasPackage;
use Composer\Package\Link;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\VersionParser as SemverVersionParser;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayLoader implements LoaderInterface
{
    protected $versionParser;
    protected $loadOptions;

    public function __construct(SemverVersionParser $parser = null, $loadOptions = false)
    {
        if (!$parser) {
            $parser = new VersionParser;
        }
        $this->versionParser = $parser;
        $this->loadOptions = $loadOptions;
    }

    public function load(array $config, $class = 'Composer\Package\CompletePackage')
    {
        if (!isset($config['name'])) {
            throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
        }
        if (!isset($config['version'])) {
            throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.');
        }

        // handle already normalized versions
        if (isset($config['version_normalized'])) {
            $version = $config['version_normalized'];
        } else {
            $version = $this->versionParser->normalize($config['version']);
        }
        $package = new $class($config['name'], $version, $config['version']);
        $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');

        if (isset($config['target-dir'])) {
            $package->setTargetDir($config['target-dir']);
        }

        if (isset($config['extra']) && is_array($config['extra'])) {
            $package->setExtra($config['extra']);
        }

        if (isset($config['bin'])) {
            if (!\is_array($config['bin'])) {
                $config['bin'] = array($config['bin']);
            }
            foreach ($config['bin'] as $key => $bin) {
                $config['bin'][$key] = ltrim($bin, '/');
            }
            $package->setBinaries($config['bin']);
        }

        if (isset($config['installation-source'])) {
            $package->setInstallationSource($config['installation-source']);
        }

        if (isset($config['source'])) {
            if (!isset($config['source']['type']) || !isset($config['source']['url']) || !isset($config['source']['reference'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['source'])
                ));
            }
            $package->setSourceType($config['source']['type']);
            $package->setSourceUrl($config['source']['url']);
            $package->setSourceReference(isset($config['source']['reference']) ? $config['source']['reference'] : null);
            if (isset($config['source']['mirrors'])) {
                $package->setSourceMirrors($config['source']['mirrors']);
            }
        }

        if (isset($config['dist'])) {
            if (!isset($config['dist']['type'])
             || !isset($config['dist']['url'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's dist key should be specified as ".
                    "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['dist'])
                ));
            }
            $package->setDistType($config['dist']['type']);
            $package->setDistUrl($config['dist']['url']);
            $package->setDistReference(isset($config['dist']['reference']) ? $config['dist']['reference'] : null);
            $package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $config['dist']['shasum'] : null);
            if (isset($config['dist']['mirrors'])) {
                $package->setDistMirrors($config['dist']['mirrors']);
            }
        }

        foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
            if (isset($config[$type])) {
                $method = 'set'.ucfirst($opts['method']);
                $package->{$method}(
                    $this->parseLinks(
                        $package->getName(),
                        $package->getPrettyVersion(),
                        $opts['description'],
                        $config[$type]
                    )
                );
            }
        }

        if (isset($config['suggest']) && is_array($config['suggest'])) {
            foreach ($config['suggest'] as $target => $reason) {
                if ('self.version' === trim($reason)) {
                    $config['suggest'][$target] = $package->getPrettyVersion();
                }
            }
            $package->setSuggests($config['suggest']);
        }

        if (isset($config['autoload'])) {
            $package->setAutoload($config['autoload']);
        }

        if (isset($config['autoload-dev'])) {
            $package->setDevAutoload($config['autoload-dev']);
        }

        if (isset($config['include-path'])) {
            $package->setIncludePaths($config['include-path']);
        }

        if (!empty($config['time'])) {
            $time = preg_match('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time'];

            try {
                $date = new \DateTime($time, new \DateTimeZone('UTC'));
                $package->setReleaseDate($date);
            } catch (\Exception $e) {
            }
        }

        if (!empty($config['notification-url'])) {
            $package->setNotificationUrl($config['notification-url']);
        }

        if (!empty($config['archive']['exclude'])) {
            $package->setArchiveExcludes($config['archive']['exclude']);
        }

        if ($package instanceof Package\CompletePackageInterface) {
            if (isset($config['scripts']) && is_array($config['scripts'])) {
                foreach ($config['scripts'] as $event => $listeners) {
                    $config['scripts'][$event] = (array) $listeners;
                }
                if (isset($config['scripts']['composer'])) {
                    trigger_error('The `composer` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED);
                }
                $package->setScripts($config['scripts']);
            }

            if (!empty($config['description']) && is_string($config['description'])) {
                $package->setDescription($config['description']);
            }

            if (!empty($config['homepage']) && is_string($config['homepage'])) {
                $package->setHomepage($config['homepage']);
            }

            if (!empty($config['keywords']) && is_array($config['keywords'])) {
                $package->setKeywords($config['keywords']);
            }

            if (!empty($config['license'])) {
                $package->setLicense(is_array($config['license']) ? $config['license'] : array($config['license']));
            }

            if (!empty($config['authors']) && is_array($config['authors'])) {
                $package->setAuthors($config['authors']);
            }

            if (isset($config['support'])) {
                $package->setSupport($config['support']);
            }

            if (!empty($config['funding']) && is_array($config['funding'])) {
                $package->setFunding($config['funding']);
            }

            if (isset($config['abandoned'])) {
                $package->setAbandoned($config['abandoned']);
            }
        }

        if ($aliasNormalized = $this->getBranchAlias($config)) {
            if ($package instanceof RootPackageInterface) {
                $package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
            } else {
                $package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
            }
        }

        if ($this->loadOptions && isset($config['transport-options'])) {
            $package->setTransportOptions($config['transport-options']);
        }

        return $package;
    }

    /**
     * @param  string $source        source package name
     * @param  string $sourceVersion source package version (pretty version ideally)
     * @param  string $description   link description (e.g. requires, replaces, ..)
     * @param  array  $links         array of package name => constraint mappings
     * @return Link[]
     */
    public function parseLinks($source, $sourceVersion, $description, $links)
    {
        $res = array();
        foreach ($links as $target => $constraint) {
            if (!is_string($constraint)) {
                throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')');
            }
            if ('self.version' === $constraint) {
                $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
            } else {
                $parsedConstraint = $this->versionParser->parseConstraints($constraint);
            }

            $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint);
        }

        return $res;
    }

    /**
     * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
     *
     * @param  array       $config the entire package config
     * @return string|null normalized version of the branch alias or null if there is none
     */
    public function getBranchAlias(array $config)
    {
        if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
            || !isset($config['extra']['branch-alias'])
            || !is_array($config['extra']['branch-alias'])
        ) {
            return;
        }

        foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
            // ensure it is an alias to a -dev package
            if ('-dev' !== substr($targetBranch, -4)) {
                continue;
            }

            // normalize without -dev and ensure it's a numeric branch that is parseable
            $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
            if ('-dev' !== substr($validatedTargetBranch, -4)) {
                continue;
            }

            // ensure that it is the current branch aliasing itself
            if (strtolower($config['version']) !== strtolower($sourceBranch)) {
                continue;
            }

            // If using numeric aliases ensure the alias is a valid subversion
            if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                && (stripos($targetPrefix, $sourcePrefix) !== 0)
            ) {
                continue;
            }

            return $validatedTargetBranch;
        }
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Spdx\SpdxLicenses;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidatingArrayLoader implements LoaderInterface
{
    const CHECK_ALL = 3;
    const CHECK_UNBOUND_CONSTRAINTS = 1;
    const CHECK_STRICT_CONSTRAINTS = 2;

    private $loader;
    private $versionParser;
    private $errors;
    private $warnings;
    private $config;
    private $strictName;
    private $flags;

    public function __construct(LoaderInterface $loader, $strictName = true, VersionParser $parser = null, $flags = 0)
    {
        $this->loader = $loader;
        $this->versionParser = $parser ?: new VersionParser();
        $this->strictName = $strictName;
        $this->flags = $flags;
    }

    public function load(array $config, $class = 'Composer\Package\CompletePackage')
    {
        $this->errors = array();
        $this->warnings = array();
        $this->config = $config;

        if ($err = self::hasPackageNamingError($config['name'])) {
            $this->warnings[] = 'Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.';
        }

        if ($this->strictName) {
            $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
        } else {
            $this->validateString('name', true);
        }

        if (!empty($this->config['version'])) {
            try {
                $this->versionParser->normalize($this->config['version']);
            } catch (\Exception $e) {
                $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage();
                unset($this->config['version']);
            }
        }

        if (!empty($this->config['config']['platform'])) {
            foreach ((array) $this->config['config']['platform'] as $key => $platform) {
                try {
                    $this->versionParser->normalize($platform);
                } catch (\Exception $e) {
                    $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage();
                }
            }
        }

        $this->validateRegex('type', '[A-Za-z0-9-]+');
        $this->validateString('target-dir');
        $this->validateArray('extra');

        if (isset($this->config['bin'])) {
            if (is_string($this->config['bin'])) {
                $this->validateString('bin');
            } else {
                $this->validateFlatArray('bin');
            }
        }

        $this->validateArray('scripts'); // TODO validate event names & listener syntax
        $this->validateString('description');
        $this->validateUrl('homepage');
        $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+');

        $releaseDate = null;
        $this->validateString('time');
        if (!empty($this->config['time'])) {
            try {
                $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC'));
            } catch (\Exception $e) {
                $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage();
                unset($this->config['time']);
            }
        }

        // check for license validity on newly updated branches
        if (isset($this->config['license']) && (!$releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days'))) {
            if (is_array($this->config['license']) || is_string($this->config['license'])) {
                $licenses = (array) $this->config['license'];

                $licenseValidator = new SpdxLicenses();
                foreach ($licenses as $license) {
                    // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer
                    if ('proprietary' === $license) {
                        continue;
                    }
                    $licenseToValidate = str_replace('proprietary', 'MIT', $license);
                    if (!$licenseValidator->validate($licenseToValidate)) {
                        if ($licenseValidator->validate(trim($licenseToValidate))) {
                            $this->warnings[] = sprintf(
                                'License %s must not contain extra spaces, make sure to trim it.',
                                json_encode($license)
                            );
                        } else {
                            $this->warnings[] = sprintf(
                                'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL .
                                'If the software is closed-source, you may use "proprietary" as license.',
                                json_encode($license)
                            );
                        }
                    }
                }
            }
        }

        if ($this->validateArray('authors') && !empty($this->config['authors'])) {
            foreach ($this->config['authors'] as $key => $author) {
                if (!is_array($author)) {
                    $this->errors[] = 'authors.'.$key.' : should be an array, '.gettype($author).' given';
                    unset($this->config['authors'][$key]);
                    continue;
                }
                foreach (array('homepage', 'email', 'name', 'role') as $authorData) {
                    if (isset($author[$authorData]) && !is_string($author[$authorData])) {
                        $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string';
                        unset($this->config['authors'][$key][$authorData]);
                    }
                }
                if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) {
                    $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL';
                    unset($this->config['authors'][$key]['homepage']);
                }
                if (isset($author['email']) && !filter_var($author['email'], FILTER_VALIDATE_EMAIL)) {
                    $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address';
                    unset($this->config['authors'][$key]['email']);
                }
                if (empty($this->config['authors'][$key])) {
                    unset($this->config['authors'][$key]);
                }
            }
            if (empty($this->config['authors'])) {
                unset($this->config['authors']);
            }
        }

        if ($this->validateArray('support') && !empty($this->config['support'])) {
            foreach (array('issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat') as $key) {
                if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) {
                    $this->errors[] = 'support.'.$key.' : invalid value, must be a string';
                    unset($this->config['support'][$key]);
                }
            }

            if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) {
                $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address';
                unset($this->config['support']['email']);
            }

            if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], array('irc'))) {
                $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc://<server>/<channel> URL';
                unset($this->config['support']['irc']);
            }

            foreach (array('issues', 'forum', 'wiki', 'source', 'docs', 'chat') as $key) {
                if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) {
                    $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL';
                    unset($this->config['support'][$key]);
                }
            }
            if (empty($this->config['support'])) {
                unset($this->config['support']);
            }
        }

        if ($this->validateArray('funding') && !empty($this->config['funding'])) {
            foreach ($this->config['funding'] as $key => $fundingOption) {
                if (!is_array($fundingOption)) {
                    $this->errors[] = 'funding.'.$key.' : should be an array, '.gettype($fundingOption).' given';
                    unset($this->config['funding'][$key]);
                    continue;
                }
                foreach (array('type', 'url') as $fundingData) {
                    if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) {
                        $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string';
                        unset($this->config['funding'][$key][$fundingData]);
                    }
                }
                if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) {
                    $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL';
                    unset($this->config['funding'][$key]['url']);
                }
                if (empty($this->config['funding'][$key])) {
                    unset($this->config['funding'][$key]);
                }
            }
            if (empty($this->config['funding'])) {
                unset($this->config['funding']);
            }
        }

        $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master'));
        $stableConstraint = new Constraint('=', '1.0.0');

        foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
                foreach ($this->config[$linkType] as $package => $constraint) {
                    if ($err = self::hasPackageNamingError($package, true)) {
                        $this->warnings[] = 'Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.';
                    } elseif (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) {
                        $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]';
                    }
                    if (!is_string($constraint)) {
                        $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint';
                        unset($this->config[$linkType][$package]);
                    } elseif ('self.version' !== $constraint) {
                        try {
                            $linkConstraint = $this->versionParser->parseConstraints($constraint);
                        } catch (\Exception $e) {
                            $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')';
                            unset($this->config[$linkType][$package]);
                            continue;
                        }

                        // check requires for unbound constraints on non-platform packages
                        if (
                            ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS)
                            && 'require' === $linkType
                            && $linkConstraint->matches($unboundConstraint)
                            && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $package)
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided';
                        } elseif (
                            // check requires for exact constraints
                            ($this->flags & self::CHECK_STRICT_CONSTRAINTS)
                            && 'require' === $linkType
                            && substr($linkConstraint, 0, 1) === '='
                            && $stableConstraint->versionCompare($stableConstraint, $linkConstraint, '<=')
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
                        }
                    }
                }
            }
        }

        if ($this->validateArray('suggest') && !empty($this->config['suggest'])) {
            foreach ($this->config['suggest'] as $package => $description) {
                if (!is_string($description)) {
                    $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested';
                    unset($this->config['suggest'][$package]);
                }
            }
        }

        if ($this->validateString('minimum-stability') && !empty($this->config['minimum-stability'])) {
            if (!isset(BasePackage::$stabilities[$this->config['minimum-stability']])) {
                $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::$stabilities));
                unset($this->config['minimum-stability']);
            }
        }

        if ($this->validateArray('autoload') && !empty($this->config['autoload'])) {
            $types = array('psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap');
            foreach ($this->config['autoload'] as $type => $typeConfig) {
                if (!in_array($type, $types)) {
                    $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types);
                    unset($this->config['autoload'][$type]);
                }
                if ($type === 'psr-4') {
                    foreach ($typeConfig as $namespace => $dirs) {
                        if ($namespace !== '' && '\\' !== substr($namespace, -1)) {
                            $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\';
                        }
                    }
                }
            }
        }

        if (!empty($this->config['autoload']['psr-4']) && !empty($this->config['target-dir'])) {
            $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4';
            // Unset the psr-4 setting, since unsetting target-dir might
            // interfere with other settings.
            unset($this->config['autoload']['psr-4']);
        }

        // TODO validate dist
        // TODO validate source

        // TODO validate repositories
        // TODO validate package repositories' packages using this recursively

        $this->validateFlatArray('include-path');
        $this->validateArray('transport-options');

        // branch alias validation
        if (isset($this->config['extra']['branch-alias'])) {
            if (!is_array($this->config['extra']['branch-alias'])) {
                $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases';
            } else {
                foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
                    // ensure it is an alias to a -dev package
                    if ('-dev' !== substr($targetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // normalize without -dev and ensure it's a numeric branch that is parseable
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                    if ('-dev' !== substr($validatedTargetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // If using numeric aliases ensure the alias is a valid subversion
                    if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                        && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                        && (stripos($targetPrefix, $sourcePrefix) !== 0)
                    ) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);
                    }
                }
            }
        }

        if ($this->errors) {
            throw new InvalidPackageException($this->errors, $this->warnings, $config);
        }

        $package = $this->loader->load($this->config, $class);
        $this->config = null;

        return $package;
    }

    public function getWarnings()
    {
        return $this->warnings;
    }

    public function getErrors()
    {
        return $this->errors;
    }

    public static function hasPackageNamingError($name, $isLink = false)
    {
        if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
            return;
        }

        if (!preg_match('{^[a-z0-9](?:[_.-]?[a-z0-9]+)*/[a-z0-9](?:(?:[_.]?|-{0,2})[a-z0-9]+)*$}iD', $name)) {
            return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".';
        }

        $reservedNames = array('nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9');
        $bits = explode('/', strtolower($name));
        if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
            return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
        }

        if (preg_match('{\.json$}', $name)) {
            return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.';
        }

        if (preg_match('{[A-Z]}', $name)) {
            if ($isLink) {
                return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.';
            }

            $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
            $suggestName = strtolower($suggestName);

            return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.';
        }
    }

    private function validateRegex($property, $regex, $mandatory = false)
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!preg_match('{^'.$regex.'$}u', $this->config[$property])) {
            $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex;
            if ($mandatory) {
                $this->errors[] = $message;
            } else {
                $this->warnings[] = $message;
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    private function validateString($property, $mandatory = false)
    {
        if (isset($this->config[$property]) && !is_string($this->config[$property])) {
            $this->errors[] = $property.' : should be a string, '.gettype($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || trim($this->config[$property]) === '') {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    private function validateArray($property, $mandatory = false)
    {
        if (isset($this->config[$property]) && !is_array($this->config[$property])) {
            $this->errors[] = $property.' : should be an array, '.gettype($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || !count($this->config[$property])) {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present and contain at least one element';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    private function validateFlatArray($property, $regex = null, $mandatory = false)
    {
        if (!$this->validateArray($property, $mandatory)) {
            return false;
        }

        $pass = true;
        foreach ($this->config[$property] as $key => $value) {
            if (!is_string($value) && !is_numeric($value)) {
                $this->errors[] = $property.'.'.$key.' : must be a string or int, '.gettype($value).' given';
                unset($this->config[$property][$key]);
                $pass = false;

                continue;
            }

            if ($regex && !preg_match('{^'.$regex.'$}u', $value)) {
                $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex;
                unset($this->config[$property][$key]);
                $pass = false;
            }
        }

        return $pass;
    }

    private function validateUrl($property, $mandatory = false)
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!$this->filterUrl($this->config[$property])) {
            $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL';
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    private function filterUrl($value, array $schemes = array('http', 'https'))
    {
        if ($value === '') {
            return true;
        }

        $bits = parse_url($value);
        if (empty($bits['scheme']) || empty($bits['host'])) {
            return false;
        }

        if (!in_array($bits['scheme'], $schemes, true)) {
            return false;
        }

        return true;
    }
}
<?php

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

namespace Composer\Package\Loader;

/**
 * Defines a loader that takes an array to create package instances
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface LoaderInterface
{
    /**
     * Converts a package from an array to a real instance
     *
     * @param  array                              $package Package config
     * @param  string                             $class   Package class to use
     * @return \Composer\Package\PackageInterface
     */
    public function load(array $package, $class = 'Composer\Package\CompletePackage');
}
<?php

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

namespace Composer\Package\Version;

use Composer\Config;
use Composer\Repository\Vcs\HgDriver;
use Composer\IO\NullIO;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Util\Git as GitUtil;
use Composer\Util\ProcessExecutor;
use Composer\Util\Svn as SvnUtil;

/**
 * Try to guess the current version number based on different VCS configuration.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Samuel Roze <samuel.roze@gmail.com>
 */
class VersionGuesser
{
    /**
     * @var Config
     */
    private $config;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var SemverVersionParser
     */
    private $versionParser;

    /**
     * @param Config              $config
     * @param ProcessExecutor     $process
     * @param SemverVersionParser $versionParser
     */
    public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser)
    {
        $this->config = $config;
        $this->process = $process;
        $this->versionParser = $versionParser;
    }

    /**
     * @param array  $packageConfig
     * @param string $path          Path to guess into
     *
     * @return null|array versionData, 'version', 'pretty_version' and 'commit' keys, if the version is a feature branch, 'feature_version' and 'feature_pretty_version' keys may also be returned
     */
    public function guessVersion(array $packageConfig, $path)
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $versionData = $this->guessGitVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessHgVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessFossilVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessSvnVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        return null;
    }

    private function postprocess(array $versionData)
    {
        if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['feature_pretty_version']) {
            unset($versionData['feature_version'], $versionData['feature_pretty_version']);
        }

        if ('-dev' === substr($versionData['version'], -4) && preg_match('{\.9{7}}', $versionData['version'])) {
            $versionData['pretty_version'] = preg_replace('{(\.9{7})+}', '.x', $versionData['version']);
        }

        if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && preg_match('{\.9{7}}', $versionData['feature_version'])) {
            $versionData['feature_pretty_version'] = preg_replace('{(\.9{7})+}', '.x', $versionData['feature_version']);
        }

        return $versionData;
    }

    private function guessGitVersion(array $packageConfig, $path)
    {
        GitUtil::cleanEnv();
        $commit = null;
        $version = null;
        $prettyVersion = null;
        $featureVersion = null;
        $featurePrettyVersion = null;
        $isDetached = false;

        // try to fetch current version from git branch
        if (0 === $this->process->execute('git branch -a --no-color --no-abbrev -v', $output, $path)) {
            $branches = array();
            $isFeatureBranch = false;

            // find current branch and collect all branch names
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && preg_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
                    if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ' || substr($match[1], 0, 17) === '(HEAD detached at') {
                        $version = 'dev-' . $match[2];
                        $prettyVersion = $version;
                        $isFeatureBranch = true;
                        $isDetached = true;
                    } else {
                        $version = $this->versionParser->normalizeBranch($match[1]);
                        $prettyVersion = 'dev-' . $match[1];
                        $isFeatureBranch = 0 === strpos($version, 'dev-');
                    }

                    if ($match[2]) {
                        $commit = $match[2];
                    }
                }

                if ($branch && !preg_match('{^ *.+/HEAD }', $branch)) {
                    if (preg_match('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) {
                        $branches[] = $match[1];
                    }
                }
            }

            if ($isFeatureBranch) {
                $featureVersion = $version;
                $featurePrettyVersion = $prettyVersion;
                // try to find the best (nearest) version branch to assume this feature's version
                $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path);
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
            }
        }

        if (!$version || $isDetached) {
            $result = $this->versionFromGitTags($path);
            if ($result) {
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
                $featureVersion = null;
                $featurePrettyVersion = null;
            }
        }

        if (!$commit) {
            $command = 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($this->process);
            if (0 === $this->process->execute($command, $output, $path)) {
                $commit = trim($output) ?: null;
            }
        }

        if ($featureVersion) {
            return array('version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion);
        }

        return array('version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion);
    }

    private function versionFromGitTags($path)
    {
        // try to fetch current version from git tags
        if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));

                return array('version' => $version, 'pretty_version' => trim($output));
            } catch (\Exception $e) {
            }
        }

        return null;
    }

    private function guessHgVersion(array $packageConfig, $path)
    {
        // try to fetch current version from hg branch
        if (0 === $this->process->execute('hg branch', $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $isFeatureBranch = 0 === strpos($version, 'dev-');

            if ('9999999-dev' === $version) {
                return array('version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch);
            }

            if (!$isFeatureBranch) {
                return array('version' => $version, 'commit' => null, 'pretty_version' => $version);
            }

            // re-use the HgDriver to fetch branches (this properly includes bookmarks)
            $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process);
            $branches = array_keys($driver->getBranches());

            // try to find the best (nearest) version branch to assume this feature's version
            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path);
            $result['commit'] = '';
            $result['feature_version'] = $version;
            $result['feature_pretty_version'] = $version;

            return $result;
        }
    }

    private function guessFeatureVersion(array $packageConfig, $version, array $branches, $scmCmdline, $path)
    {
        $prettyVersion = $version;

        // ignore feature branches if they have no branch-alias or self.version is used
        // and find the branch they came from to use as a version instead
        if ((isset($packageConfig['extra']['branch-alias']) && !isset($packageConfig['extra']['branch-alias'][$version]))
            || strpos(json_encode($packageConfig), '"self.version"')
        ) {
            $branch = preg_replace('{^dev-}', '', $version);
            $length = PHP_INT_MAX;

            $nonFeatureBranches = '';
            if (!empty($packageConfig['non-feature-branches'])) {
                $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']);
            }

            // return directly, if branch is configured to be non-feature branch
            if (preg_match('{^(' . $nonFeatureBranches . ')$}', $branch) && in_array($branch, $branches, true)) {
                return array('version' => $version, 'pretty_version' => $prettyVersion);
            }

            // sort local branches first then remote ones
            // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, main is picked
            // and sort using natural sort so that 1.10 will appear before 1.9
            usort($branches, function ($a, $b) {
                $aRemote = 0 === strpos($a, 'remotes/');
                $bRemote = 0 === strpos($b, 'remotes/');

                if ($aRemote !== $bRemote) {
                    return $aRemote ? 1 : -1;
                }

                return strnatcasecmp($b, $a);
            });

            foreach ($branches as $candidate) {
                $candidateVersion = preg_replace('{^remotes/\S+/}', '', $candidate);

                // do not compare against itself or other feature branches
                if ($candidate === $branch || !preg_match('{^(' . $nonFeatureBranches . '|master|trunk|default|develop|\d+\..+)$}', $candidateVersion, $match)) {
                    continue;
                }

                $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
                if (0 !== $this->process->execute($cmdLine, $output, $path)) {
                    continue;
                }

                if (strlen($output) < $length) {
                    $length = strlen($output);
                    $version = $this->versionParser->normalizeBranch($candidateVersion);
                    $prettyVersion = 'dev-' . $candidateVersion;
                    if ($length === 0) {
                        break;
                    }
                }
            }
        }

        return array('version' => $version, 'pretty_version' => $prettyVersion);
    }

    private function guessFossilVersion(array $packageConfig, $path)
    {
        $version = null;
        $prettyVersion = null;

        // try to fetch current version from fossil
        if (0 === $this->process->execute('fossil branch list', $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $prettyVersion = 'dev-' . $branch;
        }

        // try to fetch current version from fossil tags
        if (0 === $this->process->execute('fossil tag list', $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));
                $prettyVersion = trim($output);
            } catch (\Exception $e) {
            }
        }

        return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
    }

    private function guessSvnVersion(array $packageConfig, $path)
    {
        SvnUtil::cleanEnv();

        // try to fetch current version from svn
        if (0 === $this->process->execute('svn info --xml', $output, $path)) {
            $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
            $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
            $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';

            $urlPattern = '#<url>.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))</url>#';

            if (preg_match($urlPattern, $output, $matches)) {
                if (isset($matches[2]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) {
                    // we are in a branches path
                    $version = $this->versionParser->normalizeBranch($matches[3]);
                    $prettyVersion = 'dev-' . $matches[3];

                    return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
                }

                $prettyVersion = trim($matches[1]);
                $version = $this->versionParser->normalize($prettyVersion);

                return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
            }
        }
    }
}
<?php

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

namespace Composer\Package\Version;

use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Semver\Semver;

class VersionParser extends SemverVersionParser
{
    private static $constraints = array();

    /**
     * {@inheritDoc}
     */
    public function parseConstraints($constraints)
    {
        if (!isset(self::$constraints[$constraints])) {
            self::$constraints[$constraints] = parent::parseConstraints($constraints);
        }

        return self::$constraints[$constraints];
    }

    /**
     * Parses an array of strings representing package/version pairs.
     *
     * The parsing results in an array of arrays, each of which
     * contain a 'name' key with value and optionally a 'version' key with value.
     *
     * @param array $pairs a set of package/version pairs separated by ":", "=" or " "
     *
     * @return array[] array of arrays containing a name and (if provided) a version
     */
    public function parseNameVersionPairs(array $pairs)
    {
        $pairs = array_values($pairs);
        $result = array();

        for ($i = 0, $count = count($pairs); $i < $count; $i++) {
            $pair = preg_replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i]));
            if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/') && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $pairs[$i + 1])) {
                $pair .= ' '.$pairs[$i + 1];
                $i++;
            }

            if (strpos($pair, ' ')) {
                list($name, $version) = explode(' ', $pair, 2);
                $result[] = array('name' => $name, 'version' => $version);
            } else {
                $result[] = array('name' => $pair);
            }
        }

        return $result;
    }

    /**
     * @return bool
     */
    public static function isUpgrade($normalizedFrom, $normalizedTo)
    {
        if (substr($normalizedFrom, 0, 4) === 'dev-' || substr($normalizedTo, 0, 4) === 'dev-') {
            return true;
        }

        $sorted = Semver::sort(array($normalizedTo, $normalizedFrom));

        return $sorted[0] === $normalizedFrom;
    }
}
<?php

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

namespace Composer\Package\Version;

use Composer\DependencyResolver\Pool;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Composer;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Semver\Constraint\Constraint;

/**
 * Selects the best possible version for a package
 *
 * @author Ryan Weaver <ryan@knpuniversity.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionSelector
{
    private $pool;

    private $parser;

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    /**
     * Given a package name and optional version, returns the latest PackageInterface
     * that matches.
     *
     * @param  string                $packageName
     * @param  string                $targetPackageVersion
     * @param  string                $targetPhpVersion
     * @param  string                $preferredStability
     * @return PackageInterface|bool
     */
    public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable')
    {
        $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
        $candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true);

        if ($targetPhpVersion) {
            $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion));
            $composerRuntimeConstraint = new Constraint('==', $this->getParser()->normalize(Composer::RUNTIME_API_VERSION));
            $composerPluginConstraint = new Constraint('==', $this->getParser()->normalize(PluginInterface::PLUGIN_API_VERSION));
            $candidates = array_filter($candidates, function ($pkg) use ($phpConstraint, $composerPluginConstraint, $composerRuntimeConstraint) {
                $reqs = $pkg->getRequires();

                return (!isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint))
                    && (!isset($reqs['composer-plugin-api']) || $reqs['composer-plugin-api']->getConstraint()->matches($composerPluginConstraint))
                    && (!isset($reqs['composer-runtime-api']) || $reqs['composer-runtime-api']->getConstraint()->matches($composerRuntimeConstraint));
            });
        }

        if (!$candidates) {
            return false;
        }

        // select highest version if we have many
        $package = reset($candidates);
        $minPriority = BasePackage::$stabilities[$preferredStability];
        foreach ($candidates as $candidate) {
            $candidatePriority = $candidate->getStabilityPriority();
            $currentPriority = $package->getStabilityPriority();

            // candidate is less stable than our preferred stability,
            // and current package is more stable than candidate, skip it
            if ($minPriority < $candidatePriority && $currentPriority < $candidatePriority) {
                continue;
            }

            // candidate is less stable than our preferred stability,
            // and current package is less stable than candidate, select candidate
            if ($minPriority < $candidatePriority && $candidatePriority < $currentPriority) {
                $package = $candidate;
                continue;
            }

            // candidate is more stable than our preferred stability,
            // and current package is less stable than preferred stability, select candidate
            if ($minPriority >= $candidatePriority && $minPriority < $currentPriority) {
                $package = $candidate;
                continue;
            }

            // select highest version of the two
            if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
                $package = $candidate;
            }
        }

        return $package;
    }

    /**
     * Given a concrete version, this returns a ~ constraint (when possible)
     * that should be used, for example, in composer.json.
     *
     * For example:
     *  * 1.2.1         -> ^1.2
     *  * 1.2           -> ^1.2
     *  * v3.2.1        -> ^3.2
     *  * 2.0-beta.1    -> ^2.0@beta
     *  * dev-master    -> ^2.1@dev      (dev version with alias)
     *  * dev-master    -> dev-master    (dev versions are untouched)
     *
     * @param  PackageInterface $package
     * @return string
     */
    public function findRecommendedRequireVersion(PackageInterface $package)
    {
        $version = $package->getVersion();
        if (!$package->isDev()) {
            return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
        }

        $loader = new ArrayLoader($this->getParser());
        $dumper = new ArrayDumper();
        $extra = $loader->getBranchAlias($dumper->dump($package));
        if ($extra) {
            $extra = preg_replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count);
            if ($count) {
                $extra = str_replace('.9999999', '.0', $extra);

                return $this->transformVersion($extra, $extra, 'dev');
            }
        }

        return $package->getPrettyVersion();
    }

    private function transformVersion($version, $prettyVersion, $stability)
    {
        // attempt to transform 2.1.1 to 2.1
        // this allows you to upgrade through minor versions
        $semanticVersionParts = explode('.', $version);

        // check to see if we have a semver-looking version
        if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) {
            // remove the last parts (i.e. the patch version number and any extra)
            if ($semanticVersionParts[0] === '0') {
                unset($semanticVersionParts[3]);
            } else {
                unset($semanticVersionParts[2], $semanticVersionParts[3]);
            }
            $version = implode('.', $semanticVersionParts);
        } else {
            return $prettyVersion;
        }

        // append stability flag if not default
        if ($stability != 'stable') {
            $version .= '@'.$stability;
        }

        // 2.1 -> ^2.1
        return '^' . $version;
    }

    private function getParser()
    {
        if ($this->parser === null) {
            $this->parser = new VersionParser();
        }

        return $this->parser;
    }
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\ConstraintInterface;

trigger_error('The ' . __NAMESPACE__ . '\LinkConstraintInterface interface is deprecated, use Composer\Semver\Constraint\ConstraintInterface instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\ConstraintInterface instead
 */
interface LinkConstraintInterface extends ConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\AbstractConstraint;

trigger_error('The ' . __NAMESPACE__ . '\SpecificConstraint abstract class is deprecated, there is no replacement for it.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\AbstractConstraint instead
 */
abstract class SpecificConstraint extends AbstractConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\EmptyConstraint as SemverEmptyConstraint;

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

/**
 * @deprecated use Composer\Semver\Constraint\EmptyConstraint instead
 */
class EmptyConstraint extends SemverEmptyConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\Constraint;

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

/**
 * @deprecated use Composer\Semver\Constraint\Constraint instead
 */
class VersionConstraint extends Constraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\MultiConstraint as SemverMultiConstraint;

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

/**
 * @deprecated use Composer\Semver\Constraint\MultiConstraint instead
 */
class MultiConstraint extends SemverMultiConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package;

/**
 * The root package represents the project's composer.json and contains additional metadata
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackage extends CompletePackage implements RootPackageInterface
{
    protected $minimumStability = 'stable';
    protected $preferStable = false;
    protected $stabilityFlags = array();
    protected $config = array();
    protected $references = array();
    protected $aliases = array();

    /**
     * Set the minimumStability
     *
     * @param string $minimumStability
     */
    public function setMinimumStability($minimumStability)
    {
        $this->minimumStability = $minimumStability;
    }

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

    /**
     * Set the stabilityFlags
     *
     * @param array $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags)
    {
        $this->stabilityFlags = $stabilityFlags;
    }

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

    /**
     * Set the preferStable
     *
     * @param bool $preferStable
     */
    public function setPreferStable($preferStable)
    {
        $this->preferStable = $preferStable;
    }

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

    /**
     * Set the config
     *
     * @param array $config
     */
    public function setConfig(array $config)
    {
        $this->config = $config;
    }

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

    /**
     * Set the references
     *
     * @param array $references
     */
    public function setReferences(array $references)
    {
        $this->references = $references;
    }

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

    /**
     * Set the aliases
     *
     * @param array $aliases
     */
    public function setAliases(array $aliases)
    {
        $this->aliases = $aliases;
    }

    /**
     * {@inheritDoc}
     */
    public function getAliases()
    {
        return $this->aliases;
    }
}
<?php

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

namespace Composer\Package;

/**
 * Defines package metadata that is not necessarily needed for solving and installing packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface CompletePackageInterface extends PackageInterface
{
    /**
     * Returns the scripts of this package
     *
     * @return array array('script name' => array('listeners'))
     */
    public function getScripts();

    /**
     * Returns an array of repositories
     *
     * {"<type>": {<config key/values>}}
     *
     * @return array Repositories
     */
    public function getRepositories();

    /**
     * Returns the package license, e.g. MIT, BSD, GPL
     *
     * @return array The package licenses
     */
    public function getLicense();

    /**
     * Returns an array of keywords relating to the package
     *
     * @return array
     */
    public function getKeywords();

    /**
     * Returns the package description
     *
     * @return string
     */
    public function getDescription();

    /**
     * Returns the package homepage
     *
     * @return string
     */
    public function getHomepage();

    /**
     * Returns an array of authors of the package
     *
     * Each item can contain name/homepage/email keys
     *
     * @return array
     */
    public function getAuthors();

    /**
     * Returns the support information
     *
     * @return array
     */
    public function getSupport();

    /**
     * Returns an array of funding options for the package
     *
     * Each item will contain type and url keys
     *
     * @return array
     */
    public function getFunding();

    /**
     * Returns if the package is abandoned or not
     *
     * @return bool
     */
    public function isAbandoned();

    /**
     * If the package is abandoned and has a suggested replacement, this method returns it
     *
     * @return string
     */
    public function getReplacementPackage();
}
<?php

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

namespace Composer\Package;

use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AliasPackage extends BasePackage implements CompletePackageInterface
{
    protected $version;
    protected $prettyVersion;
    protected $dev;
    protected $rootPackageAlias = false;
    protected $stability;

    /** @var PackageInterface */
    protected $aliasOf;
    /** @var Link[] */
    protected $requires;
    /** @var Link[] */
    protected $devRequires;
    /** @var Link[] */
    protected $conflicts;
    /** @var Link[] */
    protected $provides;
    /** @var Link[] */
    protected $replaces;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param PackageInterface $aliasOf       The package this package is an alias of
     * @param string           $version       The version the alias must report
     * @param string           $prettyVersion The alias's non-normalized version
     */
    public function __construct(PackageInterface $aliasOf, $version, $prettyVersion)
    {
        parent::__construct($aliasOf->getName());

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;
        $this->aliasOf = $aliasOf;
        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';

        foreach (array('requires', 'devRequires', 'conflicts', 'provides', 'replaces') as $type) {
            $links = $aliasOf->{'get' . ucfirst($type)}();
            $this->$type = $this->replaceSelfVersionDependencies($links, $type);
        }
    }

    /**
     * @return PackageInterface
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

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

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

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

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

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

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

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

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

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

    /**
     * Stores whether this is an alias created by an aliasing in the requirements of the root package or not
     *
     * Use by the policy for sorting manually aliased packages first, see #576
     *
     * @param bool $value
     *
     * @return mixed
     */
    public function setRootPackageAlias($value)
    {
        return $this->rootPackageAlias = $value;
    }

    /**
     * @see setRootPackageAlias
     * @return bool
     */
    public function isRootPackageAlias()
    {
        return $this->rootPackageAlias;
    }

    /**
     * @param Link[] $links
     * @param string $linkType
     *
     * @return Link[]
     */
    protected function replaceSelfVersionDependencies(array $links, $linkType)
    {
        if (in_array($linkType, array('conflicts', 'provides', 'replaces'), true)) {
            $newLinks = array();
            foreach ($links as $link) {
                // link is self.version, but must be replacing also the replaced version
                if ('self.version' === $link->getPrettyConstraint()) {
                    $newLinks[] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion);
                }
            }
            $links = array_merge($links, $newLinks);
        } else {
            foreach ($links as $index => $link) {
                if ('self.version' === $link->getPrettyConstraint()) {
                    $links[$index] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion);
                }
            }
        }

        return $links;
    }

    /***************************************
     * Wrappers around the aliased package *
     ***************************************/

    public function getType()
    {
        return $this->aliasOf->getType();
    }

    public function getTargetDir()
    {
        return $this->aliasOf->getTargetDir();
    }

    public function getExtra()
    {
        return $this->aliasOf->getExtra();
    }

    public function setInstallationSource($type)
    {
        $this->aliasOf->setInstallationSource($type);
    }

    public function getInstallationSource()
    {
        return $this->aliasOf->getInstallationSource();
    }

    public function getSourceType()
    {
        return $this->aliasOf->getSourceType();
    }

    public function getSourceUrl()
    {
        return $this->aliasOf->getSourceUrl();
    }

    public function getSourceUrls()
    {
        return $this->aliasOf->getSourceUrls();
    }

    public function getSourceReference()
    {
        return $this->aliasOf->getSourceReference();
    }

    public function setSourceReference($reference)
    {
        return $this->aliasOf->setSourceReference($reference);
    }

    public function setSourceMirrors($mirrors)
    {
        return $this->aliasOf->setSourceMirrors($mirrors);
    }

    public function getSourceMirrors()
    {
        return $this->aliasOf->getSourceMirrors();
    }

    public function getDistType()
    {
        return $this->aliasOf->getDistType();
    }

    public function getDistUrl()
    {
        return $this->aliasOf->getDistUrl();
    }

    public function getDistUrls()
    {
        return $this->aliasOf->getDistUrls();
    }

    public function getDistReference()
    {
        return $this->aliasOf->getDistReference();
    }

    public function setDistReference($reference)
    {
        return $this->aliasOf->setDistReference($reference);
    }

    public function getDistSha1Checksum()
    {
        return $this->aliasOf->getDistSha1Checksum();
    }

    public function setTransportOptions(array $options)
    {
        return $this->aliasOf->setTransportOptions($options);
    }

    public function getTransportOptions()
    {
        return $this->aliasOf->getTransportOptions();
    }

    public function setDistMirrors($mirrors)
    {
        return $this->aliasOf->setDistMirrors($mirrors);
    }

    public function getDistMirrors()
    {
        return $this->aliasOf->getDistMirrors();
    }

    public function getScripts()
    {
        return $this->aliasOf->getScripts();
    }

    public function getLicense()
    {
        return $this->aliasOf->getLicense();
    }

    public function getAutoload()
    {
        return $this->aliasOf->getAutoload();
    }

    public function getDevAutoload()
    {
        return $this->aliasOf->getDevAutoload();
    }

    public function getIncludePaths()
    {
        return $this->aliasOf->getIncludePaths();
    }

    public function getRepositories()
    {
        return $this->aliasOf->getRepositories();
    }

    public function getReleaseDate()
    {
        return $this->aliasOf->getReleaseDate();
    }

    public function getBinaries()
    {
        return $this->aliasOf->getBinaries();
    }

    public function getKeywords()
    {
        return $this->aliasOf->getKeywords();
    }

    public function getDescription()
    {
        return $this->aliasOf->getDescription();
    }

    public function getHomepage()
    {
        return $this->aliasOf->getHomepage();
    }

    public function getSuggests()
    {
        return $this->aliasOf->getSuggests();
    }

    public function getAuthors()
    {
        return $this->aliasOf->getAuthors();
    }

    public function getSupport()
    {
        return $this->aliasOf->getSupport();
    }

    public function getFunding()
    {
        return $this->aliasOf->getFunding();
    }

    public function getNotificationUrl()
    {
        return $this->aliasOf->getNotificationUrl();
    }

    public function getArchiveExcludes()
    {
        return $this->aliasOf->getArchiveExcludes();
    }

    public function isAbandoned()
    {
        return $this->aliasOf->isAbandoned();
    }

    public function getReplacementPackage()
    {
        return $this->aliasOf->getReplacementPackage();
    }

    public function __toString()
    {
        return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';
    }

    public function setDistUrl($url)
    {
        return $this->aliasOf->setDistUrl($url);
    }

    public function setDistType($type)
    {
        return $this->aliasOf->setDistType($type);
    }
}
<?php

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

namespace Composer\Package;

use Composer\Package\Version\VersionParser;
use Composer\Util\ComposerMirror;

/**
 * Core package definitions that are needed to resolve dependencies and install packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Package extends BasePackage
{
    protected $type;
    protected $targetDir;
    protected $installationSource;
    protected $sourceType;
    protected $sourceUrl;
    protected $sourceReference;
    protected $sourceMirrors;
    protected $distType;
    protected $distUrl;
    protected $distReference;
    protected $distSha1Checksum;
    protected $distMirrors;
    protected $version;
    protected $prettyVersion;
    protected $releaseDate;
    protected $extra = array();
    protected $binaries = array();
    protected $dev;
    protected $stability;
    protected $notificationUrl;

    /** @var Link[] */
    protected $requires = array();
    /** @var Link[] */
    protected $conflicts = array();
    /** @var Link[] */
    protected $provides = array();
    /** @var Link[] */
    protected $replaces = array();
    /** @var Link[] */
    protected $devRequires = array();
    protected $suggests = array();
    protected $autoload = array();
    protected $devAutoload = array();
    protected $includePaths = array();
    protected $archiveExcludes = array();

    /**
     * Creates a new in memory package.
     *
     * @param string $name          The package's name
     * @param string $version       The package's version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function __construct($name, $version, $prettyVersion)
    {
        parent::__construct($name);

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

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

    /**
     * @param string $type
     */
    public function setType($type)
    {
        $this->type = $type;
    }

    /**
     * {@inheritDoc}
     */
    public function getType()
    {
        return $this->type ?: 'library';
    }

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

    /**
     * @param string $targetDir
     */
    public function setTargetDir($targetDir)
    {
        $this->targetDir = $targetDir;
    }

    /**
     * {@inheritDoc}
     */
    public function getTargetDir()
    {
        if (null === $this->targetDir) {
            return;
        }

        return ltrim(preg_replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/');
    }

    /**
     * @param array $extra
     */
    public function setExtra(array $extra)
    {
        $this->extra = $extra;
    }

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

    /**
     * @param array $binaries
     */
    public function setBinaries(array $binaries)
    {
        $this->binaries = $binaries;
    }

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

    /**
     * {@inheritDoc}
     */
    public function setInstallationSource($type)
    {
        $this->installationSource = $type;
    }

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

    /**
     * @param string $type
     */
    public function setSourceType($type)
    {
        $this->sourceType = $type;
    }

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

    /**
     * @param string $url
     */
    public function setSourceUrl($url)
    {
        $this->sourceUrl = $url;
    }

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

    /**
     * @param string $reference
     */
    public function setSourceReference($reference)
    {
        $this->sourceReference = $reference;
    }

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

    /**
     * @param array|null $mirrors
     */
    public function setSourceMirrors($mirrors)
    {
        $this->sourceMirrors = $mirrors;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getSourceUrls()
    {
        return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source');
    }

    /**
     * @param string $type
     */
    public function setDistType($type)
    {
        $this->distType = $type;
    }

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

    /**
     * @param string $url
     */
    public function setDistUrl($url)
    {
        $this->distUrl = $url;
    }

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

    /**
     * @param string $reference
     */
    public function setDistReference($reference)
    {
        $this->distReference = $reference;
    }

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

    /**
     * @param string $sha1checksum
     */
    public function setDistSha1Checksum($sha1checksum)
    {
        $this->distSha1Checksum = $sha1checksum;
    }

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

    /**
     * @param array|null $mirrors
     */
    public function setDistMirrors($mirrors)
    {
        $this->distMirrors = $mirrors;
    }

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

    /**
     * {@inheritDoc}
     */
    public function getDistUrls()
    {
        return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist');
    }

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

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

    /**
     * Set the releaseDate
     *
     * @param \DateTime $releaseDate
     */
    public function setReleaseDate(\DateTime $releaseDate)
    {
        $this->releaseDate = $releaseDate;
    }

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

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires)
    {
        $this->requires = $requires;
    }

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

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts)
    {
        $this->conflicts = $conflicts;
    }

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

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides)
    {
        $this->provides = $provides;
    }

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

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces)
    {
        $this->replaces = $replaces;
    }

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

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires)
    {
        $this->devRequires = $devRequires;
    }

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

    /**
     * Set the suggested packages
     *
     * @param array $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests)
    {
        $this->suggests = $suggests;
    }

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

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     */
    public function setAutoload(array $autoload)
    {
        $this->autoload = $autoload;
    }

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

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     */
    public function setDevAutoload(array $devAutoload)
    {
        $this->devAutoload = $devAutoload;
    }

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

    /**
     * Sets the list of paths added to PHP's include path.
     *
     * @param array $includePaths List of directories.
     */
    public function setIncludePaths(array $includePaths)
    {
        $this->includePaths = $includePaths;
    }

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

    /**
     * Sets the notification URL
     *
     * @param string $notificationUrl
     */
    public function setNotificationUrl($notificationUrl)
    {
        $this->notificationUrl = $notificationUrl;
    }

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

    /**
     * Sets a list of patterns to be excluded from archives
     *
     * @param array $excludes
     */
    public function setArchiveExcludes(array $excludes)
    {
        $this->archiveExcludes = $excludes;
    }

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

    /**
     * Replaces current version and pretty version with passed values.
     * It also sets stability.
     *
     * @param string $version       The package's normalized version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function replaceVersion($version, $prettyVersion)
    {
        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    protected function getUrls($url, $mirrors, $ref, $type, $urlType)
    {
        if (!$url) {
            return array();
        }
        $urls = array($url);
        if ($mirrors) {
            foreach ($mirrors as $mirror) {
                if ($urlType === 'dist') {
                    $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type);
                } elseif ($urlType === 'source' && $type === 'git') {
                    $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type);
                } elseif ($urlType === 'source' && $type === 'hg') {
                    $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type);
                }
                if (!in_array($mirrorUrl, $urls)) {
                    $func = $mirror['preferred'] ? 'array_unshift' : 'array_push';
                    $func($urls, $mirrorUrl);
                }
            }
        }

        return $urls;
    }
}
<?php

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

namespace Composer\Package\Archiver;

use FilterIterator;
use PharData;

class ArchivableFilesFilter extends FilterIterator
{
    private $dirs = array();

    /**
     * @return bool true if the current element is acceptable, otherwise false.
     */
    public function accept()
    {
        $file = $this->getInnerIterator()->current();
        if ($file->isDir()) {
            $this->dirs[] = (string) $file;

            return false;
        }

        return true;
    }

    public function addEmptyDir(PharData $phar, $sources)
    {
        foreach ($this->dirs as $filepath) {
            $localname = str_replace($sources . "/", '', $filepath);
            $phar->addEmptyDir($localname);
        }
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * An exclude filter which processes composer's own exclude rules
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ComposerExcludeFilter extends BaseExcludeFilter
{
    /**
     * @param string $sourcePath   Directory containing sources to be filtered
     * @param array  $excludeRules An array of exclude rules from composer.json
     */
    public function __construct($sourcePath, array $excludeRules)
    {
        parent::__construct($sourcePath);
        $this->excludePatterns = $this->generatePatterns($excludeRules);
    }
}
<?php

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

namespace Composer\Package\Archiver;

use Symfony\Component\Finder;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BaseExcludeFilter
{
    /**
     * @var string
     */
    protected $sourcePath;

    /**
     * @var array
     */
    protected $excludePatterns;

    /**
     * @param string $sourcePath Directory containing sources to be filtered
     */
    public function __construct($sourcePath)
    {
        $this->sourcePath = $sourcePath;
        $this->excludePatterns = array();
    }

    /**
     * Checks the given path against all exclude patterns in this filter
     *
     * Negated patterns overwrite exclude decisions of previous filters.
     *
     * @param string $relativePath The file's path relative to the sourcePath
     * @param bool   $exclude      Whether a previous filter wants to exclude this file
     *
     * @return bool Whether the file should be excluded
     */
    public function filter($relativePath, $exclude)
    {
        foreach ($this->excludePatterns as $patternData) {
            list($pattern, $negate, $stripLeadingSlash) = $patternData;

            if ($stripLeadingSlash) {
                $path = substr($relativePath, 1);
            } else {
                $path = $relativePath;
            }

            if (@preg_match($pattern, $path)) {
                $exclude = !$negate;
            }
        }

        return $exclude;
    }

    /**
     * Processes a file containing exclude rules of different formats per line
     *
     * @param array    $lines      A set of lines to be parsed
     * @param callable $lineParser The parser to be used on each line
     *
     * @return array Exclude patterns to be used in filter()
     */
    protected function parseLines(array $lines, $lineParser)
    {
        return array_filter(
            array_map(
                function ($line) use ($lineParser) {
                    $line = trim($line);

                    if (!$line || 0 === strpos($line, '#')) {
                        return null;
                    }

                    return call_user_func($lineParser, $line);
                },
                $lines
            ),
            function ($pattern) {
                return $pattern !== null;
            }
        );
    }

    /**
     * Generates a set of exclude patterns for filter() from gitignore rules
     *
     * @param array $rules A list of exclude rules in gitignore syntax
     *
     * @return array Exclude patterns
     */
    protected function generatePatterns($rules)
    {
        $patterns = array();
        foreach ($rules as $rule) {
            $patterns[] = $this->generatePattern($rule);
        }

        return $patterns;
    }

    /**
     * Generates an exclude pattern for filter() from a gitignore rule
     *
     * @param string $rule An exclude rule in gitignore syntax
     *
     * @return array An exclude pattern
     */
    protected function generatePattern($rule)
    {
        $negate = false;
        $pattern = '{';

        if (strlen($rule) && $rule[0] === '!') {
            $negate = true;
            $rule = substr($rule, 1);
        }

        if (strlen($rule) && $rule[0] === '/') {
            $pattern .= '^/';
            $rule = substr($rule, 1);
        } elseif (strlen($rule) - 1 === strpos($rule, '/')) {
            $pattern .= '/';
            $rule = substr($rule, 0, -1);
        } elseif (false === strpos($rule, '/')) {
            $pattern .= '/';
        }

        // remove delimiters as well as caret (^) and dollar sign ($) from the regex
        $pattern .= substr(Finder\Glob::toRegex($rule), 2, -2) . '(?=$|/)';

        return array($pattern . '}', $negate, false);
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Nils Adermann <naderman@naderman.de>
 */
interface ArchiverInterface
{
    /**
     * Create an archive from the sources.
     *
     * @param string $sources  The sources directory
     * @param string $target   The target file
     * @param string $format   The format used for archive
     * @param array  $excludes A list of patterns for files to exclude
     *
     * @return string The path to the written archive file
     */
    public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false);

    /**
     * Format supported by the archiver.
     *
     * @param string $format     The archive format
     * @param string $sourceType The source type (git, svn, hg, etc.)
     *
     * @return bool true if the format is supported by the archiver
     */
    public function supports($format, $sourceType);
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Nils Adermann <naderman@naderman.de>
 * @author Matthieu Moquet <matthieu@moquet.net>
 */
class PharArchiver implements ArchiverInterface
{
    protected static $formats = array(
        'zip' => \Phar::ZIP,
        'tar' => \Phar::TAR,
        'tar.gz' => \Phar::TAR,
        'tar.bz2' => \Phar::TAR,
    );

    protected static $compressFormats = array(
        'tar.gz' => \Phar::GZ,
        'tar.bz2' => \Phar::BZ2,
    );

    /**
     * {@inheritdoc}
     */
    public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false)
    {
        $sources = realpath($sources);

        // Phar would otherwise load the file which we don't want
        if (file_exists($target)) {
            unlink($target);
        }

        try {
            $filename = substr($target, 0, strrpos($target, $format) - 1);

            // Check if compress format
            if (isset(static::$compressFormats[$format])) {
                // Current compress format supported base on tar
                $target = $filename . '.tar';
            }

            $phar = new \PharData($target, null, null, static::$formats[$format]);
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            $filesOnly = new ArchivableFilesFilter($files);
            $phar->buildFromIterator($filesOnly, $sources);
            $filesOnly->addEmptyDir($phar, $sources);

            if (isset(static::$compressFormats[$format])) {
                // Check can be compressed?
                if (!$phar->canCompress(static::$compressFormats[$format])) {
                    throw new \RuntimeException(sprintf('Can not compress to %s format', $format));
                }

                // Delete old tar
                unlink($target);

                // Compress the new tar
                $phar->compress(static::$compressFormats[$format]);

                // Make the correct filename
                $target = $filename . '.' . $format;
            }

            return $target;
        } catch (\UnexpectedValueException $e) {
            $message = sprintf(
                "Could not create archive '%s' from '%s': %s",
                $target,
                $sources,
                $e->getMessage()
            );

            throw new \RuntimeException($message, $e->getCode(), $e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function supports($format, $sourceType)
    {
        return isset(static::$formats[$format]);
    }
}
<?php

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

namespace Composer\Package\Archiver;

use Composer\Util\Filesystem;
use FilesystemIterator;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

/**
 * A Symfony Finder wrapper which locates files that should go into archives
 *
 * Handles .gitignore, .gitattributes and .hgignore files as well as composer's
 * own exclude rules from composer.json
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArchivableFilesFinder extends \FilterIterator
{
    /**
     * @var Finder
     */
    protected $finder;

    /**
     * Initializes the internal Symfony Finder with appropriate filters
     *
     * @param string $sources       Path to source files to be archived
     * @param array  $excludes      Composer's own exclude rules from composer.json
     * @param bool   $ignoreFilters Ignore filters when looking for files
     */
    public function __construct($sources, array $excludes, $ignoreFilters = false)
    {
        $fs = new Filesystem();

        $sources = $fs->normalizePath(realpath($sources));

        if ($ignoreFilters) {
            $filters = array();
        } else {
            $filters = array(
                new HgExcludeFilter($sources),
                new GitExcludeFilter($sources),
                new ComposerExcludeFilter($sources, $excludes),
            );
        }

        $this->finder = new Finder();

        $filter = function (\SplFileInfo $file) use ($sources, $filters, $fs) {
            if ($file->isLink() && strpos($file->getRealPath(), $sources) !== 0) {
                return false;
            }

            $relativePath = preg_replace(
                '#^'.preg_quote($sources, '#').'#',
                '',
                $fs->normalizePath($file->getRealPath())
            );

            $exclude = false;
            foreach ($filters as $filter) {
                $exclude = $filter->filter($relativePath, $exclude);
            }

            return !$exclude;
        };

        if (method_exists($filter, 'bindTo')) {
            $filter = $filter->bindTo(null);
        }

        $this->finder
            ->in($sources)
            ->filter($filter)
            ->ignoreVCS(true)
            ->ignoreDotFiles(false);

        parent::__construct($this->finder->getIterator());
    }

    public function accept()
    {
        /** @var SplFileInfo $current */
        $current = $this->getInnerIterator()->current();

        if (!$current->isDir()) {
            return true;
        }

        $iterator = new FilesystemIterator($current, FilesystemIterator::SKIP_DOTS);

        return !$iterator->valid();
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * An exclude filter that processes gitignore and gitattributes
 *
 * It respects export-ignore git attributes
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class GitExcludeFilter extends BaseExcludeFilter
{
    /**
     * Parses .gitignore and .gitattributes files if they exist
     *
     * @param string $sourcePath
     */
    public function __construct($sourcePath)
    {
        parent::__construct($sourcePath);

        if (file_exists($sourcePath.'/.gitignore')) {
            $this->excludePatterns = $this->parseLines(
                file($sourcePath.'/.gitignore'),
                array($this, 'parseGitIgnoreLine')
            );
        }
        if (file_exists($sourcePath.'/.gitattributes')) {
            $this->excludePatterns = array_merge(
                $this->excludePatterns,
                $this->parseLines(
                    file($sourcePath.'/.gitattributes'),
                    array($this, 'parseGitAttributesLine')
                )
            );
        }
    }

    /**
     * Callback line parser which process gitignore lines
     *
     * @param string $line A line from .gitignore
     *
     * @return array An exclude pattern for filter()
     */
    public function parseGitIgnoreLine($line)
    {
        return $this->generatePattern($line);
    }

    /**
     * Callback parser which finds export-ignore rules in git attribute lines
     *
     * @param string $line A line from .gitattributes
     *
     * @return array|null An exclude pattern for filter()
     */
    public function parseGitAttributesLine($line)
    {
        $parts = preg_split('#\s+#', $line);

        if (count($parts) == 2 && $parts[1] === 'export-ignore') {
            return $this->generatePattern($parts[0]);
        }

        return null;
    }
}
<?php

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

namespace Composer\Package\Archiver;

use ZipArchive;
use Composer\Util\Filesystem;

/**
 * @author Jan Prieser <jan@prieser.net>
 */
class ZipArchiver implements ArchiverInterface
{
    protected static $formats = array(
        'zip' => 1,
    );

    /**
     * {@inheritdoc}
     */
    public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false)
    {
        $fs = new Filesystem();
        $sources = $fs->normalizePath($sources);

        $zip = new ZipArchive();
        $res = $zip->open($target, ZipArchive::CREATE);
        if ($res === true) {
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            foreach ($files as $file) {
                /** @var \SplFileInfo $file */
                $filepath = strtr($file->getPath()."/".$file->getFilename(), '\\', '/');
                $localname = str_replace($sources.'/', '', $filepath);
                if ($file->isDir()) {
                    $zip->addEmptyDir($localname);
                } else {
                    $zip->addFile($filepath, $localname);
                }

                /**
                 * ZipArchive::setExternalAttributesName is available from >= PHP 5.6
                 */
                if (PHP_VERSION_ID >= 50600) {
                    $perms = fileperms($filepath);

                    /**
                     * Ensure to preserve the permission umasks for the filepath in the archive.
                     */
                    $zip->setExternalAttributesName($localname, ZipArchive::OPSYS_UNIX, $perms << 16);
                }
            }
            if ($zip->close()) {
                return $target;
            }
        }
        $message = sprintf(
            "Could not create archive '%s' from '%s': %s",
            $target,
            $sources,
            $zip->getStatusString()
        );
        throw new \RuntimeException($message);
    }

    /**
     * {@inheritdoc}
     */
    public function supports($format, $sourceType)
    {
        return isset(static::$formats[$format]) && $this->compressionAvailable();
    }

    private function compressionAvailable()
    {
        return class_exists('ZipArchive');
    }
}
<?php

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

namespace Composer\Package\Archiver;

use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Util\Filesystem;
use Composer\Json\JsonFile;

/**
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Till Klampaeckel <till@php.net>
 */
class ArchiveManager
{
    protected $downloadManager;

    protected $archivers = array();

    /**
     * @var bool
     */
    protected $overwriteFiles = true;

    /**
     * @param DownloadManager $downloadManager A manager used to download package sources
     */
    public function __construct(DownloadManager $downloadManager)
    {
        $this->downloadManager = $downloadManager;
    }

    /**
     * @param ArchiverInterface $archiver
     */
    public function addArchiver(ArchiverInterface $archiver)
    {
        $this->archivers[] = $archiver;
    }

    /**
     * Set whether existing archives should be overwritten
     *
     * @param bool $overwriteFiles New setting
     *
     * @return $this
     */
    public function setOverwriteFiles($overwriteFiles)
    {
        $this->overwriteFiles = $overwriteFiles;

        return $this;
    }

    /**
     * Generate a distinct filename for a particular version of a package.
     *
     * @param PackageInterface $package The package to get a name for
     *
     * @return string A filename without an extension
     */
    public function getPackageFilename(PackageInterface $package)
    {
        $nameParts = array(preg_replace('#[^a-z0-9-_]#i', '-', $package->getName()));

        if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) {
            array_push($nameParts, $package->getDistReference(), $package->getDistType());
        } else {
            array_push($nameParts, $package->getPrettyVersion(), $package->getDistReference());
        }

        if ($package->getSourceReference()) {
            $nameParts[] = substr(sha1($package->getSourceReference()), 0, 6);
        }

        $name = implode('-', array_filter($nameParts, function ($p) {
            return !empty($p);
        }));

        return str_replace('/', '-', $name);
    }

    /**
     * Create an archive of the specified package.
     *
     * @param  PackageInterface          $package       The package to archive
     * @param  string                    $format        The format of the archive (zip, tar, ...)
     * @param  string                    $targetDir     The directory where to build the archive
     * @param  string|null               $fileName      The relative file name to use for the archive, or null to generate
     *                                                  the package name. Note that the format will be appended to this name
     * @param  bool                      $ignoreFilters Ignore filters when looking for files in the package
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     * @return string                    The path of the created archive
     */
    public function archive(PackageInterface $package, $format, $targetDir, $fileName = null, $ignoreFilters = false)
    {
        if (empty($format)) {
            throw new \InvalidArgumentException('Format must be specified');
        }

        // Search for the most appropriate archiver
        $usableArchiver = null;
        foreach ($this->archivers as $archiver) {
            if ($archiver->supports($format, $package->getSourceType())) {
                $usableArchiver = $archiver;
                break;
            }
        }

        // Checks the format/source type are supported before downloading the package
        if (null === $usableArchiver) {
            throw new \RuntimeException(sprintf('No archiver found to support %s format', $format));
        }

        $filesystem = new Filesystem();
        if (null === $fileName) {
            $packageName = $this->getPackageFilename($package);
        } else {
            $packageName = $fileName;
        }

        // Archive filename
        $filesystem->ensureDirectoryExists($targetDir);
        $target = realpath($targetDir).'/'.$packageName.'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($target));

        if (!$this->overwriteFiles && file_exists($target)) {
            return $target;
        }

        if ($package instanceof RootPackageInterface) {
            $sourcePath = realpath('.');
        } else {
            // Directory used to download the sources
            $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid();
            $filesystem->ensureDirectoryExists($sourcePath);

            try {
                // Download sources
                $this->downloadManager->download($package, $sourcePath);
            } catch (\Exception $e) {
                $filesystem->removeDirectory($sourcePath);
                throw  $e;
            }

            // Check exclude from downloaded composer.json
            if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
                $jsonFile = new JsonFile($composerJsonPath);
                $jsonData = $jsonFile->read();
                if (!empty($jsonData['archive']['exclude'])) {
                    $package->setArchiveExcludes($jsonData['archive']['exclude']);
                }
            }
        }

        // Create the archive
        $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($tempTarget));

        $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes(), $ignoreFilters);
        $filesystem->rename($archivePath, $target);

        // cleanup temporary download
        if (!$package instanceof RootPackageInterface) {
            $filesystem->removeDirectory($sourcePath);
        }
        $filesystem->remove($tempTarget);

        return $target;
    }
}
<?php

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

namespace Composer\Package\Archiver;

use Symfony\Component\Finder;

/**
 * An exclude filter that processes hgignore files
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class HgExcludeFilter extends BaseExcludeFilter
{
    const HG_IGNORE_REGEX = 1;
    const HG_IGNORE_GLOB = 2;

    /**
     * Either HG_IGNORE_REGEX or HG_IGNORE_GLOB
     * @var int
     */
    protected $patternMode;

    /**
     * Parses .hgignore file if it exist
     *
     * @param string $sourcePath
     */
    public function __construct($sourcePath)
    {
        parent::__construct($sourcePath);

        $this->patternMode = self::HG_IGNORE_REGEX;

        if (file_exists($sourcePath.'/.hgignore')) {
            $this->excludePatterns = $this->parseLines(
                file($sourcePath.'/.hgignore'),
                array($this, 'parseHgIgnoreLine')
            );
        }
    }

    /**
     * Callback line parser which process hgignore lines
     *
     * @param string $line A line from .hgignore
     *
     * @return array|null An exclude pattern for filter()
     */
    public function parseHgIgnoreLine($line)
    {
        if (preg_match('#^syntax\s*:\s*(glob|regexp)$#', $line, $matches)) {
            if ($matches[1] === 'glob') {
                $this->patternMode = self::HG_IGNORE_GLOB;
            } else {
                $this->patternMode = self::HG_IGNORE_REGEX;
            }

            return null;
        }

        if ($this->patternMode == self::HG_IGNORE_GLOB) {
            return $this->patternFromGlob($line);
        }

        return $this->patternFromRegex($line);
    }

    /**
     * Generates an exclude pattern for filter() from a hg glob expression
     *
     * @param string $line A line from .hgignore in glob mode
     *
     * @return array An exclude pattern for filter()
     */
    protected function patternFromGlob($line)
    {
        $pattern = '#'.substr(Finder\Glob::toRegex($line), 2, -1).'#';
        $pattern = str_replace('[^/]*', '.*', $pattern);

        return array($pattern, false, true);
    }

    /**
     * Generates an exclude pattern for filter() from a hg regexp expression
     *
     * @param string $line A line from .hgignore in regexp mode
     *
     * @return array An exclude pattern for filter()
     */
    public function patternFromRegex($line)
    {
        // WTF need to escape the delimiter safely
        $pattern = '#'.preg_replace('/((?:\\\\\\\\)*)(\\\\?)#/', '\1\2\2\\#', $line).'#';

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

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

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * Base class for packages providing name storage and default match implementation
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BasePackage implements PackageInterface
{
    public static $supportedLinkTypes = array(
        'require' => array('description' => 'requires', 'method' => 'requires'),
        'conflict' => array('description' => 'conflicts', 'method' => 'conflicts'),
        'provide' => array('description' => 'provides', 'method' => 'provides'),
        'replace' => array('description' => 'replaces', 'method' => 'replaces'),
        'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'),
    );

    const STABILITY_STABLE = 0;
    const STABILITY_RC = 5;
    const STABILITY_BETA = 10;
    const STABILITY_ALPHA = 15;
    const STABILITY_DEV = 20;

    public static $stabilities = array(
        'stable' => self::STABILITY_STABLE,
        'RC' => self::STABILITY_RC,
        'beta' => self::STABILITY_BETA,
        'alpha' => self::STABILITY_ALPHA,
        'dev' => self::STABILITY_DEV,
    );

    /**
     * READ-ONLY: The package id, public for fast access in dependency solver
     * @var int
     */
    public $id;
    /** @var string */
    protected $name;
    /** @var string */
    protected $prettyName;
    /** @var RepositoryInterface */
    protected $repository;
    /** @var array */
    protected $transportOptions = array();

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param string $name The package's name
     */
    public function __construct($name)
    {
        $this->prettyName = $name;
        $this->name = strtolower($name);
        $this->id = -1;
    }

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

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

    /**
     * {@inheritDoc}
     */
    public function getNames()
    {
        $names = array(
            $this->getName() => true,
        );

        foreach ($this->getProvides() as $link) {
            $names[$link->getTarget()] = true;
        }

        foreach ($this->getReplaces() as $link) {
            $names[$link->getTarget()] = true;
        }

        return array_keys($names);
    }

    /**
     * {@inheritDoc}
     */
    public function setId($id)
    {
        $this->id = $id;
    }

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

    /**
     * {@inheritDoc}
     */
    public function setRepository(RepositoryInterface $repository)
    {
        if ($this->repository && $repository !== $this->repository) {
            throw new \LogicException('A package can only be added to one repository');
        }
        $this->repository = $repository;
    }

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

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

    /**
     * Configures the list of options to download package dist files
     *
     * @param array $options
     */
    public function setTransportOptions(array $options)
    {
        $this->transportOptions = $options;
    }

    /**
     * checks if this package is a platform package
     *
     * @return bool
     */
    public function isPlatform()
    {
        return $this->getRepository() instanceof PlatformRepository;
    }

    /**
     * Returns package unique name, constructed from name, version and release type.
     *
     * @return string
     */
    public function getUniqueName()
    {
        return $this->getName().'-'.$this->getVersion();
    }

    public function equals(PackageInterface $package)
    {
        $self = $this;
        if ($this instanceof AliasPackage) {
            $self = $this->getAliasOf();
        }
        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        return $package === $self;
    }

    /**
     * Converts the package into a readable and unique string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getUniqueName();
    }

    public function getPrettyString()
    {
        return $this->getPrettyName().' '.$this->getPrettyVersion();
    }

    /**
     * {@inheritDoc}
     */
    public function getFullPrettyVersion($truncate = true)
    {
        if (!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git'))) {
            return $this->getPrettyVersion();
        }

        // if source reference is a sha1 hash -- truncate
        if ($truncate && strlen($this->getSourceReference()) === 40) {
            return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7);
        }

        return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
    }

    public function getStabilityPriority()
    {
        return self::$stabilities[$this->getStability()];
    }

    public function __clone()
    {
        $this->repository = null;
        $this->id = -1;
    }

    /**
     * Build a regexp from a package name, expanding * globs as required
     *
     * @param  string $allowListPattern
     * @param  string $wrap Wrap the cleaned string by the given string
     * @return string
     */
    public static function packageNameToRegexp($allowListPattern, $wrap = '{^%s$}i')
    {
        $cleanedAllowListPattern = str_replace('\\*', '.*', preg_quote($allowListPattern));

        return sprintf($wrap, $cleanedAllowListPattern);
    }
}
<?php

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

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootAliasPackage extends AliasPackage implements RootPackageInterface
{
    public function __construct(RootPackageInterface $aliasOf, $version, $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

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

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

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

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

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

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

    /**
     * {@inheritDoc}
     */
    public function setRequires(array $require)
    {
        $this->requires = $this->replaceSelfVersionDependencies($require, 'requires');

        $this->aliasOf->setRequires($require);
    }

    /**
     * {@inheritDoc}
     */
    public function setDevRequires(array $devRequire)
    {
        $this->devRequires = $this->replaceSelfVersionDependencies($devRequire, 'devRequires');

        $this->aliasOf->setDevRequires($devRequire);
    }

    /**
     * {@inheritDoc}
     */
    public function setConflicts(array $conflicts)
    {
        $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, 'conflicts');
        $this->aliasOf->setConflicts($conflicts);
    }

    /**
     * {@inheritDoc}
     */
    public function setProvides(array $provides)
    {
        $this->provides = $this->replaceSelfVersionDependencies($provides, 'provides');
        $this->aliasOf->setProvides($provides);
    }

    /**
     * {@inheritDoc}
     */
    public function setReplaces(array $replaces)
    {
        $this->replaces = $this->replaceSelfVersionDependencies($replaces, 'replaces');
        $this->aliasOf->setReplaces($replaces);
    }

    /**
     * {@inheritDoc}
     */
    public function setRepositories($repositories)
    {
        $this->aliasOf->setRepositories($repositories);
    }

    /**
     * {@inheritDoc}
     */
    public function setAutoload(array $autoload)
    {
        $this->aliasOf->setAutoload($autoload);
    }

    /**
     * {@inheritDoc}
     */
    public function setDevAutoload(array $devAutoload)
    {
        $this->aliasOf->setDevAutoload($devAutoload);
    }

    /**
     * {@inheritDoc}
     */
    public function setStabilityFlags(array $stabilityFlags)
    {
        $this->aliasOf->setStabilityFlags($stabilityFlags);
    }

    /**
     * {@inheritDoc}
     */
    public function setSuggests(array $suggests)
    {
        $this->aliasOf->setSuggests($suggests);
    }

    /**
     * {@inheritDoc}
     */
    public function setExtra(array $extra)
    {
        $this->aliasOf->setExtra($extra);
    }

    public function __clone()
    {
        parent::__clone();
        $this->aliasOf = clone $this->aliasOf;
    }
}
<?php

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

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;

/**
 * Defines the essential information a package has that is used during solving/installation
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface PackageInterface
{
    /**
     * Returns the package's name without version info, thus not a unique identifier
     *
     * @return string package name
     */
    public function getName();

    /**
     * Returns the package's pretty (i.e. with proper case) name
     *
     * @return string package name
     */
    public function getPrettyName();

    /**
     * Returns a set of names that could refer to this package
     *
     * No version or release type information should be included in any of the
     * names. Provided or replaced package names need to be returned as well.
     *
     * @return array An array of strings referring to this package
     */
    public function getNames();

    /**
     * Allows the solver to set an id for this package to refer to it.
     *
     * @param int $id
     */
    public function setId($id);

    /**
     * Retrieves the package's id set through setId
     *
     * @return int The previously set package id
     */
    public function getId();

    /**
     * Returns whether the package is a development virtual package or a concrete one
     *
     * @return bool
     */
    public function isDev();

    /**
     * Returns the package type, e.g. library
     *
     * @return string The package type
     */
    public function getType();

    /**
     * Returns the package targetDir property
     *
     * @return string The package targetDir
     */
    public function getTargetDir();

    /**
     * Returns the package extra data
     *
     * @return array The package extra data
     */
    public function getExtra();

    /**
     * Sets source from which this package was installed (source/dist).
     *
     * @param string $type source/dist
     */
    public function setInstallationSource($type);

    /**
     * Returns source from which this package was installed (source/dist).
     *
     * @return string source/dist
     */
    public function getInstallationSource();

    /**
     * Returns the repository type of this package, e.g. git, svn
     *
     * @return string The repository type
     */
    public function getSourceType();

    /**
     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
     *
     * @return string The repository url
     */
    public function getSourceUrl();

    /**
     * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git
     *
     * @return array
     */
    public function getSourceUrls();

    /**
     * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git
     *
     * @return string The repository reference
     */
    public function getSourceReference();

    /**
     * Returns the source mirrors of this package
     *
     * @return array|null
     */
    public function getSourceMirrors();

    /**
     * Returns the type of the distribution archive of this version, e.g. zip, tarball
     *
     * @return string The repository type
     */
    public function getDistType();

    /**
     * Returns the url of the distribution archive of this version
     *
     * @return string
     */
    public function getDistUrl();

    /**
     * Returns the urls of the distribution archive of this version, including mirrors
     *
     * @return array
     */
    public function getDistUrls();

    /**
     * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git
     *
     * @return string
     */
    public function getDistReference();

    /**
     * Returns the sha1 checksum for the distribution archive of this version
     *
     * @return string
     */
    public function getDistSha1Checksum();

    /**
     * Returns the dist mirrors of this package
     *
     * @return array|null
     */
    public function getDistMirrors();

    /**
     * Returns the version of this package
     *
     * @return string version
     */
    public function getVersion();

    /**
     * Returns the pretty (i.e. non-normalized) version string of this package
     *
     * @return string version
     */
    public function getPrettyVersion();

    /**
     * Returns the pretty version string plus a git or hg commit hash of this package
     *
     * @see getPrettyVersion
     *
     * @param  bool   $truncate If the source reference is a sha1 hash, truncate it
     * @return string version
     */
    public function getFullPrettyVersion($truncate = true);

    /**
     * Returns the release date of the package
     *
     * @return \DateTime
     */
    public function getReleaseDate();

    /**
     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
     *
     * @return string
     */
    public function getStability();

    /**
     * Returns a set of links to packages which need to be installed before
     * this package can be installed
     *
     * @return Link[] An array of package links defining required packages
     */
    public function getRequires();

    /**
     * Returns a set of links to packages which must not be installed at the
     * same time as this package
     *
     * @return Link[] An array of package links defining conflicting packages
     */
    public function getConflicts();

    /**
     * Returns a set of links to virtual packages that are provided through
     * this package
     *
     * @return Link[] An array of package links defining provided packages
     */
    public function getProvides();

    /**
     * Returns a set of links to packages which can alternatively be
     * satisfied by installing this package
     *
     * @return Link[] An array of package links defining replaced packages
     */
    public function getReplaces();

    /**
     * Returns a set of links to packages which are required to develop
     * this package. These are installed if in dev mode.
     *
     * @return Link[] An array of package links defining packages required for development
     */
    public function getDevRequires();

    /**
     * Returns a set of package names and reasons why they are useful in
     * combination with this package.
     *
     * @return array An array of package suggestions with descriptions
     */
    public function getSuggests();

    /**
     * Returns an associative array of autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of autoloading rules
     */
    public function getAutoload();

    /**
     * Returns an associative array of dev autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of dev autoloading rules
     */
    public function getDevAutoload();

    /**
     * Returns a list of directories which should get added to PHP's
     * include path.
     *
     * @return array
     */
    public function getIncludePaths();

    /**
     * Stores a reference to the repository that owns the package
     *
     * @param RepositoryInterface $repository
     */
    public function setRepository(RepositoryInterface $repository);

    /**
     * Returns a reference to the repository that owns the package
     *
     * @return RepositoryInterface
     */
    public function getRepository();

    /**
     * Returns the package binaries
     *
     * @return array
     */
    public function getBinaries();

    /**
     * Returns package unique name, constructed from name and version.
     *
     * @return string
     */
    public function getUniqueName();

    /**
     * Returns the package notification url
     *
     * @return string
     */
    public function getNotificationUrl();

    /**
     * Converts the package into a readable and unique string
     *
     * @return string
     */
    public function __toString();

    /**
     * Converts the package into a pretty readable string
     *
     * @return string
     */
    public function getPrettyString();

    /**
     * Returns a list of patterns to exclude from package archives
     *
     * @return array
     */
    public function getArchiveExcludes();

    /**
     * Returns a list of options to download package dist files
     *
     * @return array
     */
    public function getTransportOptions();

    /**
     * @param string $reference
     *
     * @return void
     */
    public function setSourceReference($reference);

    /**
     * @param string $url
     *
     * @return void
     */
    public function setDistUrl($url);

    /**
     * @param string $type
     *
     * @return void
     */
    public function setDistType($type);

    /**
     * @param string $reference
     *
     * @return void
     */
    public function setDistReference($reference);
}
<?php

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

function includeIfExists($file)
{
    return file_exists($file) ? include $file : false;
}

if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {
    echo 'You must set up the project dependencies using `composer install`'.PHP_EOL.
        'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL;
    exit(1);
}

return $loader;
<?php return array(
    'root' => array(
        'name' => 'friendsofphp/pickle',
        'pretty_version' => 'v0.7.11',
        'version' => '0.7.11.0',
        'reference' => '93d76e03b7beb80c22846d2533db12ddfcae57ce',
        'type' => 'application',
        'install_path' => __DIR__ . '/../../',
        'aliases' => array(),
        'dev' => false,
    ),
    'versions' => array(
        'composer/ca-bundle' => array(
            'pretty_version' => '1.3.1',
            'version' => '1.3.1.0',
            'reference' => '4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b',
            'type' => 'library',
            'install_path' => __DIR__ . '/./ca-bundle',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/composer' => array(
            'pretty_version' => '1.10.24',
            'version' => '1.10.24.0',
            'reference' => '6dea608377dc5409895d4cfd987947bc0abe154a',
            'type' => 'library',
            'install_path' => __DIR__ . '/./composer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/semver' => array(
            'pretty_version' => '1.7.2',
            'version' => '1.7.2.0',
            'reference' => '647490bbcaf7fc4891c58f47b825eb99d19c377a',
            'type' => 'library',
            'install_path' => __DIR__ . '/./semver',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/spdx-licenses' => array(
            'pretty_version' => '1.5.6',
            'version' => '1.5.6.0',
            'reference' => 'a30d487169d799745ca7280bc90fdfa693536901',
            'type' => 'library',
            'install_path' => __DIR__ . '/./spdx-licenses',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/xdebug-handler' => array(
            'pretty_version' => '1.4.6',
            'version' => '1.4.6.0',
            'reference' => 'f27e06cd9675801df441b3656569b328e04aa37c',
            'type' => 'library',
            'install_path' => __DIR__ . '/./xdebug-handler',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'friendsofphp/pickle' => array(
            'pretty_version' => 'v0.7.11',
            'version' => '0.7.11.0',
            'reference' => '93d76e03b7beb80c22846d2533db12ddfcae57ce',
            'type' => 'application',
            'install_path' => __DIR__ . '/../../',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'justinrainbow/json-schema' => array(
            'pretty_version' => '5.2.11',
            'version' => '5.2.11.0',
            'reference' => '2ab6744b7296ded80f8cc4f9509abbff393399aa',
            'type' => 'library',
            'install_path' => __DIR__ . '/../justinrainbow/json-schema',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'mlocati/composer-patcher' => array(
            'pretty_version' => '1.2.2',
            'version' => '1.2.2.0',
            'reference' => '38db5a938b33dfba6ccba5f0eb11a2fba8205b52',
            'type' => 'composer-plugin',
            'install_path' => __DIR__ . '/../mlocati/composer-patcher',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'padraic/humbug_get_contents' => array(
            'pretty_version' => '1.1.2',
            'version' => '1.1.2.0',
            'reference' => 'dcb086060c9dd6b2f51d8f7a895500307110b7a7',
            'type' => 'library',
            'install_path' => __DIR__ . '/../padraic/humbug_get_contents',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'padraic/phar-updater' => array(
            'pretty_version' => 'dev-master',
            'version' => 'dev-master',
            'reference' => 'e5a5da3dc3344031271157c7f10b0fa04afffa9b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../padraic/phar-updater',
            'aliases' => array(
                0 => '1.0.x-dev',
            ),
            'dev_requirement' => false,
        ),
        'psr/container' => array(
            'pretty_version' => '1.1.1',
            'version' => '1.1.1.0',
            'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/container',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/log' => array(
            'pretty_version' => '1.1.4',
            'version' => '1.1.4.0',
            'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/log',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/log-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0|2.0',
            ),
        ),
        'seld/jsonlint' => array(
            'pretty_version' => '1.8.3',
            'version' => '1.8.3.0',
            'reference' => '9ad6ce79c342fbd44df10ea95511a1b24dee5b57',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/jsonlint',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'seld/phar-utils' => array(
            'pretty_version' => '1.2.0',
            'version' => '1.2.0.0',
            'reference' => '9f3452c93ff423469c0d56450431562ca423dcee',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/phar-utils',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/console' => array(
            'pretty_version' => 'v5.4.1',
            'version' => '5.4.1.0',
            'reference' => '9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/console',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/deprecation-contracts' => array(
            'pretty_version' => 'v2.5.0',
            'version' => '2.5.0.0',
            'reference' => '6f981ee24cf69ee7ce9736146d1c57c2780598a8',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/filesystem' => array(
            'pretty_version' => 'v5.4.0',
            'version' => '5.4.0.0',
            'reference' => '731f917dc31edcffec2c6a777f3698c33bea8f01',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/filesystem',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/finder' => array(
            'pretty_version' => 'v5.4.0',
            'version' => '5.4.0.0',
            'reference' => 'd2f29dac98e96a98be467627bd49c2efb1bc2590',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/finder',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-ctype' => array(
            'pretty_version' => 'v1.23.0',
            'version' => '1.23.0.0',
            'reference' => '46cd95797e9df938fdd2b03693b5fca5e64b01ce',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-grapheme' => array(
            'pretty_version' => 'v1.23.1',
            'version' => '1.23.1.0',
            'reference' => '16880ba9c5ebe3642d1995ab866db29270b36535',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-normalizer' => array(
            'pretty_version' => 'v1.23.0',
            'version' => '1.23.0.0',
            'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-mbstring' => array(
            'pretty_version' => 'v1.23.1',
            'version' => '1.23.1.0',
            'reference' => '9174a3d80210dca8daa7f31fec659150bbeabfc6',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-php73' => array(
            'pretty_version' => 'v1.23.0',
            'version' => '1.23.0.0',
            'reference' => 'fba8933c384d6476ab14fb7b8526e5287ca7e010',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-php73',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-php80' => array(
            'pretty_version' => 'v1.23.1',
            'version' => '1.23.1.0',
            'reference' => '1100343ed1a92e3a38f9ae122fc0eb21602547be',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-php80',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/process' => array(
            'pretty_version' => 'v5.4.0',
            'version' => '5.4.0.0',
            'reference' => '5be20b3830f726e019162b26223110c8f47cf274',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/process',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/service-contracts' => array(
            'pretty_version' => 'v2.5.0',
            'version' => '2.5.0.0',
            'reference' => '1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/service-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/string' => array(
            'pretty_version' => 'v5.4.0',
            'version' => '5.4.0.0',
            'reference' => '9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/string',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
    ),
);
<?php

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

namespace Composer\Autoload;

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

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

    // PSR-0
    /**
     * @var array[]
     * @psalm-var array<string, array<string, string[]>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var array[]
     * @psalm-var array<string, string>
     */
    private $fallbackDirsPsr0 = array();

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

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

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

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

    /** @var ?string */
    private $apcuPrefix;

    /**
     * @var self[]
     */
    private static $registeredLoaders = array();

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

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

        return array();
    }

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

    /**
     * @return array[]
     * @psalm-return array<string, string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return array[]
     * @psalm-return array<string, string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

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

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

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

            return;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return true;
        }

        return null;
    }

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

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

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

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

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

        return $file;
    }

    /**
     * Returns the currently registered loaders indexed by their corresponding vendor directories.
     *
     * @return self[]
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

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

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

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

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

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

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

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

        return false;
    }
}

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

/*
 * This file is part of composer/ca-bundle.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\CaBundle;

use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;

/**
 * @author Chris Smith <chris@cs278.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CaBundle
{
    /** @var string|null */
    private static $caPath;
    /** @var array<string, bool> */
    private static $caFileValidity = array();
    /** @var bool|null */
    private static $useOpensslParse;

    /**
     * Returns the system CA bundle path, or a path to the bundled one
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     *     * Redistributions of source code must retain the above copyright notice,
     *       this list of conditions and the following disclaimer.
     *
     *     * Redistributions in binary form must reproduce the above copyright notice,
     *       this list of conditions and the following disclaimer in the documentation
     *       and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     * @param  LoggerInterface $logger optional logger for information about which CA files were loaded
     * @return string          path to a CA bundle file or directory
     */
    public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
    {
        if (self::$caPath !== null) {
            return self::$caPath;
        }
        $caBundlePaths = array();

        // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');

        // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');

        $caBundlePaths[] = ini_get('openssl.cafile');
        $caBundlePaths[] = ini_get('openssl.capath');

        $otherLocations = array(
            '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
            '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
            '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
            '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
            '/usr/ssl/certs/ca-bundle.crt', // Cygwin
            '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
            '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
            '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
            '/etc/ssl/cert.pem', // OpenBSD
            '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
            '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
            '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
        );

        foreach($otherLocations as $location) {
            $otherLocations[] = dirname($location);
        }

        $caBundlePaths = array_merge($caBundlePaths, $otherLocations);

        foreach ($caBundlePaths as $caBundle) {
            if ($caBundle && self::caFileUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }

            if ($caBundle && self::caDirUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }
        }

        return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
    }

    /**
     * Returns the path to the bundled CA file
     *
     * In case you don't want to trust the user or the system, you can use this directly
     *
     * @return string path to a CA bundle file
     */
    public static function getBundledCaBundlePath()
    {
        $caBundleFile = __DIR__.'/../res/cacert.pem';

        // cURL does not understand 'phar://' paths
        // see https://github.com/composer/ca-bundle/issues/10
        if (0 === strpos($caBundleFile, 'phar://')) {
            $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
            if (false === $tempCaBundleFile) {
                throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
            }

            file_put_contents(
                $tempCaBundleFile,
                file_get_contents($caBundleFile)
            );

            register_shutdown_function(function() use ($tempCaBundleFile) {
                @unlink($tempCaBundleFile);
            });

            $caBundleFile = $tempCaBundleFile;
        }

        return $caBundleFile;
    }

    /**
     * Validates a CA file using opensl_x509_parse only if it is safe to use
     *
     * @param string          $filename
     * @param LoggerInterface $logger   optional logger for information about which CA files were loaded
     *
     * @return bool
     */
    public static function validateCaFile($filename, LoggerInterface $logger = null)
    {
        static $warned = false;

        if (isset(self::$caFileValidity[$filename])) {
            return self::$caFileValidity[$filename];
        }

        $contents = file_get_contents($filename);

        // assume the CA is valid if php is vulnerable to
        // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
        if (!static::isOpensslParseSafe()) {
            if (!$warned && $logger) {
                $logger->warning(sprintf(
                    'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
                    PHP_VERSION
                ));
                $warned = true;
            }

            $isValid = !empty($contents);
        } elseif (is_string($contents) && strlen($contents) > 0) {
            $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
            if (null === $contents) {
                // regex extraction failed
                $isValid = false;
            } else {
                $isValid = (bool) openssl_x509_parse($contents);
            }
        } else {
            $isValid = false;
        }

        if ($logger) {
            $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
        }

        return self::$caFileValidity[$filename] = $isValid;
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     *
     * @return bool
     */
    public static function isOpensslParseSafe()
    {
        if (null !== self::$useOpensslParse) {
            return self::$useOpensslParse;
        }

        if (PHP_VERSION_ID >= 50600) {
            return self::$useOpensslParse = true;
        }

        // Vulnerable:
        // PHP 5.3.0 - PHP 5.3.27
        // PHP 5.4.0 - PHP 5.4.22
        // PHP 5.5.0 - PHP 5.5.6
        if (
               (PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
            || (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
            || PHP_VERSION_ID >= 50507
        ) {
            // This version of PHP has the fix for CVE-2013-6420 applied.
            return self::$useOpensslParse = true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            // Windows is probably insecure in this case.
            return self::$useOpensslParse = false;
        }

        $compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
            $regex = '{^'.preg_quote($prefix).'([0-9]+)$}';

            if (preg_match($regex, PHP_VERSION, $m)) {
                return ((int) $m[1]) >= $fixedVersion;
            }

            return false;
        };

        // Hard coded list of PHP distributions with the fix backported.
        if (
            $compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
            || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
            || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
        ) {
            return self::$useOpensslParse = true;
        }

        // Symfony Process component is missing so we assume it is unsafe at this point
        if (!class_exists('Symfony\Component\Process\PhpProcess')) {
            return self::$useOpensslParse = false;
        }

        // This is where things get crazy, because distros backport security
        // fixes the chances are on NIX systems the fix has been applied but
        // it's not possible to verify that from the PHP version.
        //
        // To verify exec a new PHP process and run the issue testcase with
        // known safe input that replicates the bug.

        // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
        // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
        $cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
        $script = <<<'EOT'

error_reporting(-1);
$info = openssl_x509_parse(base64_decode('%s'));
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);

EOT;
        $script = '<'."?php\n".sprintf($script, $cert);

        try {
            $process = new PhpProcess($script);
            $process->mustRun();
        } catch (\Exception $e) {
            // In the case of any exceptions just accept it is not possible to
            // determine the safety of openssl_x509_parse and bail out.
            return self::$useOpensslParse = false;
        }

        $output = preg_split('{\r?\n}', trim($process->getOutput()));
        $errorOutput = trim($process->getErrorOutput());

        if (
            is_array($output)
            && count($output) === 3
            && $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
            && $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
            && $output[2] === 'int(-1)'
            && preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
        ) {
            // This PHP has the fix backported probably by a distro security team.
            return self::$useOpensslParse = true;
        }

        return self::$useOpensslParse = false;
    }

    /**
     * Resets the static caches
     * @return void
     */
    public static function reset()
    {
        self::$caFileValidity = array();
        self::$caPath = null;
        self::$useOpensslParse = null;
    }

    /**
     * @param  string $name
     * @return string|false
     */
    private static function getEnvVariable($name)
    {
        if (isset($_SERVER[$name])) {
            return (string) $_SERVER[$name];
        }

        if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
            return (string) $value;
        }

        return false;
    }

    /**
     * @param  string|false $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caFileUsable($certFile, LoggerInterface $logger = null)
    {
        return $certFile
            && static::isFile($certFile, $logger)
            && static::isReadable($certFile, $logger)
            && static::validateCaFile($certFile, $logger);
    }

    /**
     * @param  string|false $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caDirUsable($certDir, LoggerInterface $logger = null)
    {
        return $certDir
            && static::isDir($certDir, $logger)
            && static::isReadable($certDir, $logger)
            && static::glob($certDir . '/*', $logger);
    }

    /**
     * @param  string $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isFile($certFile, LoggerInterface $logger = null)
    {
        $isFile = @is_file($certFile);
        if (!$isFile && $logger) {
            $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
        }

        return $isFile;
    }

    /**
     * @param  string $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isDir($certDir, LoggerInterface $logger = null)
    {
        $isDir = @is_dir($certDir);
        if (!$isDir && $logger) {
            $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
        }

        return $isDir;
    }

    /**
     * @param  string $certFileOrDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isReadable($certFileOrDir, LoggerInterface $logger = null)
    {
        $isReadable = @is_readable($certFileOrDir);
        if (!$isReadable && $logger) {
            $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
        }

        return $isReadable;
    }

    /**
     * @param  string $pattern
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function glob($pattern, LoggerInterface $logger = null)
    {
        $certs = glob($pattern);
        if ($certs === false) {
            if ($logger) {
                $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
            }
            return false;
        }

        if (count($certs) === 0) {
            if ($logger) {
                $logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
            }
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

trigger_error('The ' . __NAMESPACE__ . '\AbstractConstraint abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED);

/**
 * Base constraint class.
 */
abstract class AbstractConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            // see note at bottom of this class declaration
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    // implementations must implement a method of this format:
    // not declared abstract here because type hinting violates parameter coherence (TODO right word?)
    // public function matchSpecific(<SpecificConstraintType> $provider);
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

interface ConstraintInterface
{
    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider);

    /**
     * @return string
     */
    public function getPrettyString();

    /**
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a constraint.
 */
class Constraint implements ConstraintInterface
{
    /* operator integer values */
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;

    /**
     * Operator to integer translation table.
     *
     * @var array
     */
    private static $transOpStr = array(
        '=' => self::OP_EQ,
        '==' => self::OP_EQ,
        '<' => self::OP_LT,
        '<=' => self::OP_LE,
        '>' => self::OP_GT,
        '>=' => self::OP_GE,
        '<>' => self::OP_NE,
        '!=' => self::OP_NE,
    );

    /**
     * Integer to operator translation table.
     *
     * @var array
     */
    private static $transOpInt = array(
        self::OP_EQ => '==',
        self::OP_LT => '<',
        self::OP_LE => '<=',
        self::OP_GT => '>',
        self::OP_GE => '>=',
        self::OP_NE => '!=',
    );

    /** @var int */
    protected $operator;

    /** @var string */
    protected $version;

    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * Get all supported comparison operators.
     *
     * @return array
     */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }

    /**
     * Sets operator and version to compare with.
     *
     * @param string $operator
     * @param string $version
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }

    /**
     * @param string $a
     * @param string $b
     * @param string $operator
     * @param bool   $compareBranches
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @return bool
     */
    public function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $aIsBranch = 'dev-' === substr($a, 0, 4);
        $bIsBranch = 'dev-' === substr($b, 0, 4);

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return version_compare($a, $b, $operator);
    }

    /**
     * @param Constraint $provider
     * @param bool       $compareBranches
     *
     * @return bool
     */
    public function matchSpecific(Constraint $provider, $compareBranches = false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);

        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;

        // '!=' operator is match when other operator is not '==' operator or version is not match
        // these kinds of comparisons always have a solution
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            return (!$isEqualOp && !$isProviderEqualOp)
                || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }

        // an example for the condition is <= 2.0 & < 1.0
        // these kinds of comparisons always have a solution
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return true;
        }

        if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) {
            // special case, e.g. require >= 1.0 and provide < 1.0
            // 1.0 >= 1.0 but 1.0 is outside of the provided interval
            return !($provider->version === $this->version
                && self::$transOpInt[$provider->operator] === $providerNoEqualOp
                && self::$transOpInt[$this->operator] !== $noEqualOp);
        }

        return false;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines the absence of a constraint.
 */
class EmptyConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return true;
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return '[]';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a conjunctive or disjunctive set of constraints.
 */
class MultiConstraint implements ConstraintInterface
{
    /** @var ConstraintInterface[] */
    protected $constraints;

    /** @var string|null */
    protected $prettyString;

    /** @var bool */
    protected $conjunctive;

    /**
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool                  $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     */
    public function __construct(array $constraints, $conjunctive = true)
    {
        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }

    /**
     * @return ConstraintInterface[]
     */
    public function getConstraints()
    {
        return $this->constraints;
    }

    /**
     * @return bool
     */
    public function isConjunctive()
    {
        return $this->conjunctive;
    }

    /**
     * @return bool
     */
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if (false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($constraint->matches($provider)) {
                    return true;
                }
            }

            return false;
        }

        foreach ($this->constraints as $constraint) {
            if (!$constraint->matches($provider)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param string|null $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }

        return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;

    /** @var VersionParser */
    private static $versionParser;

    /**
     * Determine if given version satisfies given constraints.
     *
     * @param string $version
     * @param string $constraints
     *
     * @return bool
     */
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $parsedConstraints = $versionParser->parseConstraints($constraints);

        return $parsedConstraints->matches($provider);
    }

    /**
     * Return all versions that satisfy given constraints.
     *
     * @param array  $versions
     * @param string $constraints
     *
     * @return array
     */
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });

        return array_values($versions);
    }

    /**
     * Sort given array of versions.
     *
     * @param array $versions
     *
     * @return array
     */
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }

    /**
     * Sort given array of versions in reverse.
     *
     * @param array $versions
     *
     * @return array
     */
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }

    /**
     * @param array $versions
     * @param int   $direction
     *
     * @return array
     */
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $normalized = array();

        // Normalize outside of usort() scope for minor performance increase.
        // Creates an array of arrays: [[normalized, key], ...]
        foreach ($versions as $key => $version) {
            $normalized[] = array($versionParser->normalize($version), $key);
        }

        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }

            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }

            return $direction;
        });

        // Recreate input array, using the original indexes which are now in sorted order.
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }

        return $sorted;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Comparator
{
    /**
     * Evaluates the expression: $version1 > $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }

    /**
     * Evaluates the expression: $version1 >= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }

    /**
     * Evaluates the expression: $version1 < $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }

    /**
     * Evaluates the expression: $version1 <= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }

    /**
     * Evaluates the expression: $version1 == $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }

    /**
     * Evaluates the expression: $version1 != $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }

    /**
     * Evaluates the expression: $version1 $operator $version2.
     *
     * @param string $version1
     * @param string $operator
     * @param string $version2
     *
     * @return bool
     */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);

        return $constraint->matches(new Constraint('==', $version1));
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Constraint\Constraint;

/**
 * Version parser.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionParser
{
    /**
     * Regex to match pre-release data (sort of).
     *
     * Due to backwards compatibility:
     *   - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
     *   - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
     *   - Numerical-only pre-release identifiers are not supported, see tests.
     *
     *                        |--------------|
     * [major].[minor].[patch] -[pre-release] +[build-metadata]
     *
     * @var string
     */
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

    /** @var string */
    private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';

    /**
     * Returns the stability of a version.
     *
     * @param string $version
     *
     * @return string
     */
    public static function parseStability($version)
    {
        $version = preg_replace('{#.+$}i', '', $version);

        if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
            return 'dev';
        }

        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);

        if (!empty($match[3])) {
            return 'dev';
        }

        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }

        return 'stable';
    }

    /**
     * @param string $stability
     *
     * @return string
     */
    public static function normalizeStability($stability)
    {
        $stability = strtolower($stability);

        return $stability === 'rc' ? 'RC' : $stability;
    }

    /**
     * Normalizes a version string to be able to perform comparisons on it.
     *
     * @param string $version
     * @param string $fullVersion optional complete version string to give more context
     *
     * @throws \UnexpectedValueException
     *
     * @return string
     */
    public function normalize($version, $fullVersion = null)
    {
        $version = trim($version);
        $origVersion = $version;
        if (null === $fullVersion) {
            $fullVersion = $version;
        }

        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }

        // strip off stability flag
        if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
            $version = substr($version, 0, strlen($version) - strlen($match[0]));
        }

        // match master-like branches
        if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
            return '9999999-dev';
        }

        // if requirement is branch-like, use full name
        if (stripos($version, 'dev-') === 0) {
            return 'dev-' . substr($version, 4);
        }

        // strip off build metadata
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }

        // match classical versioning
        if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1]
                . (!empty($matches[2]) ? $matches[2] : '.0')
                . (!empty($matches[3]) ? $matches[3] : '.0')
                . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        // match date(time) based versioning
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }

        // add version modifiers if a version was matched
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
            }

            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }

            return $version;
        }

        // match dev branches
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                $normalized = $this->normalizeBranch($match[1]);
                // a branch ending with -dev is only valid if it is numeric
                // if it gets prefixed with dev- it means the branch name should
                // have had a dev- prefix already when passed to normalize
                if (strpos($normalized, 'dev-') === false) {
                    return $normalized;
                }
            } catch (\Exception $e) {
            }
        }

        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }

        throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
    }

    /**
     * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
     *
     * @param string $branch Branch name (e.g. 2.1.x-dev)
     *
     * @return string|false Numeric prefix if present (e.g. 2.1.) or false
     */
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) {
            return $matches['version'] . '.';
        }

        return false;
    }

    /**
     * Normalizes a branch name to be able to perform comparisons on it.
     *
     * @param string $name
     *
     * @return string
     */
    public function normalizeBranch($name)
    {
        $name = trim($name);

        if (in_array($name, array('master', 'trunk', 'default'))) {
            return $this->normalize($name);
        }

        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }

            return str_replace('x', '9999999', $version) . '-dev';
        }

        return 'dev-' . $name;
    }

    /**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    public function parseConstraints($constraints)
    {
        $prettyConstraint = $constraints;

        $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
        $orGroups = array();

        foreach ($orConstraints as $constraints) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
            if (count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $constraint) {
                    foreach ($this->parseConstraint($constraint) as $parsedConstraint) {
                        $constraintObjects[] = $parsedConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }

            if (1 === count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }

            $orGroups[] = $constraint;
        }

        if (1 === count($orGroups)) {
            $constraint = $orGroups[0];
        } elseif (2 === count($orGroups)
            // parse the two OR groups and if they are contiguous we collapse
            // them into one constraint
            && $orGroups[0] instanceof MultiConstraint
            && $orGroups[1] instanceof MultiConstraint
            && 2 === count($orGroups[0]->getConstraints())
            && 2 === count($orGroups[1]->getConstraints())
            && ($a = (string) $orGroups[0])
            && strpos($a, '[>=') === 0 && (false !== ($posA = strpos($a, '<', 4)))
            && ($b = (string) $orGroups[1])
            && strpos($b, '[>=') === 0 && (false !== ($posB = strpos($b, '<', 4)))
            && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
        ) {
            $constraint = new MultiConstraint(array(
                new Constraint('>=', substr($a, 4, $posA - 5)),
                new Constraint('<', substr($b, $posB + 2, -1)),
            ));
        } else {
            $constraint = new MultiConstraint($orGroups, false);
        }

        $constraint->setPrettyString($prettyConstraint);

        return $constraint;
    }

    /**
     * @param string $constraint
     *
     * @throws \UnexpectedValueException
     *
     * @return array
     */
    private function parseConstraint($constraint)
    {
        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
            $constraint = $match[1];
        }

        // strip @stability flags, and keep it for later use
        if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
            $constraint = '' !== $match[1] ? $match[1] : '*';
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }

        // get rid of #refs as those are used by composer only
        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
            $constraint = $match[1];
        }

        if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
            return array(new EmptyConstraint());
        }

        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';

        // Tilde Range
        //
        // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
        // version, to ensure that unstable instances of the current version are allowed. However, if a stability
        // suffix is added to the constraint, then a >= match on the current version is used instead.
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (strpos($constraint, '~>') === 0) {
                throw new \UnexpectedValueException(
                    'Could not parse version constraint ' . $constraint . ': ' .
                    'Invalid operator "~>", you probably meant to use the "~" operator'
                );
            }

            // Work out which position in the version we are operating at
            if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above
            if (!empty($matches[8])) {
                $position++;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Caret Range
        //
        // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
        // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
        // versions 0.X >=0.1.0, and no updates for versions 0.0.X
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            // Work out which position in the version we are operating at
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // X Range
        //
        // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
        // A partial version range is treated as an X-Range, so the special character is in fact optional.
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';

            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }

            return array(
                new Constraint('>=', $lowVersion),
                new Constraint('<', $highVersion),
            );
        }

        // Hyphen Range
        //
        // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
        // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
        // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
        // nothing that would be greater than the provided tuple parts.
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            // Calculate the stability suffix
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
                $lowStabilitySuffix = '-dev';
            }

            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);

            $empty = function ($x) {
                return ($x === 0 || $x === '0') ? false : empty($x);
            };

            if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);

                // validate to version
                $this->normalize($matches['to']);

                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Basic Comparators
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                try {
                    $version = $this->normalize($matches[2]);
                } catch (\UnexpectedValueException $e) {
                    // recover from an invalid constraint like foobar-dev which should be dev-foobar
                    // except if the constraint uses a known operator, in which case it must be a parse error
                    if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
                        $version = $this->normalize('dev-'.substr($matches[2], 0, -4));
                    } else {
                        throw $e;
                    }
                }

                $op = $matches[1] ?: '=';

                if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $op || '>=' === $op) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (strpos($matches[2], 'dev-') !== 0) {
                            $version .= '-dev';
                        }
                    }
                }

                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }

        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }

        throw new \UnexpectedValueException($message);
    }

    /**
     * Increment, decrement, or simply pad a version number.
     *
     * Support function for {@link parseConstraint()}
     *
     * @param array  $matches   Array with version parts in array indexes 1,2,3,4
     * @param int    $position  1,2,3,4 - which segment of the version to increment/decrement
     * @param int    $increment
     * @param string $pad       The string to pad version parts after $position
     *
     * @return string|null The new version
     */
    private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                // If $matches[$i] was 0, carry the decrement
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;

                    // Return null on a carry overflow
                    if ($i === 1) {
                        return null;
                    }
                }
            }
        }

        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }

    /**
     * Expand shorthand stability string to long version.
     *
     * @param string $stability
     *
     * @return string
     */
    private function expandStability($stability)
    {
        $stability = strtolower($stability);

        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

// autoload_psr4.php @generated by Composer

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

return array(
    'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
    'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
    'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
    'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
    'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'),
    'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
    'Pickle\\' => array($baseDir . '/src'),
    'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
    'Humbug\\SelfUpdate\\' => array($vendorDir . '/padraic/phar-updater/src'),
    'Humbug\\' => array($vendorDir . '/padraic/humbug_get_contents/src'),
    'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'),
    'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'),
    'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
    'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
    'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'),
    'ComposerPatcher\\' => array($vendorDir . '/mlocati/composer-patcher/src'),
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit8e4de4eb796111b7004a7747b952dd58
{
    public static $files = array (
        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
        'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
        '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
        'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
        'd9d39f82a605ebe5918f683dd402334c' => __DIR__ . '/..' . '/padraic/humbug_get_contents/src/function.php',
        '3a50d90d85c7fe889a94ae1114b921ce' => __DIR__ . '/..' . '/padraic/humbug_get_contents/src/functions.php',
    );

    public static $prefixLengthsPsr4 = array (
        'S' => 
        array (
            'Symfony\\Polyfill\\Php80\\' => 23,
            'Symfony\\Polyfill\\Php73\\' => 23,
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
            'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
            'Symfony\\Polyfill\\Ctype\\' => 23,
            'Symfony\\Contracts\\Service\\' => 26,
            'Symfony\\Component\\String\\' => 25,
            'Symfony\\Component\\Process\\' => 26,
            'Symfony\\Component\\Finder\\' => 25,
            'Symfony\\Component\\Filesystem\\' => 29,
            'Symfony\\Component\\Console\\' => 26,
            'Seld\\PharUtils\\' => 15,
            'Seld\\JsonLint\\' => 14,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'Psr\\Container\\' => 14,
            'Pickle\\' => 7,
        ),
        'J' => 
        array (
            'JsonSchema\\' => 11,
        ),
        'H' => 
        array (
            'Humbug\\SelfUpdate\\' => 18,
            'Humbug\\' => 7,
        ),
        'C' => 
        array (
            'Composer\\XdebugHandler\\' => 23,
            'Composer\\Spdx\\' => 14,
            'Composer\\Semver\\' => 16,
            'Composer\\CaBundle\\' => 18,
            'Composer\\' => 9,
            'ComposerPatcher\\' => 16,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'Symfony\\Polyfill\\Php80\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
        ),
        'Symfony\\Polyfill\\Php73\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Polyfill\\Intl\\Normalizer\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
        ),
        'Symfony\\Polyfill\\Intl\\Grapheme\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
        ),
        'Symfony\\Polyfill\\Ctype\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Contracts\\Service\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/service-contracts',
        ),
        'Symfony\\Component\\String\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/string',
        ),
        'Symfony\\Component\\Process\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/process',
        ),
        'Symfony\\Component\\Finder\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/finder',
        ),
        'Symfony\\Component\\Filesystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/filesystem',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Seld\\PharUtils\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/phar-utils/src',
        ),
        'Seld\\JsonLint\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'Psr\\Container\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/container/src',
        ),
        'Pickle\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'JsonSchema\\' => 
        array (
            0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema',
        ),
        'Humbug\\SelfUpdate\\' => 
        array (
            0 => __DIR__ . '/..' . '/padraic/phar-updater/src',
        ),
        'Humbug\\' => 
        array (
            0 => __DIR__ . '/..' . '/padraic/humbug_get_contents/src',
        ),
        'Composer\\XdebugHandler\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/xdebug-handler/src',
        ),
        'Composer\\Spdx\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/spdx-licenses/src',
        ),
        'Composer\\Semver\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
        'Composer\\CaBundle\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
        ),
        'Composer\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/composer/src/Composer',
        ),
        'ComposerPatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/mlocati/composer-patcher/src',
        ),
    );

    public static $classMap = array (
        'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
        'ComposerPatcher\\Event' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Event.php',
        'ComposerPatcher\\Event\\Patch' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Event/Patch.php',
        'ComposerPatcher\\Exception' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception.php',
        'ComposerPatcher\\Exception\\CommandNotFound' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/CommandNotFound.php',
        'ComposerPatcher\\Exception\\InvalidPackageConfigurationValue' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/InvalidPackageConfigurationValue.php',
        'ComposerPatcher\\Exception\\PatchAlreadyApplied' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PatchAlreadyApplied.php',
        'ComposerPatcher\\Exception\\PatchNotApplied' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PatchNotApplied.php',
        'ComposerPatcher\\Exception\\PathNotCreated' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PathNotCreated.php',
        'ComposerPatcher\\Exception\\PathNotFound' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PathNotFound.php',
        'ComposerPatcher\\Exception\\PathNotReadable' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PathNotReadable.php',
        'ComposerPatcher\\Exception\\PathNotWritable' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Exception/PathNotWritable.php',
        'ComposerPatcher\\Patch' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Patch.php',
        'ComposerPatcher\\PatchCollection' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/PatchCollection.php',
        'ComposerPatcher\\PatchCollector' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/PatchCollector.php',
        'ComposerPatcher\\PatchExecutor' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/PatchExecutor.php',
        'ComposerPatcher\\PatchExecutor\\GitPatcher' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/PatchExecutor/GitPatcher.php',
        'ComposerPatcher\\PatchExecutor\\PatchPatcher' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/PatchExecutor/PatchPatcher.php',
        'ComposerPatcher\\Patcher' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Patcher.php',
        'ComposerPatcher\\Plugin' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Plugin.php',
        'ComposerPatcher\\Util\\PathResolver' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Util/PathResolver.php',
        'ComposerPatcher\\Util\\VolatileDirectory' => __DIR__ . '/..' . '/mlocati/composer-patcher/src/Util/VolatileDirectory.php',
        'Composer\\Autoload\\AutoloadGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
        'Composer\\Autoload\\ClassLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
        'Composer\\Autoload\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
        'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php',
        'Composer\\Cache' => __DIR__ . '/..' . '/composer/composer/src/Composer/Cache.php',
        'Composer\\Command\\AboutCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AboutCommand.php',
        'Composer\\Command\\ArchiveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
        'Composer\\Command\\BaseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseCommand.php',
        'Composer\\Command\\BaseDependencyCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
        'Composer\\Command\\CheckPlatformReqsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
        'Composer\\Command\\ClearCacheCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
        'Composer\\Command\\ConfigCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ConfigCommand.php',
        'Composer\\Command\\CreateProjectCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
        'Composer\\Command\\DependsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DependsCommand.php',
        'Composer\\Command\\DiagnoseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
        'Composer\\Command\\DumpAutoloadCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
        'Composer\\Command\\ExecCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ExecCommand.php',
        'Composer\\Command\\FundCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/FundCommand.php',
        'Composer\\Command\\GlobalCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/GlobalCommand.php',
        'Composer\\Command\\HomeCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/HomeCommand.php',
        'Composer\\Command\\InitCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InitCommand.php',
        'Composer\\Command\\InstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InstallCommand.php',
        'Composer\\Command\\LicensesCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/LicensesCommand.php',
        'Composer\\Command\\OutdatedCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
        'Composer\\Command\\ProhibitsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
        'Composer\\Command\\RemoveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RemoveCommand.php',
        'Composer\\Command\\RequireCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RequireCommand.php',
        'Composer\\Command\\RunScriptCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
        'Composer\\Command\\ScriptAliasCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
        'Composer\\Command\\SearchCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SearchCommand.php',
        'Composer\\Command\\SelfUpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
        'Composer\\Command\\ShowCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ShowCommand.php',
        'Composer\\Command\\StatusCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/StatusCommand.php',
        'Composer\\Command\\SuggestsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
        'Composer\\Command\\UpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/UpdateCommand.php',
        'Composer\\Command\\ValidateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ValidateCommand.php',
        'Composer\\Compiler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Compiler.php',
        'Composer\\Composer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Composer.php',
        'Composer\\Config' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config.php',
        'Composer\\Config\\ConfigSourceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
        'Composer\\Config\\JsonConfigSource' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
        'Composer\\Console\\Application' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Application.php',
        'Composer\\Console\\HtmlOutputFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
        'Composer\\DependencyResolver\\Decisions' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
        'Composer\\DependencyResolver\\DefaultPolicy' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
        'Composer\\DependencyResolver\\GenericRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
        'Composer\\DependencyResolver\\Operation\\InstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\OperationInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
        'Composer\\DependencyResolver\\Operation\\SolverOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
        'Composer\\DependencyResolver\\Operation\\UninstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\UpdateOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
        'Composer\\DependencyResolver\\PolicyInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
        'Composer\\DependencyResolver\\Pool' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
        'Composer\\DependencyResolver\\Problem' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
        'Composer\\DependencyResolver\\Request' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Request.php',
        'Composer\\DependencyResolver\\Rule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
        'Composer\\DependencyResolver\\Rule2Literals' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
        'Composer\\DependencyResolver\\RuleSet' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
        'Composer\\DependencyResolver\\RuleSetGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
        'Composer\\DependencyResolver\\RuleSetIterator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
        'Composer\\DependencyResolver\\RuleWatchChain' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
        'Composer\\DependencyResolver\\RuleWatchGraph' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
        'Composer\\DependencyResolver\\RuleWatchNode' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
        'Composer\\DependencyResolver\\Solver' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
        'Composer\\DependencyResolver\\SolverBugException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
        'Composer\\DependencyResolver\\SolverProblemsException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
        'Composer\\DependencyResolver\\Transaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
        'Composer\\Downloader\\ArchiveDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
        'Composer\\Downloader\\ChangeReportInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
        'Composer\\Downloader\\DownloadManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
        'Composer\\Downloader\\DownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
        'Composer\\Downloader\\DvcsDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
        'Composer\\Downloader\\FileDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
        'Composer\\Downloader\\FilesystemException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
        'Composer\\Downloader\\FossilDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
        'Composer\\Downloader\\GitDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
        'Composer\\Downloader\\GzipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
        'Composer\\Downloader\\HgDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
        'Composer\\Downloader\\PathDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
        'Composer\\Downloader\\PearPackageExtractor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PearPackageExtractor.php',
        'Composer\\Downloader\\PerforceDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
        'Composer\\Downloader\\PharDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
        'Composer\\Downloader\\RarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
        'Composer\\Downloader\\SvnDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
        'Composer\\Downloader\\TarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
        'Composer\\Downloader\\TransportException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TransportException.php',
        'Composer\\Downloader\\VcsCapableDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
        'Composer\\Downloader\\VcsDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
        'Composer\\Downloader\\XzDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
        'Composer\\Downloader\\ZipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
        'Composer\\EventDispatcher\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/Event.php',
        'Composer\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
        'Composer\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
        'Composer\\EventDispatcher\\ScriptExecutionException' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
        'Composer\\Exception\\NoSslException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/NoSslException.php',
        'Composer\\Factory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Factory.php',
        'Composer\\IO\\BaseIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BaseIO.php',
        'Composer\\IO\\BufferIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BufferIO.php',
        'Composer\\IO\\ConsoleIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/ConsoleIO.php',
        'Composer\\IO\\IOInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/IOInterface.php',
        'Composer\\IO\\NullIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/NullIO.php',
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'Composer\\Installer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer.php',
        'Composer\\Installer\\BinaryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
        'Composer\\Installer\\BinaryPresenceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
        'Composer\\Installer\\InstallationManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallationManager.php',
        'Composer\\Installer\\InstallerEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
        'Composer\\Installer\\InstallerEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
        'Composer\\Installer\\InstallerInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
        'Composer\\Installer\\LibraryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
        'Composer\\Installer\\MetapackageInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
        'Composer\\Installer\\NoopInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
        'Composer\\Installer\\PackageEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvent.php',
        'Composer\\Installer\\PackageEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvents.php',
        'Composer\\Installer\\PearBinaryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PearBinaryInstaller.php',
        'Composer\\Installer\\PearInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PearInstaller.php',
        'Composer\\Installer\\PluginInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
        'Composer\\Installer\\ProjectInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
        'Composer\\Installer\\SuggestedPackagesReporter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
        'Composer\\Json\\JsonFile' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFile.php',
        'Composer\\Json\\JsonFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFormatter.php',
        'Composer\\Json\\JsonManipulator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonManipulator.php',
        'Composer\\Json\\JsonValidationException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonValidationException.php',
        'Composer\\Package\\AliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/AliasPackage.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFinder' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
        'Composer\\Package\\Archiver\\ArchiveManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
        'Composer\\Package\\Archiver\\ArchiverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
        'Composer\\Package\\Archiver\\BaseExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
        'Composer\\Package\\Archiver\\ComposerExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
        'Composer\\Package\\Archiver\\GitExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
        'Composer\\Package\\Archiver\\HgExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php',
        'Composer\\Package\\Archiver\\PharArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
        'Composer\\Package\\Archiver\\ZipArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
        'Composer\\Package\\BasePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/BasePackage.php',
        'Composer\\Package\\Comparer\\Comparer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
        'Composer\\Package\\CompletePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackage.php',
        'Composer\\Package\\CompletePackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
        'Composer\\Package\\Dumper\\ArrayDumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
        'Composer\\Package\\Link' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Link.php',
        'Composer\\Package\\LinkConstraint\\EmptyConstraint' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php',
        'Composer\\Package\\LinkConstraint\\LinkConstraintInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php',
        'Composer\\Package\\LinkConstraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php',
        'Composer\\Package\\LinkConstraint\\SpecificConstraint' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php',
        'Composer\\Package\\LinkConstraint\\VersionConstraint' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php',
        'Composer\\Package\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
        'Composer\\Package\\Loader\\InvalidPackageException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
        'Composer\\Package\\Loader\\JsonLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
        'Composer\\Package\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
        'Composer\\Package\\Loader\\RootPackageLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
        'Composer\\Package\\Loader\\ValidatingArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
        'Composer\\Package\\Locker' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Locker.php',
        'Composer\\Package\\Package' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Package.php',
        'Composer\\Package\\PackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/PackageInterface.php',
        'Composer\\Package\\RootAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
        'Composer\\Package\\RootPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackage.php',
        'Composer\\Package\\RootPackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
        'Composer\\Package\\Version\\VersionGuesser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
        'Composer\\Package\\Version\\VersionParser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
        'Composer\\Package\\Version\\VersionSelector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
        'Composer\\Plugin\\Capability\\Capability' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
        'Composer\\Plugin\\Capability\\CommandProvider' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
        'Composer\\Plugin\\Capable' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capable.php',
        'Composer\\Plugin\\CommandEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
        'Composer\\Plugin\\PluginEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
        'Composer\\Plugin\\PluginInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
        'Composer\\Plugin\\PluginManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginManager.php',
        'Composer\\Plugin\\PreCommandRunEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
        'Composer\\Plugin\\PreFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
        'Composer\\Question\\StrictConfirmationQuestion' => __DIR__ . '/..' . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
        'Composer\\Repository\\ArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
        'Composer\\Repository\\ArtifactRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
        'Composer\\Repository\\BaseRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/BaseRepository.php',
        'Composer\\Repository\\ComposerRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
        'Composer\\Repository\\CompositeRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
        'Composer\\Repository\\ConfigurableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
        'Composer\\Repository\\FilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
        'Composer\\Repository\\InstalledArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
        'Composer\\Repository\\InstalledFilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
        'Composer\\Repository\\InstalledRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
        'Composer\\Repository\\InvalidRepositoryException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
        'Composer\\Repository\\PackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PackageRepository.php',
        'Composer\\Repository\\PathRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PathRepository.php',
        'Composer\\Repository\\PearRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PearRepository.php',
        'Composer\\Repository\\Pear\\BaseChannelReader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/BaseChannelReader.php',
        'Composer\\Repository\\Pear\\ChannelInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/ChannelInfo.php',
        'Composer\\Repository\\Pear\\ChannelReader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/ChannelReader.php',
        'Composer\\Repository\\Pear\\ChannelRest10Reader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/ChannelRest10Reader.php',
        'Composer\\Repository\\Pear\\ChannelRest11Reader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/ChannelRest11Reader.php',
        'Composer\\Repository\\Pear\\DependencyConstraint' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/DependencyConstraint.php',
        'Composer\\Repository\\Pear\\DependencyInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/DependencyInfo.php',
        'Composer\\Repository\\Pear\\PackageDependencyParser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/PackageDependencyParser.php',
        'Composer\\Repository\\Pear\\PackageInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/PackageInfo.php',
        'Composer\\Repository\\Pear\\ReleaseInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Pear/ReleaseInfo.php',
        'Composer\\Repository\\PlatformRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
        'Composer\\Repository\\RepositoryFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
        'Composer\\Repository\\RepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
        'Composer\\Repository\\RepositoryManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
        'Composer\\Repository\\RepositorySecurityException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
        'Composer\\Repository\\VcsRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VcsRepository.php',
        'Composer\\Repository\\Vcs\\BitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php',
        'Composer\\Repository\\Vcs\\FossilDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
        'Composer\\Repository\\Vcs\\GitBitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
        'Composer\\Repository\\Vcs\\GitDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
        'Composer\\Repository\\Vcs\\GitHubDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
        'Composer\\Repository\\Vcs\\GitLabDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
        'Composer\\Repository\\Vcs\\HgBitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.php',
        'Composer\\Repository\\Vcs\\HgDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
        'Composer\\Repository\\Vcs\\PerforceDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
        'Composer\\Repository\\Vcs\\SvnDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
        'Composer\\Repository\\VersionCacheInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
        'Composer\\Repository\\WritableArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
        'Composer\\Repository\\WritableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
        'Composer\\Script\\CommandEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/CommandEvent.php',
        'Composer\\Script\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/Event.php',
        'Composer\\Script\\PackageEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/PackageEvent.php',
        'Composer\\Script\\ScriptEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/ScriptEvents.php',
        'Composer\\SelfUpdate\\Keys' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
        'Composer\\SelfUpdate\\Versions' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
        'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'Composer\\Semver\\Constraint\\AbstractConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/AbstractConstraint.php',
        'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'Composer\\Semver\\Constraint\\EmptyConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/EmptyConstraint.php',
        'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'Composer\\Spdx\\SpdxLicenses' => __DIR__ . '/..' . '/composer/spdx-licenses/src/SpdxLicenses.php',
        'Composer\\Util\\AuthHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/AuthHelper.php',
        'Composer\\Util\\Bitbucket' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Bitbucket.php',
        'Composer\\Util\\ComposerMirror' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ComposerMirror.php',
        'Composer\\Util\\ConfigValidator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ConfigValidator.php',
        'Composer\\Util\\ErrorHandler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ErrorHandler.php',
        'Composer\\Util\\Filesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Filesystem.php',
        'Composer\\Util\\Git' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Git.php',
        'Composer\\Util\\GitHub' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitHub.php',
        'Composer\\Util\\GitLab' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitLab.php',
        'Composer\\Util\\Hg' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Hg.php',
        'Composer\\Util\\IniHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/IniHelper.php',
        'Composer\\Util\\NoProxyPattern' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
        'Composer\\Util\\PackageSorter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageSorter.php',
        'Composer\\Util\\Perforce' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Perforce.php',
        'Composer\\Util\\Platform' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Platform.php',
        'Composer\\Util\\ProcessExecutor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
        'Composer\\Util\\RemoteFilesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
        'Composer\\Util\\Silencer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Silencer.php',
        'Composer\\Util\\SpdxLicense' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/SpdxLicense.php',
        'Composer\\Util\\StreamContextFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
        'Composer\\Util\\Svn' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Svn.php',
        'Composer\\Util\\TlsHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/TlsHelper.php',
        'Composer\\Util\\Url' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Url.php',
        'Composer\\Util\\Zip' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Zip.php',
        'Composer\\XdebugHandler' => __DIR__ . '/..' . '/composer/composer/src/Composer/XdebugHandler.php',
        'Composer\\XdebugHandler\\PhpConfig' => __DIR__ . '/..' . '/composer/xdebug-handler/src/PhpConfig.php',
        'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php',
        'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php',
        'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php',
        'Humbug\\FileGetContents' => __DIR__ . '/..' . '/padraic/humbug_get_contents/src/FileGetContents.php',
        'Humbug\\SelfUpdate\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/ExceptionInterface.php',
        'Humbug\\SelfUpdate\\Exception\\FilesystemException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/FilesystemException.php',
        'Humbug\\SelfUpdate\\Exception\\HttpRequestException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/HttpRequestException.php',
        'Humbug\\SelfUpdate\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/InvalidArgumentException.php',
        'Humbug\\SelfUpdate\\Exception\\JsonParsingException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/JsonParsingException.php',
        'Humbug\\SelfUpdate\\Exception\\NoSignatureException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/NoSignatureException.php',
        'Humbug\\SelfUpdate\\Exception\\RuntimeException' => __DIR__ . '/..' . '/padraic/phar-updater/src/Exception/RuntimeException.php',
        'Humbug\\SelfUpdate\\Strategy\\GithubStrategy' => __DIR__ . '/..' . '/padraic/phar-updater/src/Strategy/GithubStrategy.php',
        'Humbug\\SelfUpdate\\Strategy\\Sha256Strategy' => __DIR__ . '/..' . '/padraic/phar-updater/src/Strategy/Sha256Strategy.php',
        'Humbug\\SelfUpdate\\Strategy\\ShaStrategy' => __DIR__ . '/..' . '/padraic/phar-updater/src/Strategy/ShaStrategy.php',
        'Humbug\\SelfUpdate\\Strategy\\ShaStrategyAbstract' => __DIR__ . '/..' . '/padraic/phar-updater/src/Strategy/ShaStrategyAbstract.php',
        'Humbug\\SelfUpdate\\Strategy\\StrategyInterface' => __DIR__ . '/..' . '/padraic/phar-updater/src/Strategy/StrategyInterface.php',
        'Humbug\\SelfUpdate\\Updater' => __DIR__ . '/..' . '/padraic/phar-updater/src/Updater.php',
        'Humbug\\SelfUpdate\\VersionParser' => __DIR__ . '/..' . '/padraic/phar-updater/src/VersionParser.php',
        'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
        'JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
        'JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
        'JsonSchema\\Constraints\\Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
        'JsonSchema\\Constraints\\ConstraintInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
        'JsonSchema\\Constraints\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
        'JsonSchema\\Constraints\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
        'JsonSchema\\Constraints\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
        'JsonSchema\\Constraints\\NumberConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
        'JsonSchema\\Constraints\\ObjectConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
        'JsonSchema\\Constraints\\SchemaConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
        'JsonSchema\\Constraints\\StringConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
        'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
        'JsonSchema\\Constraints\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
        'JsonSchema\\Constraints\\UndefinedConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
        'JsonSchema\\Entity\\JsonPointer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
        'JsonSchema\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
        'JsonSchema\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
        'JsonSchema\\Exception\\InvalidConfigException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
        'JsonSchema\\Exception\\InvalidSchemaException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
        'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
        'JsonSchema\\Exception\\InvalidSourceUriException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
        'JsonSchema\\Exception\\JsonDecodingException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
        'JsonSchema\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
        'JsonSchema\\Exception\\RuntimeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
        'JsonSchema\\Exception\\UnresolvableJsonPointerException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
        'JsonSchema\\Exception\\UriResolverException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
        'JsonSchema\\Exception\\ValidationException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
        'JsonSchema\\Iterator\\ObjectIterator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
        'JsonSchema\\Rfc3339' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
        'JsonSchema\\SchemaStorage' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
        'JsonSchema\\SchemaStorageInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
        'JsonSchema\\UriResolverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
        'JsonSchema\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
        'JsonSchema\\Uri\\Retrievers\\Curl' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
        'JsonSchema\\Uri\\Retrievers\\FileGetContents' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
        'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
        'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
        'JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
        'JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
        'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
        'Pickle\\Base\\Abstracts\\Console\\Command\\BuildCommand' => __DIR__ . '/../..' . '/src/Base/Abstracts/Console/Command/BuildCommand.php',
        'Pickle\\Base\\Abstracts\\Engine' => __DIR__ . '/../..' . '/src/Base/Abstracts/Engine.php',
        'Pickle\\Base\\Abstracts\\Engine\\Ini' => __DIR__ . '/../..' . '/src/Base/Abstracts/Engine/Ini.php',
        'Pickle\\Base\\Abstracts\\Package' => __DIR__ . '/../..' . '/src/Base/Abstracts/Package.php',
        'Pickle\\Base\\Abstracts\\Package\\Build' => __DIR__ . '/../..' . '/src/Base/Abstracts/Package/Build.php',
        'Pickle\\Base\\Abstracts\\Package\\Convey\\Command' => __DIR__ . '/../..' . '/src/Base/Abstracts/Package/Convey/Command.php',
        'Pickle\\Base\\Archive\\Factory' => __DIR__ . '/../..' . '/src/Base/Archive/Factory.php',
        'Pickle\\Base\\Archive\\Infozip' => __DIR__ . '/../..' . '/src/Base/Archive/Infozip.php',
        'Pickle\\Base\\Archive\\InfozipUnzipper' => __DIR__ . '/../..' . '/src/Base/Archive/InfozipUnzipper.php',
        'Pickle\\Base\\Archive\\InfozipZipper' => __DIR__ . '/../..' . '/src/Base/Archive/InfozipZipper.php',
        'Pickle\\Base\\Archive\\PHP' => __DIR__ . '/../..' . '/src/Base/Archive/PHP.php',
        'Pickle\\Base\\Archive\\PHPUnzipper' => __DIR__ . '/../..' . '/src/Base/Archive/PHPUnzipper.php',
        'Pickle\\Base\\Archive\\PHPZipper' => __DIR__ . '/../..' . '/src/Base/Archive/PHPZipper.php',
        'Pickle\\Base\\Interfaces\\Archive\\Unzipper' => __DIR__ . '/../..' . '/src/Base/Interfaces/Archive/Unzipper.php',
        'Pickle\\Base\\Interfaces\\Archive\\Zipper' => __DIR__ . '/../..' . '/src/Base/Interfaces/Archive/Zipper.php',
        'Pickle\\Base\\Interfaces\\Engine' => __DIR__ . '/../..' . '/src/Base/Interfaces/Engine.php',
        'Pickle\\Base\\Interfaces\\Engine\\Ini' => __DIR__ . '/../..' . '/src/Base/Interfaces/Engine/Ini.php',
        'Pickle\\Base\\Interfaces\\Package' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package.php',
        'Pickle\\Base\\Interfaces\\Package\\Build' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Build.php',
        'Pickle\\Base\\Interfaces\\Package\\Convey\\Command' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Convey/Command.php',
        'Pickle\\Base\\Interfaces\\Package\\Convey\\DefaultExecutor' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Convey/DefaultExecutor.php',
        'Pickle\\Base\\Interfaces\\Package\\Info' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Info.php',
        'Pickle\\Base\\Interfaces\\Package\\Release' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Release.php',
        'Pickle\\Base\\Interfaces\\Package\\Validate' => __DIR__ . '/../..' . '/src/Base/Interfaces/Package/Validate.php',
        'Pickle\\Base\\Pecl\\Website' => __DIR__ . '/../..' . '/src/Base/Pecl/Website.php',
        'Pickle\\Base\\Pecl\\WebsiteFactory' => __DIR__ . '/../..' . '/src/Base/Pecl/WebsiteFactory.php',
        'Pickle\\Base\\Util\\FileOps' => __DIR__ . '/../..' . '/src/Base/Util/FileOps.php',
        'Pickle\\Base\\Util\\GitIgnore' => __DIR__ . '/../..' . '/src/Base/Util/GitIgnore.php',
        'Pickle\\Base\\Util\\TmpDir' => __DIR__ . '/../..' . '/src/Base/Util/TmpDir.php',
        'Pickle\\Config' => __DIR__ . '/../..' . '/src/Config.php',
        'Pickle\\Console\\Application' => __DIR__ . '/../..' . '/src/Console/Application.php',
        'Pickle\\Console\\Command\\ConvertCommand' => __DIR__ . '/../..' . '/src/Console/Command/ConvertCommand.php',
        'Pickle\\Console\\Command\\InfoCommand' => __DIR__ . '/../..' . '/src/Console/Command/InfoCommand.php',
        'Pickle\\Console\\Command\\InstallerCommand' => __DIR__ . '/../..' . '/src/Console/Command/InstallerCommand.php',
        'Pickle\\Console\\Command\\ReleaseCommand' => __DIR__ . '/../..' . '/src/Console/Command/ReleaseCommand.php',
        'Pickle\\Console\\Command\\SelfUpdateCommand' => __DIR__ . '/../..' . '/src/Console/Command/SelfUpdateCommand.php',
        'Pickle\\Console\\Command\\ValidateCommand' => __DIR__ . '/../..' . '/src/Console/Command/ValidateCommand.php',
        'Pickle\\Console\\Helper\\PackageHelper' => __DIR__ . '/../..' . '/src/Console/Helper/PackageHelper.php',
        'Pickle\\Downloader\\PECLDownloader' => __DIR__ . '/../..' . '/src/Downloader/PECLDownloader.php',
        'Pickle\\Downloader\\TGZDownloader' => __DIR__ . '/../..' . '/src/Downloader/TGZDownloader.php',
        'Pickle\\Engine' => __DIR__ . '/../..' . '/src/Engine.php',
        'Pickle\\Engine\\Ini' => __DIR__ . '/../..' . '/src/Engine/Ini.php',
        'Pickle\\Engine\\PHP' => __DIR__ . '/../..' . '/src/Engine/PHP.php',
        'Pickle\\Engine\\PHP\\Ini' => __DIR__ . '/../..' . '/src/Engine/PHP/Ini.php',
        'Pickle\\Package' => __DIR__ . '/../..' . '/src/Package.php',
        'Pickle\\Package\\Command\\Build' => __DIR__ . '/../..' . '/src/Package/Command/Build.php',
        'Pickle\\Package\\Command\\Convert' => __DIR__ . '/../..' . '/src/Package/Command/Convert.php',
        'Pickle\\Package\\Command\\Info' => __DIR__ . '/../..' . '/src/Package/Command/Info.php',
        'Pickle\\Package\\Command\\Install' => __DIR__ . '/../..' . '/src/Package/Command/Install.php',
        'Pickle\\Package\\Command\\Release' => __DIR__ . '/../..' . '/src/Package/Command/Release.php',
        'Pickle\\Package\\Command\\Validate' => __DIR__ . '/../..' . '/src/Package/Command/Validate.php',
        'Pickle\\Package\\Convey' => __DIR__ . '/../..' . '/src/Package/Convey.php',
        'Pickle\\Package\\Convey\\Command\\Any' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Any.php',
        'Pickle\\Package\\Convey\\Command\\DefaultExecutor' => __DIR__ . '/../..' . '/src/Package/Convey/Command/DefaultExecutor.php',
        'Pickle\\Package\\Convey\\Command\\Factory' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Factory.php',
        'Pickle\\Package\\Convey\\Command\\Git' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Git.php',
        'Pickle\\Package\\Convey\\Command\\Pickle' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Pickle.php',
        'Pickle\\Package\\Convey\\Command\\SrcDir' => __DIR__ . '/../..' . '/src/Package/Convey/Command/SrcDir.php',
        'Pickle\\Package\\Convey\\Command\\Tgz' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Tgz.php',
        'Pickle\\Package\\Convey\\Command\\Type' => __DIR__ . '/../..' . '/src/Package/Convey/Command/Type.php',
        'Pickle\\Package\\PHP\\Command\\Build\\Unix' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Build/Unix.php',
        'Pickle\\Package\\PHP\\Command\\Build\\Windows' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Build/Windows.php',
        'Pickle\\Package\\PHP\\Command\\Convert' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Convert.php',
        'Pickle\\Package\\PHP\\Command\\Info' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Info.php',
        'Pickle\\Package\\PHP\\Command\\Install\\Windows\\Binary' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Install/Windows/Binary.php',
        'Pickle\\Package\\PHP\\Command\\Release' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Release.php',
        'Pickle\\Package\\PHP\\Command\\Release\\Windows\\Binary' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Release/Windows/Binary.php',
        'Pickle\\Package\\PHP\\Command\\Validate' => __DIR__ . '/../..' . '/src/Package/PHP/Command/Validate.php',
        'Pickle\\Package\\PHP\\Convey\\Command\\DefaultExecutor' => __DIR__ . '/../..' . '/src/Package/PHP/Convey/Command/DefaultExecutor.php',
        'Pickle\\Package\\PHP\\Convey\\Command\\Pecl' => __DIR__ . '/../..' . '/src/Package/PHP/Convey/Command/Pecl.php',
        'Pickle\\Package\\PHP\\Package' => __DIR__ . '/../..' . '/src/Package/PHP/Package.php',
        'Pickle\\Package\\PHP\\Util\\ConvertChangeLog' => __DIR__ . '/../..' . '/src/Package/PHP/Util/ConvertChangeLog.php',
        'Pickle\\Package\\PHP\\Util\\PackageJson' => __DIR__ . '/../..' . '/src/Package/PHP/Util/PackageJson.php',
        'Pickle\\Package\\PHP\\Util\\PackageXml' => __DIR__ . '/../..' . '/src/Package/PHP/Util/PackageXml.php',
        'Pickle\\Package\\PHP\\Util\\XML\\Loader' => __DIR__ . '/../..' . '/src/Package/PHP/Util/XML/Loader.php',
        'Pickle\\Package\\Util\\Dumper' => __DIR__ . '/../..' . '/src/Package/Util/Dumper.php',
        'Pickle\\Package\\Util\\Header\\Version' => __DIR__ . '/../..' . '/src/Package/Util/Header/Version.php',
        'Pickle\\Package\\Util\\JSON\\Dumper' => __DIR__ . '/../..' . '/src/Package/Util/JSON/Dumper.php',
        'Pickle\\Package\\Util\\JSON\\Loader' => __DIR__ . '/../..' . '/src/Package/Util/JSON/Loader.php',
        'Pickle\\Package\\Util\\Loader' => __DIR__ . '/../..' . '/src/Package/Util/Loader.php',
        'Pickle\\Package\\Util\\Windows\\DependencyLib' => __DIR__ . '/../..' . '/src/Package/Util/Windows/DependencyLib.php',
        'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php',
        'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php',
        'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php',
        'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
        'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php',
        'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
        'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php',
        'Seld\\JsonLint\\DuplicateKeyException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
        'Seld\\JsonLint\\JsonParser' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
        'Seld\\JsonLint\\Lexer' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
        'Seld\\JsonLint\\ParsingException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
        'Seld\\JsonLint\\Undefined' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
        'Seld\\PharUtils\\Linter' => __DIR__ . '/..' . '/seld/phar-utils/src/Linter.php',
        'Seld\\PharUtils\\Timestamps' => __DIR__ . '/..' . '/seld/phar-utils/src/Timestamps.php',
        'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
        'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
        'Symfony\\Component\\Console\\Attribute\\AsCommand' => __DIR__ . '/..' . '/symfony/console/Attribute/AsCommand.php',
        'Symfony\\Component\\Console\\CI\\GithubActionReporter' => __DIR__ . '/..' . '/symfony/console/CI/GithubActionReporter.php',
        'Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php',
        'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
        'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
        'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
        'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php',
        'Symfony\\Component\\Console\\Command\\CompleteCommand' => __DIR__ . '/..' . '/symfony/console/Command/CompleteCommand.php',
        'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => __DIR__ . '/..' . '/symfony/console/Command/DumpCompletionCommand.php',
        'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php',
        'Symfony\\Component\\Console\\Command\\LazyCommand' => __DIR__ . '/..' . '/symfony/console/Command/LazyCommand.php',
        'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php',
        'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php',
        'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => __DIR__ . '/..' . '/symfony/console/Command/SignalableCommandInterface.php',
        'Symfony\\Component\\Console\\Completion\\CompletionInput' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionInput.php',
        'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionSuggestions.php',
        'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/BashCompletionOutput.php',
        'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => __DIR__ . '/..' . '/symfony/console/Completion/Output/CompletionOutputInterface.php',
        'Symfony\\Component\\Console\\Completion\\Suggestion' => __DIR__ . '/..' . '/symfony/console/Completion/Suggestion.php',
        'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php',
        'Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php',
        'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
        'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php',
        'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
        'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
        'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
        'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleSignalEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php',
        'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php',
        'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php',
        'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php',
        'Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php',
        'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php',
        'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php',
        'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php',
        'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
        'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php',
        'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php',
        'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php',
        'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php',
        'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php',
        'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
        'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
        'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
        'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php',
        'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php',
        'Symfony\\Component\\Console\\Helper\\TableCellStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableCellStyle.php',
        'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php',
        'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php',
        'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php',
        'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
        'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
        'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
        'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
        'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
        'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
        'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
        'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
        'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
        'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
        'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
        'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php',
        'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php',
        'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php',
        'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php',
        'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php',
        'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php',
        'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => __DIR__ . '/..' . '/symfony/console/Output/TrimmedBufferOutput.php',
        'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php',
        'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php',
        'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php',
        'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalRegistry.php',
        'Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php',
        'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php',
        'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php',
        'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php',
        'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php',
        'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
        'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandCompletionTester.php',
        'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
        'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => __DIR__ . '/..' . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php',
        'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php',
        'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php',
        'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php',
        'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php',
        'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/RuntimeException.php',
        'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php',
        'Symfony\\Component\\Filesystem\\Path' => __DIR__ . '/..' . '/symfony/filesystem/Path.php',
        'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php',
        'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php',
        'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php',
        'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php',
        'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php',
        'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php',
        'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php',
        'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php',
        'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/LazyIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php',
        'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php',
        'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php',
        'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php',
        'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php',
        'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php',
        'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php',
        'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php',
        'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php',
        'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php',
        'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php',
        'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php',
        'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php',
        'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.php',
        'Symfony\\Component\\String\\AbstractString' => __DIR__ . '/..' . '/symfony/string/AbstractString.php',
        'Symfony\\Component\\String\\AbstractUnicodeString' => __DIR__ . '/..' . '/symfony/string/AbstractUnicodeString.php',
        'Symfony\\Component\\String\\ByteString' => __DIR__ . '/..' . '/symfony/string/ByteString.php',
        'Symfony\\Component\\String\\CodePointString' => __DIR__ . '/..' . '/symfony/string/CodePointString.php',
        'Symfony\\Component\\String\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/string/Exception/ExceptionInterface.php',
        'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/string/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\String\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/string/Exception/RuntimeException.php',
        'Symfony\\Component\\String\\Inflector\\EnglishInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/EnglishInflector.php',
        'Symfony\\Component\\String\\Inflector\\FrenchInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/FrenchInflector.php',
        'Symfony\\Component\\String\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/symfony/string/Inflector/InflectorInterface.php',
        'Symfony\\Component\\String\\LazyString' => __DIR__ . '/..' . '/symfony/string/LazyString.php',
        'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php',
        'Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php',
        'Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php',
        'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php',
        'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/SubscribedService.php',
        'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php',
        'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php',
        'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php',
        'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php',
        'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php',
        'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
        'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
        'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php',
        'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php',
        'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php',
        'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php',
        'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php',
        'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
        'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
    );

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

        }, null, ClassLoader::class);
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate\Strategy;

use Humbug\SelfUpdate\Updater;
use Humbug\SelfUpdate\VersionParser;
use Humbug\SelfUpdate\Exception\HttpRequestException;
use Humbug\SelfUpdate\Exception\InvalidArgumentException;
use Humbug\SelfUpdate\Exception\JsonParsingException;
use function Humbug\get_contents;

class GithubStrategy implements StrategyInterface
{
    const API_URL = 'https://packagist.org/p/%s.json';

    const STABLE = 'stable';

    const UNSTABLE = 'unstable';

    const ANY = 'any';

    /**
     * @var string
     */
    private $localVersion;

    /**
     * @var string
     */
    private $remoteVersion;

    /**
     * @var string
     */
    private $remoteUrl;

    /**
     * @var string
     */
    private $pharName;

    /**
     * @var string
     */
    private $packageName;

    /**
     * @var string
     */
    private $stability = self::STABLE;

    /**
     * Download the remote Phar file.
     *
     * @param Updater $updater
     * @return void
     */
    public function download(Updater $updater)
    {
        /** Switch remote request errors to HttpRequestExceptions */
        set_error_handler(array($updater, 'throwHttpRequestException'));
        $result = get_contents($this->remoteUrl);
        restore_error_handler();
        if (false === $result) {
            throw new HttpRequestException(sprintf(
                'Request to URL failed: %s', $this->remoteUrl
            ));
        }

        file_put_contents($updater->getTempPharFile(), $result);
    }

    /**
     * Retrieve the current version available remotely.
     *
     * @param Updater $updater
     * @return string|bool
     */
    public function getCurrentRemoteVersion(Updater $updater)
    {
        /** Switch remote request errors to HttpRequestExceptions */
        set_error_handler(array($updater, 'throwHttpRequestException'));
        $packageUrl = $this->getApiUrl();
        $package = json_decode(get_contents($packageUrl), true);
        restore_error_handler();

        if (null === $package || json_last_error() !== JSON_ERROR_NONE) {
            throw new JsonParsingException(
                'Error parsing JSON package data'
                . (function_exists('json_last_error_msg') ? ': ' . json_last_error_msg() : '')
            );
        }

        $versions = array_keys($package['packages'][$this->getPackageName()]);
        $versionParser = new VersionParser($versions);
        if ($this->getStability() === self::STABLE) {
            $this->remoteVersion = $versionParser->getMostRecentStable();
        } elseif ($this->getStability() === self::UNSTABLE) {
            $this->remoteVersion = $versionParser->getMostRecentUnstable();
        } else {
            $this->remoteVersion = $versionParser->getMostRecentAll();
        }

        /**
         * Setup remote URL if there's an actual version to download
         */
        if (!empty($this->remoteVersion)) {
            $this->remoteUrl = $this->getDownloadUrl($package);
        }

        return $this->remoteVersion;
    }

    /**
     * Retrieve the current version of the local phar file.
     *
     * @param Updater $updater
     * @return string
     */
    public function getCurrentLocalVersion(Updater $updater)
    {
        return $this->localVersion;
    }

    /**
     * Set version string of the local phar
     *
     * @param string $version
     */
    public function setCurrentLocalVersion($version)
    {
        $this->localVersion = $version;
    }

    /**
     * Set Package name
     *
     * @param string $name
     */
    public function setPackageName($name)
    {
        $this->packageName = $name;
    }

    /**
     * Get Package name
     *
     * @return string
     */
    public function getPackageName()
    {
        return $this->packageName;
    }

    /**
     * Set phar file's name
     *
     * @param string $name
     */
    public function setPharName($name)
    {
        $this->pharName = $name;
    }

    /**
     * Get phar file's name
     *
     * @return string
     */
    public function getPharName()
    {
        return $this->pharName;
    }

    /**
     * Set target stability
     *
     * @param string $stability
     */
    public function setStability($stability)
    {
        if ($stability !== self::STABLE && $stability !== self::UNSTABLE && $stability !== self::ANY) {
            throw new InvalidArgumentException(
                'Invalid stability value. Must be one of "stable", "unstable" or "any".'
            );
        }
        $this->stability = $stability;
    }

    /**
     * Get target stability
     *
     * @return string
     */
    public function getStability()
    {
        return $this->stability;
    }

    protected function getApiUrl()
    {
        return sprintf(self::API_URL, $this->getPackageName());
    }

    protected function getDownloadUrl(array $package)
    {
        $baseUrl = preg_replace(
            '{\.git$}',
            '',
            $package['packages'][$this->getPackageName()][$this->remoteVersion]['source']['url']
        );
        $downloadUrl = sprintf(
            '%s/releases/download/%s/%s',
            $baseUrl,
            $this->remoteVersion,
            $this->getPharName()
        );
        return $downloadUrl;
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate\Strategy;

use Humbug\SelfUpdate\Updater;

interface StrategyInterface
{

    /**
     * Download the remote Phar file.
     *
     * @param Updater $updater
     * @return void
     */
    public function download(Updater $updater);

    /**
     * Retrieve the current version available remotely.
     *
     * @param Updater $updater
     * @return string|bool
     */
    public function getCurrentRemoteVersion(Updater $updater);

    /**
     * Retrieve the current version of the local phar file.
     *
     * @param Updater $updater
     * @return string
     */
    public function getCurrentLocalVersion(Updater $updater);
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2017 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate\Strategy;

use Humbug\SelfUpdate\Exception\HttpRequestException;
use Humbug\SelfUpdate\Updater;
use function Humbug\get_contents;

final class Sha256Strategy extends ShaStrategyAbstract
{
    /**
     * Retrieve the current version available remotely.
     *
     * @param Updater $updater
     * @return string|bool
     */
    public function getCurrentRemoteVersion(Updater $updater)
    {
        /** Switch remote request errors to HttpRequestExceptions */
        set_error_handler(array($updater, 'throwHttpRequestException'));
        $version = get_contents($this->getVersionUrl());
        restore_error_handler();
        if (false === $version) {
            throw new HttpRequestException(sprintf(
                'Request to URL failed: %s', $this->getVersionUrl()
            ));
        }
        if (empty($version)) {
            throw new HttpRequestException(
                'Version request returned empty response.'
            );
        }
        if (!preg_match('%^[a-z0-9]{64}%', $version, $matches)) {
            throw new HttpRequestException(
                'Version request returned incorrectly formatted response.'
            );
        }

        return $matches[0];
    }

    /**
     * Retrieve the current version of the local phar file.
     *
     * @param Updater $updater
     * @return string
     */
    public function getCurrentLocalVersion(Updater $updater)
    {
        return hash_file('sha256', $updater->getLocalPharFile());
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate\Strategy;

use Humbug\SelfUpdate\Exception\HttpRequestException;
use Humbug\SelfUpdate\Exception\InvalidArgumentException;
use Humbug\SelfUpdate\Updater;
use function Humbug\get_contents;

abstract class ShaStrategyAbstract implements StrategyInterface
{
    /** @private */
    const SUPPORTED_SCHEMES = [
        'http',
        'https',
        'file',
    ];

    /**
     * @var string
     */
    protected $versionUrl;

    /**
     * @var string
     */
    protected $pharUrl;

    /**
     * Download the remote Phar file.
     *
     * @param Updater $updater
     * @return void
     */
    public function download(Updater $updater)
    {
        /** Switch remote request errors to HttpRequestExceptions */
        set_error_handler(array($updater, 'throwHttpRequestException'));
        $result = get_contents($this->getPharUrl());
        restore_error_handler();
        if (false === $result) {
            throw new HttpRequestException(sprintf(
                'Request to URL failed: %s', $this->getPharUrl()
            ));
        }

        file_put_contents($updater->getTempPharFile(), $result);
    }

    /**
     * Set URL to phar file
     *
     * @param string $url
     */
    public function setPharUrl($url)
    {
        if (!$this->validateAllowedUrl($url)) {
            throw new InvalidArgumentException(
                sprintf('Invalid url passed as argument: %s.', $url)
            );
        }
        $this->pharUrl = $url;
    }

    /**
     * Get URL for phar file
     *
     * @return string
     */
    public function getPharUrl()
    {
        return $this->pharUrl;
    }

    /**
     * Set URL to version file
     *
     * @param string $url
     */
    public function setVersionUrl($url)
    {
        if (!$this->validateAllowedUrl($url)) {
            throw new InvalidArgumentException(
                sprintf('Invalid url passed as argument: %s.', $url)
            );
        }
        $this->versionUrl = $url;
    }

    /**
     * Get URL for version file
     *
     * @return string
     */
    public function getVersionUrl()
    {
        return $this->versionUrl;
    }

    protected function validateAllowedUrl($url)
    {
        return (
            filter_var($url, FILTER_VALIDATE_URL)
            && in_array(parse_url($url, PHP_URL_SCHEME), self::SUPPORTED_SCHEMES)
        );
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate\Strategy;

use Humbug\SelfUpdate\Exception\HttpRequestException;
use Humbug\SelfUpdate\Updater;
use function Humbug\get_contents;

/**
 * @deprecated 1.0.4 SHA-1 is increasingly susceptible to collision attacks; use SHA-256
 */
class ShaStrategy extends ShaStrategyAbstract
{

    /**
     * Retrieve the current version available remotely.
     *
     * @param Updater $updater
     * @return string|bool
     */
    public function getCurrentRemoteVersion(Updater $updater)
    {
        /** Switch remote request errors to HttpRequestExceptions */
        set_error_handler(array($updater, 'throwHttpRequestException'));
        $version = get_contents($this->getVersionUrl());
        restore_error_handler();
        if (false === $version) {
            throw new HttpRequestException(sprintf(
                'Request to URL failed: %s', $this->getVersionUrl()
            ));
        }
        if (empty($version)) {
            throw new HttpRequestException(
                'Version request returned empty response.'
            );
        }
        if (!preg_match('%^[a-z0-9]{40}%', $version, $matches)) {
            throw new HttpRequestException(
                'Version request returned incorrectly formatted response.'
            );
        }

        return $matches[0];
    }

    /**
     * Retrieve the current version of the local phar file.
     *
     * @param Updater $updater
     * @return string
     */
    public function getCurrentLocalVersion(Updater $updater)
    {
        return sha1_file($updater->getLocalPharFile());
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

interface ExceptionInterface
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class FilesystemException extends RuntimeException
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class HttpRequestException extends RuntimeException
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class NoSignatureException extends RuntimeException
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class JsonParsingException extends RuntimeException
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/humbug/blob/master/LICENSE New BSD License
 */

namespace Humbug\SelfUpdate\Exception;

class InvalidArgumentException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's self-update.
 */

namespace Humbug\SelfUpdate;

use Humbug\SelfUpdate\Exception\RuntimeException;
use Humbug\SelfUpdate\Exception\InvalidArgumentException;
use Humbug\SelfUpdate\Exception\FilesystemException;
use Humbug\SelfUpdate\Exception\HttpRequestException;
use Humbug\SelfUpdate\Exception\NoSignatureException;
use Humbug\SelfUpdate\Strategy\StrategyInterface;
use Humbug\SelfUpdate\Strategy\ShaStrategy;
use Humbug\SelfUpdate\Strategy\Sha256Strategy;
use Humbug\SelfUpdate\Strategy\GithubStrategy;

class Updater
{
    const STRATEGY_SHA1 = 'sha1';

    const STRATEGY_SHA256 = 'sha256';

    const STRATEGY_GITHUB = 'github';

    /**
     * @var StrategyInterface
     */
    protected $strategy;

    /**
     * @var string
     */
    protected $localPharFile;

    /**
     * @var string
     */
    protected $localPharFileBasename;

    /**
     * @var string
     */
    protected $localPubKeyFile;

    /**
     * @var bool
     */
    protected $hasPubKey;

    /**
     * @var string
     */
    protected $tempDirectory;

    /**
     * @var string
     */
    protected $newVersion;

    /**
     * @var string
     */
    protected $oldVersion;

    /**
     * @var string
     */
    protected $backupExtension = '-old.phar';

    /**
     * @var string
     */
    protected $backupPath;

    /**
     * @var string
     */
    protected $restorePath;

    /**
     * @var bool
     */
    protected $newVersionAvailable;

    /**
     * Constructor
     *
     * @param string $localPharFile
     * @param bool $hasPubKey
     * @param string $strategy
     */
    public function __construct($localPharFile = null, $hasPubKey = true, $strategy = self::STRATEGY_SHA1)
    {
        ini_set('phar.require_hash', 1);
        $this->setLocalPharFile($localPharFile);
        if (!is_bool($hasPubKey)) {
            throw new InvalidArgumentException(
                'Constructor parameter $hasPubKey must be boolean or null.'
            );
        } else {
            $this->hasPubKey = $hasPubKey;
        }
        if ($this->hasPubKey) {
            $this->setLocalPubKeyFile();
        }
        $this->setTempDirectory();
        $this->setStrategy($strategy);
    }

    /**
     * Check for update
     *
     * @return bool
     */
    public function hasUpdate()
    {
        $this->newVersionAvailable = $this->newVersionAvailable();
        return $this->newVersionAvailable;
    }

    /**
     * Perform an update
     *
     * @return bool
     */
    public function update()
    {
        if ($this->newVersionAvailable === false
        || (!is_bool($this->newVersionAvailable) && !$this->hasUpdate())) {
            return false;
        }
        $this->backupPhar();
        $this->downloadPhar();
        $this->replacePhar();
        return true;
    }

    /**
     * Perform an rollback to previous version
     *
     * @return bool
     */
    public function rollback()
    {
        if (!$this->restorePhar()) {
            return false;
        }
        return true;
    }

    /**
     * @param string $strategy
     */
    public function setStrategy($strategy)
    {
        switch ($strategy) {
            case self::STRATEGY_GITHUB:
                $this->strategy = new GithubStrategy;
                break;

            case self::STRATEGY_SHA256:
                $this->strategy = new Sha256Strategy;
                break;

            default:
                $this->strategy = new ShaStrategy;
                break;
        }
    }

    public function setStrategyObject(StrategyInterface $strategy)
    {
        $this->strategy = $strategy;
    }

    public function getStrategy()
    {
        return $this->strategy;
    }

    /**
     * Set backup extension for old phar versions
     *
     * @param string $extension
     */
    public function setBackupExtension($extension)
    {
        $this->backupExtension = $extension;
    }

    /**
     * Get backup extension for old phar versions
     *
     * @return string
     */
    public function getBackupExtension()
    {
        return $this->backupExtension;
    }

    public function getLocalPharFile()
    {
        return $this->localPharFile;
    }

    public function getLocalPharFileBasename()
    {
        return $this->localPharFileBasename;
    }

    public function getLocalPubKeyFile()
    {
        return $this->localPubKeyFile;
    }

    public function getTempDirectory()
    {
        return $this->tempDirectory;
    }

    public function getTempPharFile()
    {
        return $this->getTempDirectory()
            . '/'
            . sprintf('%s.phar.temp', $this->getLocalPharFileBasename());
    }

    public function getNewVersion()
    {
        return $this->newVersion;
    }

    public function getOldVersion()
    {
        return $this->oldVersion;
    }

    /**
     * Set backup path for old phar versions
     *
     * @param string $filePath
     */
    public function setBackupPath($filePath)
    {
        $path = realpath(dirname($filePath));
        if (!is_dir($path)) {
            throw new FilesystemException(sprintf(
                'The backup directory does not exist: %s.', $path
            ));
        }
        if (!is_writable($path)) {
            throw new FilesystemException(sprintf(
                'The backup directory is not writeable: %s.', $path
            ));
        }
        $this->backupPath = $filePath;
    }

    /**
     * Get backup path for old phar versions
     *
     * @return string
     */
    public function getBackupPath()
    {
        return $this->backupPath;
    }

    /**
     * Set path for the backup phar to rollback/restore from
     *
     * @param string $filePath
     */
    public function setRestorePath($filePath)
    {
        $path = realpath(dirname($filePath));
        if (!file_exists($path)) {
            throw new FilesystemException(sprintf(
                'The restore phar does not exist: %s.', $path
            ));
        }
        if (!is_readable($path)) {
            throw new FilesystemException(sprintf(
                'The restore file is not readable: %s.', $path
            ));
        }
        $this->restorePath = $filePath;
    }

    /**
     * Get path for the backup phar to rollback/restore from
     *
     * @return string
     */
    public function getRestorePath()
    {
        return $this->restorePath;
    }

    public function throwRuntimeException($errno, $errstr)
    {
        throw new RuntimeException($errstr);
    }

    public function throwHttpRequestException($errno, $errstr)
    {
        throw new HttpRequestException($errstr);
    }

    protected function hasPubKey()
    {
        return $this->hasPubKey;
    }

    protected function newVersionAvailable()
    {
        $this->newVersion = $this->strategy->getCurrentRemoteVersion($this);
        $this->oldVersion = $this->strategy->getCurrentLocalVersion($this);

        if (!empty($this->newVersion) && ($this->newVersion !== $this->oldVersion)) {
            return true;
        }
        return false;
    }

    protected function backupPhar()
    {
        $result = copy($this->getLocalPharFile(), $this->getBackupPharFile());
        if ($result === false) {
            $this->cleanupAfterError();
            throw new FilesystemException(sprintf(
                'Unable to backup %s to %s.',
                $this->getLocalPharFile(),
                $this->getBackupPharFile()
            ));
        }
    }

    protected function downloadPhar()
    {
        $this->strategy->download($this);

        if (!file_exists($this->getTempPharFile())) {
            throw new FilesystemException(
                'Creation of download file failed.'
            );
        }

        if ($this->getStrategy() instanceof ShaStrategy
            || $this->getStrategy() instanceof Sha256Strategy
        ) {
            if ($this->getStrategy() instanceof ShaStrategy) {
                $tmpVersion = sha1_file($this->getTempPharFile());
                $algo = 'SHA-1';
            } else {
                $tmpVersion = hash_file('sha256', $this->getTempPharFile());
                $algo = 'SHA-256';
            }
            if ($tmpVersion !== $this->getNewVersion()) {
                $this->cleanupAfterError();
                throw new HttpRequestException(sprintf(
                    'Download file appears to be corrupted or outdated. The file '
                        . 'received does not have the expected %s hash: %s.',
                    $algo,
                    $this->getNewVersion()
                ));
            }
        }

        try {
            $this->validatePhar($this->getTempPharFile());
        } catch (\Exception $e) {
            restore_error_handler();
            $this->cleanupAfterError();
            throw $e;
        }
    }

    protected function replacePhar()
    {
        rename($this->getTempPharFile(), $this->getLocalPharFile());
    }

    protected function restorePhar()
    {
        $backup = $this->getRestorePharFile();
        if (!file_exists($backup)) {
            throw new RuntimeException(sprintf(
                'The backup file does not exist: %s.', $backup
            ));
        }
        $this->validatePhar($backup);
        return rename($backup, $this->getLocalPharFile());
    }

    protected function getBackupPharFile()
    {
        if (null !== $this->getBackupPath()) {
            return $this->getBackupPath();
        }
        return $this->getTempDirectory()
            . '/'
            . sprintf('%s%s', $this->getLocalPharFileBasename(), $this->getBackupExtension());
    }

    protected function getRestorePharFile()
    {
        if (null !== $this->getRestorePath()) {
            return $this->getRestorePath();
        }
        return $this->getTempDirectory()
            . '/'
            . sprintf('%s%s', $this->getLocalPharFileBasename(), $this->getBackupExtension()
        );
    }

    protected function getTempPubKeyFile()
    {
        return $this->getTempDirectory()
            . '/'
            . sprintf('%s.phar.temp.pubkey', $this->getLocalPharFileBasename());
    }

    protected function setLocalPharFile($localPharFile)
    {
        if (!is_null($localPharFile)) {
            $localPharFile = realpath($localPharFile);
        } else {
            $localPharFile = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
        }
        if (!file_exists($localPharFile)) {
            throw new RuntimeException(sprintf(
                'The set phar file does not exist: %s.', $localPharFile
            ));
        }
        if (!is_writable($localPharFile)) {
            throw new FilesystemException(sprintf(
                'The current phar file is not writeable and cannot be replaced: %s.',
                $localPharFile
            ));
        }
        $this->localPharFile = $localPharFile;
        $this->localPharFileBasename = basename($localPharFile, '.phar');
    }

    protected function setLocalPubKeyFile()
    {
        $localPubKeyFile = $this->getLocalPharFile() . '.pubkey';
        if (!file_exists($localPubKeyFile)) {
            throw new RuntimeException(sprintf(
                'The phar pubkey file does not exist: %s.', $localPubKeyFile
            ));
        }
        $this->localPubKeyFile = $localPubKeyFile;
    }

    protected function setTempDirectory()
    {
        $tempDirectory = dirname($this->getLocalPharFile());
        if (!is_writable($tempDirectory)) {
            throw new FilesystemException(sprintf(
                'The directory is not writeable: %s.', $tempDirectory
            ));
        }
        $this->tempDirectory = $tempDirectory;
    }

    protected function validatePhar($phar)
    {
        $phar = realpath($phar);
        if ($this->hasPubKey()) {
            copy($this->getLocalPubKeyFile(), $phar . '.pubkey');
        }
        chmod($phar, fileperms($this->getLocalPharFile()));
        /** Switch invalid key errors to RuntimeExceptions */
        set_error_handler(array($this, 'throwRuntimeException'));
        $phar = new \Phar($phar);
        $signature = $phar->getSignature();
        if ($this->hasPubKey() && strtolower($signature['hash_type']) !== 'openssl') {
            throw new NoSignatureException(
                'The downloaded phar file has no OpenSSL signature.'
            );
        }
        restore_error_handler();
        if ($this->hasPubKey()) {
            @unlink($phar . '.pubkey');
        }
        unset($phar);
    }

    protected function cleanupAfterError()
    {
        //@unlink($this->getBackupPharFile());
        @unlink($this->getTempPharFile());
        @unlink($this->getTempPubKeyFile());
    }
}
<?php
/**
 * Humbug
 *
 * @category   Humbug
 * @package    Humbug
 * @copyright  Copyright (c) 2015 Pádraic Brady (http://blog.astrumfutura.com)
 * @license    https://github.com/padraic/phar-updater/blob/master/LICENSE New BSD License
 *
 * This class is partially patterned after Composer's version parser.
 */

namespace Humbug\SelfUpdate;

class VersionParser
{

    /**
     * @var array
     */
    private $versions;

    /**
     * @var string
     */
    private $modifier = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';

    /**
     * @param array $versions
     */
    public function __construct(array $versions = array())
    {
        $this->versions = $versions;
    }

    /**
     * Get the most recent stable numbered version from versions passed to
     * constructor (if any)
     *
     * @return string
     */
    public function getMostRecentStable()
    {
        return $this->selectRecentStable();
    }

    /**
     * Get the most recent unstable numbered version from versions passed to
     * constructor (if any)
     *
     * @return string
     */
    public function getMostRecentUnStable()
    {
        return $this->selectRecentUnstable();
    }

    /**
     * Get the most recent stable or unstable numbered version from versions passed to
     * constructor (if any)
     *
     * @return string
     */
    public function getMostRecentAll()
    {
        return $this->selectRecentAll();
    }

    /**
     * Checks if given version string represents a stable numbered version
     *
     * @param string $version
     * @return bool
     */
    public function isStable($version)
    {
        return $this->stable($version);
    }

    /**
     * Checks if given version string represents a 'pre-release' version, i.e.
     * it's unstable but not development level.
     *
     * @param string $version
     * @return bool
     */
    public function isPreRelease($version)
    {
        return !$this->stable($version) && !$this->development($version);
    }

    /**
     * Checks if given version string represents an unstable or dev-level
     * numbered version
     *
     * @param string $version
     * @return bool
     */
    public function isUnstable($version)
    {
        return !$this->stable($version);
    }

    /**
     * Checks if given version string represents a dev-level numbered version
     *
     * @param string $version
     * @return bool
     */
    public function isDevelopment($version)
    {
        return $this->development($version);
    }

    private function selectRecentStable()
    {
        $candidates = array();
        foreach ($this->versions as $version) {
            if (!$this->stable($version)) {
                continue;
            }
            $candidates[] = $version;
        }
        if (empty($candidates)) {
            return false;
        }
        return $this->findMostRecent($candidates);
    }

    private function selectRecentUnstable()
    {
        $candidates = array();
        foreach ($this->versions as $version) {
            if ($this->stable($version) || $this->development($version)) {
                continue;
            }
            $candidates[] = $version;
        }
        if (empty($candidates)) {
            return false;
        }
        return $this->findMostRecent($candidates);
    }

    private function selectRecentAll()
    {
        $candidates = array();
        foreach ($this->versions as $version) {
            if ($this->development($version)) {
                continue;
            }
            $candidates[] = $version;
        }
        if (empty($candidates)) {
            return false;
        }
        return $this->findMostRecent($candidates);
    }

    private function findMostRecent(array $candidates)
    {
        $candidate = null;
        $tracker = null;
        foreach ($candidates as $version) {
            if (version_compare($candidate, $version, '<')) {
                $candidate = $version;
            }
        }
        return $candidate;
    }

    private function stable($version)
    {
        $version = preg_replace('{#.+$}i', '', $version);
        if ($this->development($version)) {
            return false;
        }
        preg_match('{'.$this->modifier.'$}i', strtolower($version), $match);
        if (!empty($match[3])) {
            return false;
        }
        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]
            || 'alpha' === $match[1] || 'a' === $match[1]
            || 'rc' === $match[1]) {
                return false;
            }
        }
        return true;
    }

    private function development($version)
    {
        if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
            return true;
        }
        if (1 == preg_match("/-\d+-[a-z0-9]{8,}$/", $version)) {
            return true;
        }
        return false;
    }
}
<?php

/*
 * This file is part of the Humbug package.
 *
 * (c) 2015 Pádraic Brady <padraic.brady@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Humbug;

/**
 * Reads entire file into a string.
 *
 * @param string              $filename Name of the file to read.
 * @param bool                $use_include_path
 * @param resource|array|null $context A valid context resource created with `stream_context_create()`. If you don't
 *                                     need to use a custom context, you can skip this parameter.
 *
 * @return string|bool The read data or `false` on failure.
 */
function get_contents($filename, $use_include_path = false, $context = null)
{
    static $fileGetContents = null;

    if ('https' == parse_url($filename, PHP_URL_SCHEME) && PHP_VERSION_ID < 50600) {
        if (!isset($fileGetContents)) {
            $fileGetContents = new FileGetContents();
        }

        return $fileGetContents->get($filename, $context);
    } elseif (FileGetContents::hasNextRequestHeaders()) {
        if ($context === null) {
            $context = stream_context_create();
        }

        $context = FileGetContents::setHttpHeaders($context);
    }

    $return = file_get_contents($filename, $use_include_path, $context);

    if (isset($http_response_header)) {
        FileGetContents::setLastResponseHeaders($http_response_header);
    }

    return $return;
}

/**
 * Fetches all the headers sent by the server in response to a HTTPS request triggered by `get_contents()`.
 *
 * @return array|null Returns an indexed or associative array with the headers, or `null` on failure or if no request
 *                    has been made yet.
 */
function get_headers()
{
    return FileGetContents::getLastResponseHeaders();
}

/**
 * @param array $headers An indexed or associative array.
 */
function set_headers(array $headers)
{
    FileGetContents::setNextRequestHeaders($headers);
}
<?php

/*
 * This file is part of the Humbug package.
 *
 * (c) 2015 Pádraic Brady <padraic.brady@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Humbug;

use Composer\CaBundle\CaBundle;
use RuntimeException;

/**
 * This is largely extracted from the Composer Installer where originally implemented.
 */
class FileGetContents
{
    /**
     * @var array|null
     *
     * @private
     */
    protected static $lastResponseHeaders;

    /**
     * @var array|null
     *
     * @private
     */
    protected static $nextRequestHeaders;

    protected $options = array('http' => array());

    public function __construct()
    {
        $options = $this->getTlsStreamContextDefaults(null);
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * @param string              $filename Name of the file to read.
     * @param resource|array|null $context A valid context resource created with `stream_context_create()`. If you don't
     *                                     need to use a custom context, you can skip this parameter.
     *
     * @return bool|string The read data or `false` on failure.
     */
    public function get($filename, $context = null)
    {
        $context = $this->getStreamContext($filename);
        $context = self::setHttpHeaders($context);

        $result = file_get_contents($filename, null, $context);

        self::setLastResponseHeaders($http_response_header);

        return $result;
    }

    /**
     * @param array $headers HTTP response headers.
     *
     * @final Since 1.1.0
     */
    public static function setLastResponseHeaders($headers)
    {
        self::$lastResponseHeaders = $headers;
    }

    /**
     * @return array|null HTTP response headers for the last response recorded or `null` if none has been recorded.
     *
     * @final Since 1.1.0
     */
    public static function getLastResponseHeaders()
    {
        return self::$lastResponseHeaders;
    }

    /**
     * @param array $headers An indexed or associative array.
     *
     * @final since 1.1.0
     */
    public static function setNextRequestHeaders(array $headers)
    {
        self::$nextRequestHeaders = $headers;
    }

    /**
     * @return bool
     *
     * @final since 1.1.0
     */
    public static function hasNextRequestHeaders()
    {
        return null !== self::$nextRequestHeaders;
    }

    /**
     * @return array|null An indexed or associative array whence there is any headers, `null` otherwise.
     */
    public static function getNextRequestHeaders()
    {
        $return = self::$nextRequestHeaders;
        self::$nextRequestHeaders = null;

        return $return;
    }

    /**
     * @param resource|array|null $context A valid context resource created with `stream_context_create()`. If you don't
     *                                     need to use a custom context, you can skip this parameter.
     *
     * @return resource|array|null Context to which the headers has been set.
     *
     * @private since 1.1.0
     *
     * TODO (2.0.0): change the name to reflect on the immutable side.
     */
    public static function setHttpHeaders($context)
    {
        $headers = self::getNextRequestHeaders();

        if (empty($headers)) {
            return $context;
        }

        $options = stream_context_get_options($context);
        if (!isset($options['http'])) {
            $options['http'] = array('header' => array());
        } elseif (!isset($options['http']['header'])) {
            $options['http']['header'] = array();
        } elseif (is_string($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", $options['http']['header']);
        }

        $headers = empty($options['http']['headers']) ? $headers : array_merge($options['http']['headers'], $headers);

        stream_context_set_option(
            $context,
            'http',
            'header',
            $headers
        );

        return $context;
    }

    /**
     * @param string $url URL path to access to the file to read.
     *
     * @return resource
     *
     * @final since 1.1.0
     */
    protected function getStreamContext($url)
    {
        $host = parse_url($url, PHP_URL_HOST);

        if (PHP_VERSION_ID < 50600) {
            $this->options['ssl']['CN_match'] = $host;
            $this->options['ssl']['SNI_server_name'] = $host;
        }

        return $this->getMergedStreamContext($url);
    }

    /**
     * @param string $cafile
     *
     * @return array
     *
     * @private since 1.1.0
     *
     * TODO (2.0.0): remove argument (unnused in the codebase) and rename this method as an `init*` as is used in the
     *               constructor anymore
     */
    protected function getTlsStreamContextDefaults($cafile)
    {
        $ciphers = implode(':', array(
            'ECDHE-RSA-AES128-GCM-SHA256',
            'ECDHE-ECDSA-AES128-GCM-SHA256',
            'ECDHE-RSA-AES256-GCM-SHA384',
            'ECDHE-ECDSA-AES256-GCM-SHA384',
            'DHE-RSA-AES128-GCM-SHA256',
            'DHE-DSS-AES128-GCM-SHA256',
            'kEDH+AESGCM',
            'ECDHE-RSA-AES128-SHA256',
            'ECDHE-ECDSA-AES128-SHA256',
            'ECDHE-RSA-AES128-SHA',
            'ECDHE-ECDSA-AES128-SHA',
            'ECDHE-RSA-AES256-SHA384',
            'ECDHE-ECDSA-AES256-SHA384',
            'ECDHE-RSA-AES256-SHA',
            'ECDHE-ECDSA-AES256-SHA',
            'DHE-RSA-AES128-SHA256',
            'DHE-RSA-AES128-SHA',
            'DHE-DSS-AES128-SHA256',
            'DHE-RSA-AES256-SHA256',
            'DHE-DSS-AES256-SHA',
            'DHE-RSA-AES256-SHA',
            'AES128-GCM-SHA256',
            'AES256-GCM-SHA384',
            'AES128-SHA256',
            'AES256-SHA256',
            'AES128-SHA',
            'AES256-SHA',
            'AES',
            'CAMELLIA',
            'DES-CBC3-SHA',
            '!aNULL',
            '!eNULL',
            '!EXPORT',
            '!DES',
            '!RC4',
            '!MD5',
            '!PSK',
            '!aECDH',
            '!EDH-DSS-DES-CBC3-SHA',
            '!EDH-RSA-DES-CBC3-SHA',
            '!KRB5-DES-CBC3-SHA',
            '!ADH'
        ));

        $options = array(
            'ssl' => array(
                'ciphers' => $ciphers,
                'verify_peer' => true,
                'verify_depth' => 7,
                'SNI_enabled' => true,
            )
        );

        if (!$cafile) {
            $cafile = CaBundle::getSystemCaRootBundlePath();
        }
        if (is_dir($cafile)) {
            $options['ssl']['capath'] = $cafile;
        } elseif ($cafile) {
            $options['ssl']['cafile'] = $cafile;
        } else {
            throw new RuntimeException('A valid cafile could not be located locally.');
        }

        if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
            $options['ssl']['disable_compression'] = true;
        }

        return $options;
    }

    /**
     * Function copied from Composer\Util\StreamContextFactory::getContext
     *
     * This function is part of Composer.
     *
     * (c) Nils Adermann <naderman@naderman.de>
     *     Jordi Boggiano <j.boggiano@seld.be>
     *
     * @param string $url URL the context is to be used for
     *
     * @throws \RuntimeException If https proxy required and OpenSSL uninstalled
     *
     * @return resource Default context
     *
     * @final since 1.1.0
     */
    protected function getMergedStreamContext($url)
    {
        $options = $this->options;

        // See CVE-2016-5385, due to (emulation of) header copying with PHP web SAPIs into HTTP_* variables,
        // HTTP_PROXY can be set by an user to any value he wants by setting the Proxy header.
        // Mitigate the vulnerability by only allowing CLI SAPIs to use HTTP(S)_PROXY environment variables.
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            // Handle system proxy
            if (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy'])) {
                // Some systems seem to rely on a lowercased version instead...
                $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
            }
        }

        if (!empty($proxy)) {
            $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'].'://' : '';
            $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';

            if (isset($proxy['port'])) {
                $proxyURL .= ':'.$proxy['port'];
            } elseif ('http://' == substr($proxyURL, 0, 7)) {
                $proxyURL .= ':80';
            } elseif ('https://' == substr($proxyURL, 0, 8)) {
                $proxyURL .= ':443';
            }

            // http(s):// is not supported in proxy
            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);

            if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
                throw new RuntimeException('You must enable the openssl extension to use a proxy over https');
            }

            $options['http'] = array(
                'proxy' => $proxyURL,
            );

            // enabled request_fulluri unless it is explicitly disabled
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
                case 'https': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
            }


            if (isset($proxy['user'])) {
                $auth = urldecode($proxy['user']);
                if (isset($proxy['pass'])) {
                    $auth .= ':'.urldecode($proxy['pass']);
                }
                $auth = base64_encode($auth);

                $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
            }
        }

        return stream_context_create($options);
    }

    /**
     * @deprecated since 1.1.0 and will be removed in 2.0.0.
     */
    public static function getSystemCaRootBundlePath()
    {
        @trigger_error(
            'Deprecated since 1.1.0. Use `Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()` instead.',
            E_USER_DEPRECATED
        );

        return CaBundle::getSystemCaRootBundlePath();
    }

    /**
     * @deprecated since 1.1.0 and will be removed in 2.0.0.
     */
    protected static function validateCaFile($contents)
    {
        // Assumes the CA is valid if PHP is vulnerable to
        // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
        if (!CaBundle::isOpensslParseSafe()) {
            return !empty($contents);
        }

        return (bool) openssl_x509_parse($contents);
    }
}
<?php

/*
 * This file is part of the Humbug package.
 *
 * (c) 2015 Pádraic Brady <padraic.brady@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (!function_exists('humbug_get_contents')) {
    function humbug_get_contents($filename, $use_include_path = false, $context = null)
    {
        @trigger_error(
            'humbug_get_contents() is deprecated since 1.1.0 and will be removed in 2.0.0. Use '
            .'Humbug/get_contents() instead.',
            E_USER_DEPRECATED
        );

        return Humbug\get_contents($filename, $use_include_path, $context);
    }
}

if (!function_exists('humbug_get_headers')) {
    function humbug_get_headers()
    {
        @trigger_error(
            'humbug_get_headers() is deprecated since 1.1.0 and will be removed in 2.0.0. Use '
            .'Humbug/get_headers() instead.',
            E_USER_DEPRECATED
        );

        return Humbug\get_headers();
    }
}

if (!function_exists('humbug_set_headers')) {
    function humbug_set_headers(array $headers)
    {
        @trigger_error(
            'humbug_set_headers() is deprecated since 1.1.0 and will be removed in 2.0.0. Use '
            .'Humbug/get_headers() instead.',
            E_USER_DEPRECATED
        );

        Humbug\set_headers($headers);
    }
}
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    exit(1);
}

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

return ComposerAutoloaderInit8e4de4eb796111b7004a7747b952dd58::getLoader();
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Pickle\Base\Util;
use Pickle\Package\Command\Convert;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ConvertCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('convert')
            ->setDescription('Convert package.xml to new format')
            ->addArgument(
                'path',
                InputArgument::OPTIONAL,
                'Path to the PECL extension root directory (default pwd)',
                getcwd()
            )
            ->addOption(
                'tmp-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'path to a custom temp dir',
                sys_get_temp_dir()
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        Util\TmpDir::set($input->getOption('tmp-dir'));
        $helper = $this->getHelper('package');
        $cb = function (\Pickle\Base\Interfaces\Package $package) use ($helper, $output) {
            $output->writeln('<info>Successfully converted ' . $package->getPrettyName() . '</info>');
            $helper->showInfo($output, $package);
        };
        $convert = Convert::factory($input->getArgument('path'), $cb);
        $convert->process();
        return 0;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Pickle\Base\Interfaces;
use Pickle\Base\Util;
use Pickle\Package\Command\Info;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class InfoCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('info')
            ->setDescription('Display information about a PECL extension')
            ->addArgument(
                'path',
                InputArgument::OPTIONAL,
                'Path to the PECL extension root directory (default pwd)',
                getcwd()
            )
            ->addOption(
                'tmp-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'path to a custom temp dir',
                sys_get_temp_dir()
            )
            ->addOption(
                'version-override',
                null,
                InputOption::VALUE_OPTIONAL,
                'Override detected version (no value - or empty value - to use the version from package.xml)'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        Util\TmpDir::set($input->getOption('tmp-dir'));
        $helper = $this->getHelper('package');

        $cb = function (Interfaces\Package\Info $info) use ($helper, $output) {
            $helper->showInfo($output, $info->getPackage());
            $output->writeln(['', trim($info->getPackage()->getDescription()), '']);
            $output->writeln('<info>Configure options</info>');
            $helper->showOptions($output, $info->getPackage());
        };

        $path = rtrim($input->getArgument('path'), DIRECTORY_SEPARATOR);
        $package = $helper->convey($input, $output, $path);

        $info = Info::factory($package, $cb);
        $info->show();
        return 0;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Exception;
use Pickle\Base\Abstracts\Console\Command\BuildCommand;
use Pickle\Base\Archive\Factory;
use Pickle\Base\Interfaces;
use Pickle\Base\Util;
use Pickle\Package\Command\Release;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ReleaseCommand extends BuildCommand
{
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('release')
            ->setDescription('Package a PECL extension for release')
            ->addOption(
                'binary',
                null,
                InputOption::VALUE_NONE,
                'create binary package'
            )
            ->addOption(
                'pack-logs',
                null,
                InputOption::VALUE_NONE,
                'package build logs'
            )
            ->addOption(
                'tmp-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'path to a custom temp dir',
                sys_get_temp_dir()
            )
        ;

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $this->addOption(
                'binary',
                null,
                InputOption::VALUE_NONE,
                'use binary package'
            );
        }
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (DIRECTORY_SEPARATOR === '\\' || $input->getOption('pack-logs')) {
            Factory::getZipperClassName(); // Be sure we have a way to zip files
        }
        $helper = $this->getHelper('package');
        Util\TmpDir::set($input->getOption('tmp-dir'));

        $cb = function (Interfaces\Package $package) use ($helper, $output) {
            $helper->showInfo($output, $package);
        };
        $path = rtrim($input->getArgument('path'), '/\\');

        /* Getting package unpacked first, then use the path*/
        $package = $this->getHelper('package')->convey($input, $output, $path);

        $release = Release::factory($package->getRootDir(), $cb, $input->getOption('no-convert'), $input->getOption('binary'));

        if ($input->getOption('binary')) {
            [$optionsValue, $force_opts] = $this->buildOptions($package, $input, $output);

            $build = \Pickle\Package\Command\Build::factory($package, $optionsValue);

            try {
                $build->prepare();
                $build->createTempDir($package->getUniqueNameForFs());
                $build->configure($force_opts);
                $build->make();
                $this->saveBuildLogs($input, $build);
            } catch (Exception $e) {
                if ($input->getOption('pack-logs')) {
                    $release->packLog($build);
                } else {
                    $this->saveBuildLogs($input, $build);
                }

                $output->writeln('The following error(s) happened: ' . $e->getMessage());
            }

            $args = [
                'build' => $build,
            ];

            try {
                $release->create($args);
                if ($input->getOption('pack-logs')) {
                    $release->packLog();
                }
            } catch (Exception $e) {
                if ($input->getOption('pack-logs')) {
                    $release->packLog();
                }
                $build->cleanup();
                throw new Exception($e->getMessage());
            }
        } else {
            /* imply --source */
            try {
                $release->create();
                if ($input->getOption('pack-logs')) {
                    $release->packLog();
                }
            } catch (Exception $e) {
                if ($input->getOption('pack-logs')) {
                    $release->packLog();
                }
                throw new Exception($e->getMessage());
            }
        }
        return 0;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Exception;
use Pickle\Base\Abstracts\Console\Command\BuildCommand;
use Pickle\Base\Archive\Factory;
use Pickle\Base\Util;
use Pickle\Engine;
use Pickle\Package\Command\Install;
use Pickle\Package\Util\Windows;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class InstallerCommand extends BuildCommand
{
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('install')
            ->setDescription('Install a php extension')
            ->addOption(
                'dry-run',
                null,
                InputOption::VALUE_NONE,
                'Do not install extension'
            )
            ->addOption(
                'php',
                null,
                InputOption::VALUE_REQUIRED,
                'path to an alternative php (exec)'
            )
            ->addOption(
                'ini',
                null,
                InputOption::VALUE_REQUIRED,
                'path to an alternative php.ini'
            )
            ->addOption(
                'source',
                null,
                InputOption::VALUE_NONE,
                'use source package'
            )
            ->addOption(
                'save-logs',
                null,
                InputOption::VALUE_REQUIRED,
                'path to save the build logs'
            )
            ->addOption(
                'tmp-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'path to a custom temp dir',
                sys_get_temp_dir()
            )
            ->addOption(
                'version-override',
                null,
                InputOption::VALUE_OPTIONAL,
                'Override detected version (no value - or empty value - to use the version from package.xml)'
            )
        ;

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $this->addOption(
                'binary',
                null,
                InputOption::VALUE_NONE,
                'use binary package'
            );
        }
    }

    /**
     * @param string $path
     */
    protected function binaryInstallWindows($path, InputInterface $input, OutputInterface $output)
    {
        Factory::getUnzipperClassName(); // Be sure we have a way to unzip files
        $php = Engine::factory();
        $table = new Table($output);
        $table
            ->setRows([
                ['<info>' . $php->getName() . ' Path</info>', $php->getPath()],
                ['<info>' . $php->getName() . ' Version</info>', $php->getVersion()],
                ['<info>Compiler</info>', $php->getCompiler()],
                ['<info>Architecture</info>', $php->getArchitecture()],
                ['<info>Thread safety</info>', $php->getZts() ? 'yes' : 'no'],
                ['<info>Extension dir</info>', $php->getExtensionDir()],
                ['<info>php.ini</info>', $php->getIniPath()],
            ])
            ->render()
        ;

        $inst = Install::factory($path);
        $progress = new ProgressBar($output, 100);

        $inst->setProgress($progress);
        $inst->setInput($input);
        $inst->setOutput($output);
        $inst->install();

        $deps_handler = new Windows\DependencyLib($php);
        $deps_handler->setProgress($progress);
        $deps_handler->setInput($input);
        $deps_handler->setOutput($output);

        $helper = $this->getHelperSet()->get('question');

        $cb = function ($choices) use ($helper, $input, $output) {
            $question = new ChoiceQuestion(
                'Multiple choices found, please select the appropriate dependency package',
                $choices
            );
            $question->setMultiselect(false);

            return $helper->ask($input, $output, $question);
        };

        foreach ($inst->getExtDllPaths() as $dll) {
            if (!$deps_handler->resolveForBin($dll, $cb)) {
                throw new Exception('Failed to resolve dependencies');
            }
        }
    }

    /*  The most of this needs to be incapsulated into an extra Build class*/
    protected function sourceInstall($package, InputInterface $input, OutputInterface $output, $optionsValue = [], $force_opts = ''): bool
    {
        $helper = $this->getHelperSet()->get('question');

        $build = \Pickle\Package\Command\Build::factory($package, $optionsValue);

        try {
            $build->prepare();
            $build->createTempDir($package->getUniqueNameForFs());
            $build->configure($force_opts);
            $build->make();
            $build->install();

            $this->saveBuildLogs($input, $build);
            $result = true;
        } catch (Exception $e) {
            $this->saveBuildLogs($input, $build);

            $output->writeln('The following error(s) happened: ' . $e->getMessage());
            $prompt = new ConfirmationQuestion('Would you like to read the log?', true);
            if ($helper->ask($input, $output, $prompt)) {
                $output->write($build->getLog());
            }
            $result = false;
        }
        $build->cleanup();

        return $result;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $path = rtrim($input->getArgument('path'), '/\\');
        Util\TmpDir::set($input->getOption('tmp-dir'));

        /* if windows, try bin install by default */
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $sourceRequested = $input->getOption('source');
            if (!$sourceRequested) {
                $this->binaryInstallWindows($path, $input, $output);

                return 0;
            }
        }

        $package = $this->getHelper('package')->convey($input, $output, $path);
        $this->getHelper('package')->showInfo($output, $package);

        [$optionsValue, $force_opts] = $this->buildOptions($package, $input, $output);

        if ($input->getOption('dry-run')) {
            return 0;
        }

        return $this->sourceInstall($package, $input, $output, $optionsValue, $force_opts) ? 0 : 1;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Humbug\SelfUpdate\Updater;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SelfUpdateCommand extends Command
{
    protected const PHAR_NAME = 'pickle.phar';

    protected function configure()
    {
        $this
            ->setName('self-update')
            ->setDescription('Updates pickle.phar to the latest version.')
            ->addOption(
                'unstable',
                null,
                InputOption::VALUE_NONE,
                'Update to a non-stable version'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $updater = new Updater(null, false, Updater::STRATEGY_GITHUB);
        $updater->getStrategy()->setPackageName('friendsofphp/pickle');
        $updater->getStrategy()->setPharName(self::PHAR_NAME);
        $updater->getStrategy()->setCurrentLocalVersion($this->getApplication()->getVersion());

        if ($input->getOption('unstable')) {
            $updater->getStrategy()->setStability('unstable');
        }

        if ($updater->update() === false) {
            $output->writeln('<info>Already up-to-date.</info>');
        } else {
            $output->writeln('<info>' . $updater->getLocalPharFileBasename() . ' has been updated!</info>');
        }
        return 0;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Command;

use Pickle\Base\Interfaces;
use Pickle\Base\Util;
use Pickle\Package\Command\Validate;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ValidateCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('validate')
            ->setDescription('Validate a PECL extension')
            ->addArgument(
                'path',
                InputArgument::OPTIONAL,
                'Path to the PECL extension root directory (default pwd)',
                getcwd()
            )
            ->addOption(
                'tmp-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'path to a custom temp dir',
                sys_get_temp_dir()
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $path = rtrim($input->getArgument('path'), '/\\');
        $helper = $this->getHelper('package');
        Util\TmpDir::set($input->getOption('tmp-dir'));

        $cb = function (Interfaces\Package $package) use ($helper, $output) {
            $helper->showInfo($output, $package);
            $output->writeln(trim($package->getDescription()));
        };

        $validate = Validate::factory($path, $cb);
        $validate->process();
        return 0;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console;

use Exception;
use Phar;
use Symfony\Component\Console\Application as BaseApplication;

class Application extends BaseApplication
{
    public const NAME = 'pickle';

    public const VERSION = 'v0.7.11';

    public function __construct($name = null, $version = null)
    {
        self::checkExtensions();

        parent::__construct($name ?: static::NAME, $version ?: (static::VERSION === '@' . 'pickle-version@' ? 'source' : static::VERSION));
    }

    protected function getDefaultHelperSet()
    {
        $helperSet = parent::getDefaultHelperSet();

        $helperSet->set(new Helper\PackageHelper());

        return $helperSet;
    }

    protected function getDefaultCommands()
    {
        $commands = parent::getDefaultCommands();

        $commands[] = new Command\ValidateCommand();
        $commands[] = new Command\ConvertCommand();
        $commands[] = new Command\ReleaseCommand();
        $commands[] = new Command\InstallerCommand();
        $commands[] = new Command\InfoCommand();

        if (Phar::running() !== '') {
            $commands[] = new Command\SelfUpdateCommand();
        }

        return $commands;
    }

    private static function checkExtensions()
    {
        $required_exts = [
            'zlib',
            'mbstring',
            'simplexml',
            'json',
            'dom',
            'openssl',
            'phar',
        ];

        foreach ($required_exts as $ext) {
            if (!extension_loaded($ext)) {
                throw new Exception("Extension '{$ext}' required but not loaded, full required list: " . implode(', ', $required_exts));
            }
        }
    }
}
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Console\Helper;

use Composer\IO\ConsoleIO;
use Pickle\Base\Interfaces;
use Pickle\Package\Convey;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class PackageHelper extends Helper
{
    /**
     * Returns the canonical name of this helper.
     *
     * @return string The canonical name
     *
     * @api
     */
    public function getName()
    {
        return 'package';
    }

    public function showInfo(OutputInterface $output, Interfaces\Package $package)
    {
        $table = new Table($output);
        $stability = $package->getStability();
        $table
            ->setRows([
                ['<info>Package name</info>', $package->getPrettyName()],
                ['<info>Package version (current release)</info>', str_replace("-{$stability}", '', $package->getPrettyVersion())],
                ['<info>Package status</info>', $stability],
            ])
            ->render()
        ;
    }

    public function showOptions(OutputInterface $output, Interfaces\Package $package)
    {
        $table = new Table($output);
        $table->setHeaders(['Type', 'Description', 'Default']);

        foreach ($package->getConfigureOptions() as $option) {
            $default = $option->default;

            if ($option->type === 'enable') {
                $option->type = '<fg=yellow>' . $option->type . '</fg=yellow>';
                $default = $default ? '<fg=green>yes</fg=green>' : '<fg=red>no</fg=red>';
            }

            $table->addRow([
                $option->type,
                wordwrap($option->prompt, 40, PHP_EOL),
                $default,
            ]);
        }

        $table->render();
    }

    /**
     * @param $path
     *
     * @return \Pickle\Base\Interfaces\Package
     */
    public function convey(InputInterface $input, OutputInterface $output, $path, $target = null)
    {
        $helperSet = $this->getHelperSet();
        $io = new ConsoleIO($input, $output, ($helperSet ?: new HelperSet()));

        $no_convert = $input->hasOption('no-convert') ? $input->getOption('no-convert') : false;
        if ($input->hasOption('version-override')) {
            $versionOverride = $input->getOption('version-override');
            if ($versionOverride === null && $input->hasParameterOption('--version-override', true)) {
                $versionOverride = '';
            }
        } else {
            $versionOverride = null;
        }

        return (new Convey($path, $io))->deliver($target, $no_convert, $versionOverride);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle;

use Composer;

class Config extends Composer\Config
{
    public const DEFAULT_BASE_DIRNAME = '.pickle';

    public function __construct($useEnvironment = true, $baseDir = null)
    {
        if ($useEnvironment === true) {
            $baseDir = $baseDir ?: (getenv('PICKLE_BASE_DIR') ?: null);
        }

        $baseDir = $baseDir ?: (getenv('HOME') ?: sys_get_temp_dir());
        $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . self::DEFAULT_BASE_DIRNAME;

        parent::__construct($useEnvironment, $baseDir);
    }
}
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle;

use Exception;
use Pickle\Package\PHP;

class Package
{
    protected static $instance;

    public static function factory($name, $version, $prettyVersion, $force = false)
    {
        if (self::deliverFresh($force)) {
            $engine = Engine::factory();

            switch ($engine->getName()) {
                case 'php':
                    self::$instance = new PHP\Package($name, $version, $prettyVersion);
                    break;

                default:
                    throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
            }
        }

        return self::$instance;
    }

    protected static function deliverFresh($force)
    {
        if ($force && self::$instance || self::$instance === null) {
            self::$instance = null;

            return true;
        }

        return false;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Downloader;

use Composer\Package\PackageInterface as Package;

class PECLDownloader extends TGZDownloader
{
    protected function getFileName(Package $package, $path)
    {
        return parent::getFileName($package, $path) . '.tgz';
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Downloader;

use Composer\Downloader\ArchiveDownloader;
use PharData;

class TGZDownloader extends ArchiveDownloader
{
    protected function extract($file, $path)
    {
        $archive = new PharData($file);
        $archive->decompress()->extractTo($path, null, true);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts\Console\Command;

use Exception;
use Pickle\Base\Interfaces\Package;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;

abstract class BuildCommand extends Command
{
    protected function configure()
    {
        $this
            ->addArgument(
                'path',
                InputArgument::OPTIONAL,
                'Path to the PECL extension root directory (default pwd), archive or extension name',
                getcwd()
            )
            ->addOption(
                'no-convert',
                null,
                InputOption::VALUE_NONE,
                'Disable package conversion'
            )
            ->addOption(
                'defaults',
                null,
                InputOption::VALUE_NONE,
                'Use defaults configure options values'
            )->addOption(
                'source',
                null,
                InputOption::VALUE_NONE,
                'use source package'
            )->addOption(
                'with-configure-options',
                null,
                InputOption::VALUE_REQUIRED,
                'path to the additional configure options'
            )->addOption(
                'save-logs',
                null,
                InputOption::VALUE_REQUIRED,
                'path to save the build logs'
            );
    }

    protected function saveBuildLogs(InputInterface $input, $build)
    {
        $save_log_path = $input->getOption('save-logs');
        if ($save_log_path) {
            $build->saveLog($save_log_path);
        }
    }

    protected function buildOptions(Package $package, InputInterface $input, OutputInterface $output)
    {
        $helper = $this->getHelperSet()->get('question');

        $configureOptions = $input->getOption('with-configure-options');

        if ($configureOptions) {
            if (!file_exists($configureOptions) || is_dir($configureOptions) || !is_readable($configureOptions)) {
                throw new Exception("File '{$configureOptions}' is unusable");
            }

            if (DIRECTORY_SEPARATOR !== '\\' && preg_match('_^/dev/fd/\d+$_', $configureOptions)) {
                // https://bugs.php.net/bug.php?id=53465
                $configureOptions = str_replace('/dev/', 'php://', $configureOptions);
            }
            $configureOptions = preg_replace(',\\s+,', ' ', file_get_contents($configureOptions));

            return [null, $configureOptions];
        }

        $options = $package->getConfigureOptions();
        $optionsValue = [];

        foreach ($options as $name => $opt) {
            /* enable/with-<extname> */
            if ($name == $package->getName() || str_replace('-', '_', $name) == $package->getName()) {
                $optionsValue[$name] = (object) [
                    'type' => $opt->type,
                    'input' => true,
                ];

                continue;
            }

            if ($input->getOption('defaults')) {
                $value = $opt->default;
            } else {
                if ($opt->type == 'enable') {
                    $prompt = new ConfirmationQuestion($opt->prompt . ' (default: ' . ($opt->default ? 'yes' : 'no') . '): ', $opt->default);
                } else {
                    $prompt = new Question($opt->prompt . ' (default: ' . ($opt->default ?: '') . '): ', $opt->default);
                }

                $value = $helper->ask($input, $output, $prompt);
            }

            $optionsValue[$name] = (object) [
                'type' => $opt->type,
                'input' => $value,
            ];
        }

        return [$optionsValue, null];
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts;

use Composer\Package\CompletePackage;
use Composer\Package\Version\VersionParser;
use Pickle\Package\Util\Header;

class Package extends CompletePackage
{
    public function getSimpleName()
    {
        $full_name = $this->getName();
        $m = null;
        if (!preg_match(',(.+)/(.+),', $full_name, $m)) {
            return $full_name;
        }

        return $m[2];
    }

    public function getVendorName()
    {
        $full_name = $this->getName();
        $m = null;
        if (!preg_match(',(.+)/(.+),', $full_name, $m)) {
            return;
        }

        return $m[1];
    }

    public function getUniqueNameForFs()
    {
        return sha1($this->getUniqueName());
    }

    /* This might be not common for other extensions to
        other engines. Lets see if this is needed to be
        moved into the Interface so each engines package
        is forced to implement it on its own. But so far ... */
    public function updateVersion($version = null)
    {
        if ($version === '') {
            return;
        }
        /* Be sure package root is set before! */
        if ($version === null) {
            $version = new Header\Version($this);
        }
        $parser = new VersionParser();

        $this->version = $parser->normalize($version);
        $this->prettyVersion = (string) $version;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts\Engine;

use Exception;

class Ini
{
    public const PICKLE_HEADER = ';Pickle installed extension, do not edit this line and below';

    public const PICKLE_FOOTER = ';Pickle installed extension, do not edit this line and above';

    protected $engine;

    protected $path;

    protected $raw;

    protected $pickleHeaderStartPos = -1;

    protected $pickleHeaderEndPos = -1;

    protected $pickleFooterStartPos = -1;

    protected $pickleFooterEndPos = -1;

    public function __construct(\Pickle\Base\Interfaces\Engine $php)
    {
        $this->engine = $php;
        $this->path = $php->getIniPath();

        $this->raw = @file_get_contents($this->path);
        if ($this->raw === false) {
            throw new Exception('Cannot read php.ini');
        }
    }

    public function getengine()
    {
        return $this->engine;
    }

    protected function getPickleSection()
    {
        return substr($this->raw, $this->pickleHeaderEndPos, $this->pickleFooterStartPos - $this->pickleHeaderEndPos);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts;

abstract class Engine
{
    public function getArchitecture()
    {
        /* Just a basic method, a concrete Engine implementation
        might need to override this. */
        $is_64_bit = 8 == PHP_INT_SIZE;

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            if ($is_64_bit) {
                return 'x64';
            }
            return 'x86';
        }
        if ($is_64_bit) {
            return 'x86_64';
        }
        return 'i386';
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts\Package\Convey;

use Composer\IO\ConsoleIO;

abstract class Command
{
    protected $path;

    protected $version;

    protected $prettyVersion;

    protected $name;

    protected $stability;

    protected $url;

    protected $io;

    public function __construct($path, ConsoleIO $io)
    {
        $this->path = $path;
        $this->io = $io;

        $this->prepare();
    }

    abstract public function execute($target, $no_convert, $versionOverride);

    public function getPath()
    {
        return $this->path;
    }

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

    public function getPrettyVersion()
    {
        return $this->prettyVersion;
    }

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

    public function getStability()
    {
        return $this->stability;
    }

    public function getUrl()
    {
        return $this->url;
    }

    abstract protected function prepare();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Abstracts\Package;

use Exception;
use Pickle\Base\Archive;
use Pickle\Base\Interfaces;
use Pickle\Base\Interfaces\Package;
use Pickle\Base\Util\FileOps;

abstract class Build
{
    use FileOps;

    protected $pkg;

    protected $options;

    protected $log = [];

    protected $cwdBack;

    public function __construct(Package $pkg, $options = null)
    {
        $this->pkg = $pkg;
        $this->options = $options;
        $this->cwdBack = getcwd();
    }

    /**
     * @param int $level
     * @param string $msg
     */
    public function log($level, $msg, $hint = '')
    {
        $this->log[] = [
            'level' => $level,
            'msg' => $msg,
            'hint' => $hint,
        ];
    }

    public function getLog($hint = null)
    {
        $ret = [];

        foreach ($this->log as $item) {
            if (isset($hint) && $hint !== $item['hint']) {
                continue;
            }
            $tmp = explode("\n", $item['msg']);
            foreach ($tmp as $ln) {
                $ret[] = $item['level'] . ': ' . $ln;
            }
        }

        return implode("\n", $ret);
    }

    public function saveLog($path)
    {
        $logs = [];
        $def_fl = null;

        $this->prepareSaveLog($path, $def_fl);

        foreach ($this->log as $item) {
            $fname = $this->getLogFilename($path, $item, $def_fl, $logs);

            if (file_put_contents($fname, "{$item['msg']}\n", FILE_APPEND) != strlen($item['msg']) + 1) {
                throw new Exception("Couldn't write contents to '{$fname}'");
            }
        }
    }

    /* zip is default */
    public function packLog($path)
    {
        $zipperClass = Archive\Factory::getZipperClassName();
        $zip = new $zipperClass($path, Interfaces\Archive\Zipper::FLAG_CREATE_OVERWRITE);
        /** @var \Pickle\Base\Interfaces\Archive\Zipper $zip */
        $no_hint_logs = '';
        foreach ($this->log as $item) {
            $msg = $this->fixEol($item['msg']);
            if ((isset($item['hint']) && !empty($item['hint']))) {
                $zip->addFromString("{$item['hint']}.log", $msg);
            } else {
                $no_hint_logs = "{$no_hint_logs}\n\n{$msg}";
            }
        }
        if ($no_hint_logs) {
            $zip->addFromString('build.log', $this->fixEol($item['msg']));
        }
    }

    public function getPackage()
    {
        return $this->pkg;
    }

    protected function prepareSaveLog($path, &$def_fl)
    {
        if ($path && !is_dir($path)) {
            if (!mkdir($path)) {
                throw new EXception("Location '{$path}' could not be created, unable to save build logs");
            }
        }

        $def_fl = $path . DIRECTORY_SEPARATOR . 'build.log';
        if (file_exists($def_fl)) {
            unlink($def_fl);
        }
    }

    protected function getLogFilename($path, $log_item, $def_fl, array &$logs)
    {
        $is_hint = (isset($log_item['hint']) && !empty($log_item['hint']));
        $fname = $is_hint ? $path . DIRECTORY_SEPARATOR . "{$log_item['hint']}.log" : $def_fl;

        if (!in_array($fname, $logs)) {
            if (file_exists($fname)) {
                unlink($fname);
            }
            $logs[] = $fname;
        }

        return $fname;
    }

    protected function fixEol($s)
    {
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $ret = preg_replace(",(?!\r)\n,", "\r\n", $s);
        } else {
            $ret = $s;
        }

        return $ret;
    }

    /**
     * @param string $command
     *
     * @throws Exception
     *
     * @return bool
     */
    protected function runCommand($command)
    {
        $hint = basename(strtok($command, " \n"));

        $this->log(1, $command, $hint);
        $pp = popen("{$command} 2>&1", 'r');
        if (!$pp) {
            throw new Exception(
                'Failed to run the following command: ' . $command
            );
        }

        $out = [];
        while ($line = fgets($pp, 1024)) {
            $out[] = rtrim($line);
        }
        $this->log(2, implode("\n", $out), $hint);

        $exitCode = is_resource($pp) ? pclose($pp) : -1;

        return $exitCode === 0;
    }

    protected function appendPkgConfigureOptions(&$configureOptions)
    {
        $opt = $this->pkg->getConfigureOptions();
        if (isset($opt[$this->pkg->getName()])) {
            $extEnableOption = $opt[$this->pkg->getName()];
            if ($extEnableOption->type == 'enable') {
                $confOption = '--enable-' . $this->pkg->getName() . '=shared';
            } else {
                $confOption = '--with-' . $this->pkg->getName() . '=shared';
            }
            $configureOptions = $confOption . ' ' . $configureOptions;
        } else {
            $name = str_replace('_', '-', $this->pkg->getName());
            if (isset($opt[$name])) {
                $extEnableOption = $opt[$name];
                if ($extEnableOption->type == 'enable') {
                    $confOption = '--enable-' . $name . '=shared';
                } else {
                    $confOption = '--with-' . $name . '=shared';
                }
                $configureOptions = $confOption . ' ' . $configureOptions;
            }
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use Pickle\Base\Interfaces;
use RuntimeException;
use ZipArchive;

class PHPZipper extends PHP implements Interfaces\Archive\Zipper
{
    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::__construct()
     */
    public function __construct(string $path, int $flags)
    {
        parent::__construct();
        switch ($flags) {
            case self::FLAG_OPEN:
                $this->open($path);
                break;
            case self::FLAG_CREATE:
                $this->create($path, false);
                break;
            case self::FLAG_CREATE_OVERWRITE:
                $this->create($path, true);
                break;
            default:
                throw new RuntimeException('Invalid value of $flags in ' . __METHOD__);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::__destruct()
     */
    public function __destruct()
    {
        parent::__destruct();
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::addFromString($localname, $contents)
     */
    public function addFromString(string $localname, string $contents): void
    {
        $localname = ltrim(str_replace(DIRECTORY_SEPARATOR, '/', $localname), '/');
        if ($this->zipArchive->addFromString($localname, $contents) !== true) {
            $error = 'Failed to add a file to the ZIP archive starting from its contents';
            $details = (string) $this->zipArchive->getStatusString();
            if ($details !== '') {
                $error .= ": {$details}";
            }
            throw new RuntimeException($error);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::addFileWithoutPath()
     */
    public function addFileWithoutPath(string $path): void
    {
        if (!is_file($path)) {
            throw new RuntimeException("Failed to find the file {$path}");
        }
        if (!is_readable($path)) {
            throw new RuntimeException("The file {$path} is not readable");
        }
        if ($this->zipArchive->addFile($path, basename($path)) !== true) {
            $error = "Failed to add the file {$path} to the ZIP archive";
            $details = (string) $this->zipArchive->getStatusString();
            if ($details !== '') {
                $error .= ": {$details}";
            }
            throw new RuntimeException($error);
        }
    }

    /**
     * @throws RuntimeException
     */
    private function create(string $path, bool $overwrite): void
    {
        $flags = ZipArchive::CREATE;
        if ($overwrite === false) {
            if (file_exists($path)) {
                throw new RuntimeException("The ZIP archive {$path} already exists");
            }
        } else {
            $flags |= ZipArchive::OVERWRITE;
        }
        if ($this->zipArchive->open($path, $flags) !== true) {
            $error = "Failed to create the ZIP archive {$path}";
            $details = (string) $this->zipArchive->getStatusString();
            if ($details !== '') {
                $error .= ": {$details}";
            }
            throw new RuntimeException($error);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use Pickle\Base\Interfaces;
use Pickle\Base\Util\FileOps;
use RuntimeException;
use Throwable;

class InfozipZipper extends Infozip implements Interfaces\Archive\Zipper
{
    use FileOps;

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::__construct()
     */
    public function __construct(string $path, int $flags)
    {
        parent::__construct($path);
        switch ($flags) {
            case self::FLAG_OPEN:
                $this->checkExisting();
                break;
            case self::FLAG_CREATE:
                $this->create(false);
                break;
            case self::FLAG_CREATE_OVERWRITE:
                $this->create(true);
                break;
            default:
                throw new RuntimeException('Invalid value of $flags in ' . __METHOD__);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::__destruct()
     */
    public function __destruct()
    {
        $this->cleanup();
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::addFromString($localname, $contents)
     */
    public function addFromString(string $localname, string $contents): void
    {
        $localname = ltrim(str_replace(DIRECTORY_SEPARATOR, '/', $localname), '/');
        $this->createTempDir();
        $tempDir = $this->getTempDir();
        $dirname = dirname($localname);
        if ($dirname !== '' && $dirname !== '.') {
            if (mkdir($tempDir . '/' . $dirname, 0777, true) !== true) {
                throw new RuntimeException('Failed to create a temporary directory');
            }
        }
        if (file_put_contents($tempDir . '/' . $localname, $contents) === false) {
            throw new RuntimeException('Failed to write a temporary file');
        }
        $originalCWD = getcwd();
        if (chdir($tempDir) !== true) {
            throw new RuntimeException("Failed to enter directory {$tempDir}");
        }
        try {
            $this->run('zip', [
                escapeshellarg($this->path),
                escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $localname)),
            ]);
        } finally {
            chdir($originalCWD);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Zipper::addFileWithoutPath()
     */
    public function addFileWithoutPath(string $path): void
    {
        $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
        if (!is_file($path)) {
            throw new RuntimeException("Failed to find the file {$path}");
        }
        if (!is_readable($path)) {
            throw new RuntimeException("The file {$path} is not readable");
        }
        $this->run('zip', [
            '-j',
            escapeshellarg($this->path),
            escapeshellarg($path),
        ]);
    }

    private function create(bool $overwrite): void
    {
        if (file_exists($this->path)) {
            if ($overwrite === false) {
                throw new RuntimeException("The ZIP archive {$this->path} already exists");
            }
            if (unlink($this->path) === false) {
                throw new RuntimeException("Failed to overwrite the ZIP archive {$this->path}");
            }
        }
        $this->run([
            '-j1',
            escapeshellarg($this->path),
            escapeshellarg(__FILE__),
        ]);
        try {
            $this->run([
                '-d',
                escapeshellarg($this->path),
                escapeshellarg(basename(__FILE__)),
            ]);
        } catch (Throwable $x) {
            unlink($this->path);
            throw $x;
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use RuntimeException;

class Factory
{
    /**
     * Get the fully-qualified name of a class implementing the Zipper interface.
     *
     * @throws RuntimeException if no zipper is available
     */
    public static function getZipperClassName(): string
    {
        if (static::isInfozipZipAvailable()) {
            return InfozipZipper::class;
        }
        if (static::isZipExtensionAvailable()) {
            return PHPZipper::class;
        }
        throw new RuntimeException(self::buildErrorMessage('zip'));
    }

    /**
     * Get the fully-qualified name of a class implementing the Unzipper interface.
     *
     * @throws RuntimeException if no zipper is available
     */
    public static function getUnzipperClassName(): string
    {
        if (static::isInfozipUnzipAvailable()) {
            return InfozipUnzipper::class;
        }
        if (static::isZipExtensionAvailable()) {
            return PHPUnzipper::class;
        }
        throw new RuntimeException(self::buildErrorMessage('unzip'));
    }

    public static function isZipExtensionAvailable(): bool
    {
        return extension_loaded('zip');
    }

    public static function isInfozipZipAvailable(): bool
    {
        $output = [];
        $rc = -1;
        exec('zip -v 2>&1', $output, $rc);
        return $rc === 0 && preg_match('/info.?zip/i', $output[0] ?? '');
    }

    public static function isInfozipUnzipAvailable(): bool
    {
        $output = [];
        $rc = -1;
        exec('unzip -v 2>&1', $output, $rc);
        return $rc === 0 && preg_match('/info.?zip/i', $output[0] ?? '');
    }

    private static function buildErrorMessage(string $command): string
    {
        $message = <<<EOT
No support for ZIP files has been detected.
You have two options:
1. enable the zip PHP extension
2. install the {$command} system command

EOT
        ;
        if (DIRECTORY_SEPARATOR === '\\') {
            $message .= "You can find the Windows {$command}.exe program at the following address:\nftp://ftp.info-zip.org/pub/infozip/win32/";
        } else {
            $message .= <<<EOT
For example, to install the {$command} command you may try to run
apt-get install {$command}
or
apk add {$command}
EOT
            ;
        }

        return $message;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use RuntimeException;
use ZipArchive;

abstract class PHP
{
    /**
     * @var ZipArchive
     */
    protected $zipArchive;

    protected function __construct()
    {
        $this->zipArchive = new ZipArchive();
    }

    public function __destruct()
    {
        set_error_handler(static function () {
        }, -1);
        $this->zipArchive->close();
        restore_error_handler();
    }

    /**
     * @throws RuntimeException
     */
    protected function open(string $path): void
    {
        if (!is_file($path)) {
            throw new RuntimeException("Unable to find the file {$path}");
        }
        if (!is_readable($path)) {
            throw new RuntimeException("The ZIP archive {$path} is not readable");
        }
        if ($this->zipArchive->open($path) !== true) {
            $error = "Failed to open the ZIP archive {$path}";
            $details = (string) $this->zipArchive->getStatusString();
            if ($details !== '') {
                $error .= ": {$details}";
            }
            throw new RuntimeException($error);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use RuntimeException;

abstract class Infozip
{
    private const FILE_SIGNATURE = "PK\x03\x04";

    /**
     * The path (already normalized for the current OS).
     *
     * @var string
     */
    protected $path;

    protected function __construct(string $path)
    {
        $this->path = str_replace('/', DIRECTORY_SEPARATOR, $path);
    }

    protected function checkExisting(): void
    {
        if (!is_file($this->path)) {
            throw new RuntimeException("Unable to find the file {$this->path}");
        }
        if (!is_readable($this->path)) {
            throw new RuntimeException("The ZIP archive {$this->path} is not readable");
        }
        $fd = fopen($this->path, 'rb');
        if (!$fd) {
            throw new RuntimeException("Failed to open the ZIP archive {$this->path}");
        }
        try {
            $signature = fread($fd, strlen(self::FILE_SIGNATURE));
            if ($signature !== self::FILE_SIGNATURE) {
                throw new RuntimeException("The file {$this->path} doesn't seems a valid ZIP archive");
            }
        } finally {
            fclose($fd);
        }
    }

    protected function run(string $command, array $args)
    {
        $commandLine = $command . ' ' . implode(' ', $args) . ' 2>&1';
        $output = [];
        $rc = -1;
        exec($commandLine, $output, $rc);
        if ($rc !== 0) {
            throw new RuntimeException(trim(implode("\n", $output)) ?: "{$command} command failed");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use Pickle\Base\Interfaces;

class PHPUnzipper extends PHP implements Interfaces\Archive\Unzipper
{
    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::__construct()
     */
    public function __construct(string $path)
    {
        parent::__construct();
        $this->open($path);
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::__destruct()
     */
    public function __destruct()
    {
        parent::__destruct();
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::extractTo()
     */
    public function extractTo(string $path): void
    {
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Archive;

use Pickle\Base\Interfaces;
use RuntimeException;

class InfozipUnzipper extends Infozip implements Interfaces\Archive\Unzipper
{
    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::__construct()
     */
    public function __construct(string $path)
    {
        parent::__construct($path);
        $this->checkExisting();
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::__destruct()
     */
    public function __destruct()
    {
    }

    /**
     * {@inheritDoc}
     *
     * @see \Pickle\Base\Interfaces\Archive\Unzipper::extractTo()
     */
    public function extractTo(string $path): void
    {
        if (!is_dir($path)) {
            if (mkdir($path, 0777, true) !== true) {
                throw new RuntimeException("Failed to create directory {$path}");
            }
        }
        $originalCWD = getcwd();
        if (chdir($path) !== true) {
            throw new RuntimeException("Failed to enter directory {$path}");
        }
        try {
            $this->run('unzip', [
                escapeshellarg($this->path),
            ]);
        } finally {
            chdir($originalCWD);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Util;

use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

trait FileOps
{
    protected $tempDir;

    public function createTempDir($name = '')
    {
        $tmp = TmpDir::get();
        if (!$name) {
            $name = md5(uniqid());
        }
        $tempDir = $tmp . '/pickle-' . $name;

        if (is_dir($tempDir)) {
            $this->cleanup();
        }
        if (!is_dir($tempDir)) {
            mkdir($tempDir);
        }
        $this->tempDir = $tempDir;
    }

    public function cleanup()
    {
        if (is_dir($this->tempDir)) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator(
                    $this->tempDir,
                    FilesystemIterator::SKIP_DOTS
                ),
                RecursiveIteratorIterator::CHILD_FIRST
            );
            foreach ($iterator as $path) {
                if ($path->isDir()) {
                    rmdir($path->getPathname());
                } else {
                    unlink($path->getPathname());
                }
            }
            rmdir($this->tempDir);
        }
    }

    public function getTempDir()
    {
        return $this->tempDir;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Util;

use InvalidArgumentException;
use Pickle\Base\Interfaces;
use SplFileInfo;

class GitIgnore
{
    protected $excluded = [];

    public function __construct(Interfaces\Package $package)
    {
        $dir = $package->getSourceDir();
        $path = $package->getSourceDir() . '/.gitignore';
        $this->excluded = glob("{$dir}/.git/*");

        $this->excluded = [
            "{$dir}/.git/", "{$dir}/.gitignore", "{$dir}/.gitmodules",
        ];
        if (is_file($path) === false) {
            throw new InvalidArgumentException('File not found: ' . $path);
        }

        foreach (file($path) as $line) {
            $line = trim($line);

            // empty line or comment
            if ($line === '' || $line[0] === '#') {
                continue;
            }

            // negated glob
            if ($line[0] === '!') {
                $line = substr($line, 1);
                $files = array_diff(glob("{$dir}/*"), glob("{$dir}/{$line}"));
            // normal glob
            } else {
                $files = [];

                if (substr($line, -1) !== '/') {
                    $files = glob("{$dir}/{$line}");

                    $line .= '/';
                }

                $files = array_merge(glob("{$dir}/{$line}*"), $files);
            }

            $this->excluded = array_merge($this->excluded, $files);
        }
    }

    public function __invoke(SplFileInfo $file)
    {
        return $this->isExcluded($file) === false;
    }

    public function isExcluded(SplFileInfo $file)
    {
        foreach ($this->excluded as $path) {
            if (!strncmp($file, $path, strlen($path))) {
                return true;
            }
        }

        return false;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Util;

use Exception;

/* This is a completely static class to manage a global temporary dir.
   The temporary dir has to be setup from the --tmp-dir option in the
   corresponding command. */
class TmpDir
{
    public static $tmpDir;

    public static function get()
    {
        if (!self::$tmpDir) {
            self::$tmpDir = sys_get_temp_dir();
        }

        return self::$tmpDir;
    }

    public static function set($tmpDir, $create = false)
    {
        if (!$create) {
            if (!is_dir($tmpDir)) {
                throw new Exception("Directory '{$tmpDir}' does not exist");
            }
        } else {
            if (!mkdir($tmpDir)) {
                throw new Exception("Could not create temporary dir at '{$tmpDir}'");
            }
        }

        self::$tmpDir = $tmpDir;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

declare(strict_types=1);

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Pecl;

class WebsiteFactory
{
    /**
     * @var Website|null
     */
    private static $website;

    /**
     * Get the PECL website configuration.
     */
    public static function getWebsite(): Website
    {
        if (self::$website === null) {
            if (static::isTestServer()) {
                self::$website = new Website('http://127.0.0.1:' . Website::TEST_PORT . '/index.php', true);
            } else {
                $unsafe = static::allowUnsafeConnections();
                self::$website = new Website($unsafe ? 'http://pecl.php.net' : 'https://pecl.php.net', $unsafe);
            }
        }

        return self::$website;
    }

    public static function isTestServer(): bool
    {
        $v = strtolower(getenv('PICKLE_PECL_TESTSERVER') ?: '');

        return in_array($v, ['1', 'true', 'yes'], true);
    }

    protected static function allowUnsafeConnections(): bool
    {
        if (static::isTestServer()) {
            return true;
        }
        $v = strtolower(getenv('PICKLE_PECL_UNSAFE') ?: '');

        return in_array($v, ['1', 'true', 'yes', 'y', 'ok'], true);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

declare(strict_types=1);

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Pecl;

class Website
{
    /**
     * The port to be used for the test web server.
     *
     * @var int
     */
    public const TEST_PORT = 50123;

    /**
     * The base URL of the PECL website to be used.
     *
     * @var string
     *
     * @example 'https://pecl.php.net'
     * @example 'https://127.0.0.1:50123'
     */
    private $baseUrl;

    /**
     * Should we allow unsafe connections to the PECL website?
     *
     * @var bool
     */
    private $unsafe;

    public function __construct(string $baseUrl, bool $unsafe)
    {
        $this->baseUrl = $baseUrl;
        $this->unsafe = $unsafe;
    }

    /**
     * Get the base URL of the PECL website to be used.
     *
     * @example 'https://pecl.php.net'
     * @example 'https://127.0.0.1:50123'
     */
    public function getBaseUrl(): string
    {
        return $this->baseUrl;
    }

    /**
     * Should we allow unsafe connections to the PECL website?
     */
    public function isUnsafe(): bool
    {
        return $this->unsafe;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Archive;

use RuntimeException;

interface Unzipper
{
    /**
     * Initialize the instance, with the path of an existing zip file.
     *
     * @throws RuntimeException in case of errors
     */
    public function __construct(string $path);

    /**
     * Close the archive.
     */
    public function __destruct();

    /**
     * Extract the archive contents to a specific directory.
     *
     * @param string $path it will created if it does not exist
     *
     * @throws RuntimeException in case of errors
     */
    public function extractTo(string $path): void;
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Archive;

use RuntimeException;

interface Zipper
{
    /**
     * Instance creation flag: open an existing archive.
     *
     * @var int
     */
    public const FLAG_OPEN = 1;

    /**
     * Instance creation flag: create a new archive.
     *
     * @var int
     */
    public const FLAG_CREATE = 2;

    /**
     * Instance creation flag: create a new archive (overwriting an existing one if it already exists).
     *
     * @var int
     */
    public const FLAG_CREATE_OVERWRITE = 3;

    /**
     * Open an existing archive, or create a new one.
     *
     * @param string $path the path of the archive to be open/created
     *
     * @throws RuntimeException in case of errors
     */
    public function __construct(string $path, int $flags);

    /**
     * Close the archive.
     */
    public function __destruct();

    /**
     * Add a file the archive, without saving the directory names, only the file name.
     *
     * @param string $path the path to the file to be saved
     */
    public function addFileWithoutPath(string $path): void;

    /**
     * Add a file the archive using its contents.
     *
     * @throws RuntimeException in case of errors
     */
    public function addFromString(string $localname, string $contents): void;
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces;

use Composer\Package\PackageInterface as ComposerPackageInterface;

interface Package extends ComposerPackageInterface
{
    public function setRootDir($path);

    public function getRootDir();

    public function getSourceDir();

    public function setStability($stability);

    public function getConfigureOptions();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Engine;

interface Ini
{
    public function __construct(\Pickle\Base\Interfaces\Engine $php);

    public function updatePickleSection(array $dlls_add, array $dlls_del = []);

    public function getEngine();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces;

interface Engine
{
    public function __construct($phpCli = PHP_BINARY);

    public function getName();

    public function getArchitecture();

    public function getCompiler();

    public function getPath();

    public function getVersion();

    public function getMajorVersion();

    public function getMinorVersion();

    public function getReleaseVersion();

    public function getZts();

    public function getExtensionDir();

    public function getIniPath();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package\Convey;

use Pickle\Base\Interfaces;

interface DefaultExecutor
{
    public function __construct(Interfaces\Package\Convey\Command $command);

    public function execute($target, $no_convert, $versionOverride);
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package\Convey;

use Composer\IO\ConsoleIO;

interface Command
{
    public function __construct($path, ConsoleIO $io);

    public function execute($target, $no_convert, $versionOverride);

    public function getType();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package;

interface Validate
{
    public function __construct($path, $cb = null);

    public function process();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package;

use Pickle\Base\Interfaces;

interface Info
{
    public function __construct(Interfaces\Package $package, $cb = null);

    public function show();

    public function getPackage();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package;

use Pickle\Base\Interfaces\Package;

interface Build
{
    public function __construct(Package $pkg, $options = null);

    public function prepare();

    public function configure($opts = null);

    public function make();

    public function install();

    public function getInfo();
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Base\Interfaces\Package;

interface Release
{
    public function __construct($path, $cb = null, $noConvert = false);

    public function create(array $args = []);
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Engine;

use Exception;
use Pickle\Base\Interfaces;

class Ini
{
    protected static $instance;

    public static function factory(?Interfaces\Engine $engine = null)
    {
        if (self::$instance === null) {
            $engine = $engine === null ? \Pickle\Engine::factory() : $engine;

            switch ($engine->getName()) {
                case 'php':
                    self::$instance = new PHP\Ini($engine);
                    break;

                default:
                    throw new Exception("Unsupported engine '{$engine->getName()}'");
            }
        }

        return self::$instance;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Engine;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;

class PHP extends Abstracts\Engine implements Interfaces\Engine
{
    private $phpCliEscaped;

    private $phpCli;

    private $phpize;

    private $version;

    private $major;

    private $minor;

    private $release;

    private $extra;

    private $compiler;

    private $architecture;

    private $zts;

    private $debug;

    private $iniPath;

    private $extensionDir;

    private $hasSdk;

    public function __construct($phpCli = PHP_BINARY)
    {
        if (!(is_file($phpCli) && is_executable($phpCli))) {
            throw new Exception("Invalid php executable: {$phpCli}");
        }
        $this->phpCliEscaped = escapeshellcmd($phpCli);
        $this->phpCli = $phpCli;
        $this->getFromConstants();
    }

    public function getName()
    {
        return 'php';
    }

    public function hasSdk()
    {
        if (isset($this->hasSdk)) {
            return $this->hasSdk;
        }
        $cliDir = dirname($this->phpCli);
        $res = glob($cliDir . DIRECTORY_SEPARATOR . 'phpize*');
        if (!$res) {
            $this->hasSdk = false;
        }
        $this->phpize = $res[0];

        return $this->hasSdk = false;
    }

    public function getArchitecture()
    {
        return $this->architecture;
    }

    public function getCompiler()
    {
        return $this->compiler;
    }

    public function getPath()
    {
        return $this->phpCli;
    }

    public function getMajorVersion()
    {
        return $this->major;
    }

    public function getMinorVersion()
    {
        return $this->minor;
    }

    public function getReleaseVersion()
    {
        return $this->release;
    }

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

    public function getZts()
    {
        return $this->zts;
    }

    public function getExtensionDir()
    {
        return $this->extensionDir;
    }

    public function getIniPath()
    {
        return $this->iniPath;
    }

    protected function getFullPathExtDir($dir)
    {
        $realpathDir = realpath($dir);
        $baseDir = dirname($realpathDir);
        $baseDirPhp = dirname($this->phpCli);
        if (empty($baseDir)) {
            if (empty($dir)) {
                return $baseDirPhp . 'ext';
            }
            return $baseDirPhp . '\\' . $dir;
        }

        if ($baseDir == $baseDirPhp) {
            return $realpathDir;
        }
    }

    protected function getExtensionDirFromPhpInfo($info)
    {
        $extensionDir = '';

        foreach ($info as $s) {
            $pos_ext_dir = strpos($s, 'extension_dir');
            if ($pos_ext_dir !== false && substr($s, $pos_ext_dir - 1, 1) != '.') {
                [, $extensionDir] = explode('=>', $s);
                break;
            }
        }

        $extensionDir = trim($extensionDir);
        if ($extensionDir == '') {
            throw new Exception('Cannot detect PHP extension directory');
        }
        return $this->getFullPathExtDir($extensionDir);
    }

    protected function getArchFromPhpInfo($info)
    {
        $arch = '';

        foreach ($info as $s) {
            if (strpos($s, 'Architecture') !== false) {
                [, $arch] = explode('=>', $s);
                break;
            }
        }

        $arch = trim($arch);
        if ($arch == '') {
            throw new Exception('Cannot detect PHP build architecture');
        }

        return $arch;
    }

    protected function getIniPathFromPhpInfo($info)
    {
        $iniPath = '';

        foreach ($info as $s) {
            if (strpos($s, 'Loaded Configuration File') !== false) {
                [, $iniPath] = explode('=>', $s);
                if ($iniPath === '(None)') {
                    $iniPath = '';
                }

                break;
            }
        }

        $iniPath = trim($iniPath);
        if ($iniPath == '') {
            throw new Exception('Cannot detect php.ini directory');
        }

        return $iniPath;
    }

    protected function getCompilerFromPhpInfo($info)
    {
        $compiler = '';

        foreach ($info as $s) {
            if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
                if (strpos($s, 'PHP Extension Build') !== false) {
                    [, $build] = explode('=>', $s);
                    [, , $compiler] = explode(',', $build);
                    $compiler = strtolower($compiler);
                    break;
                }
            } else {
                if (strpos($s, 'Compiler') !== false) {
                    [, $compiler] = explode('=>', $s);
                    break;
                }
            }
        }

        $compiler = trim($compiler);
        if ($compiler == '') {
            throw new Exception('Cannot detect PHP build compiler version');
        }

        return $compiler;
    }

    private function getFromConstants()
    {
        $script = 'echo PHP_VERSION . \"\n\"; '
                . 'echo PHP_MAJOR_VERSION . \"\n\"; '
                . 'echo PHP_MINOR_VERSION . \"\n\"; '
                . 'echo PHP_RELEASE_VERSION . \"\n\"; '
                . 'echo PHP_EXTRA_VERSION . \"\n\"; '
                . 'echo PHP_ZTS . \"\n\"; '
                . 'echo PHP_DEBUG . \"\n\"; ';

        $cmd = $this->phpCliEscaped . ' -n -r ' . '"' . str_replace("\n", '', $script) . '"';
        if (DIRECTORY_SEPARATOR === '\\') {
            $cmd .= ' 2>NUL';
        } else {
            $cmd .= ' 2>/dev/null';
        }

        $info = null;
        exec($cmd, $info);
        if (count($info) !== 7) {
            throw new Exception('Could not determine info from the PHP binary');
        }

        [$this->version, $this->major, $this->minor, $this->release, $this->extra, $this->zts, $this->debug] = $info;
        $this->zts = (bool) $this->zts;

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            [$this->compiler, $this->architecture, $this->iniPath, $this->extensionDir] = $this->getFromPhpInfo();
        }
    }

    private function getFromPhpInfo()
    {
        $cmd = $this->phpCliEscaped . ' -i';
        $info = null;
        exec($cmd, $info);

        if (!is_array($info)) {
            throw new Exception('Cannot parse phpinfo output');
        }

        $arch = $this->getArchFromPhpInfo($info);
        $iniPath = $this->getIniPathFromPhpInfo($info);
        $extensionDir = $this->getExtensionDirFromPhpInfo($info);

        $compiler = strtolower($this->getCompilerFromPhpInfo($info));
        return [$compiler, $arch, $iniPath, $extensionDir];
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Engine\PHP;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;

class Ini extends Abstracts\Engine\Ini implements Interfaces\Engine\Ini
{
    public function __construct(Interfaces\Engine $php)
    {
        parent::__construct($php);

        $this->setupPickleSectionPositions();
    }

    public function updatePickleSection(array $dlls_add, array $dlls_del = [])
    {
        $before = '';
        $after = '';

        $pickleSection = $this->rebuildPickleParts($this->getPickleSection(), $dlls_add, $dlls_del);

        $before = substr($this->raw, 0, $this->pickleHeaderStartPos);

        /* If the footer end pos is < 0, there was no footer in php.ini. In this case the footer start pos
           means the end of the last extension directive after the header start, where the footer should  be */
        if ($this->pickleFooterEndPos > 0) {
            $after = substr($this->raw, $this->pickleFooterEndPos);
        } else {
            $after = substr($this->raw, $this->pickleFooterStartPos);
        }

        $before = rtrim($before);
        $after = ltrim($after);

        $this->raw = $before . "\n\n" . self::PICKLE_HEADER . "\n" . trim($pickleSection) . "\n" . self::PICKLE_FOOTER . "\n\n" . $after;
        if (!@file_put_contents($this->path, $this->raw)) {
            throw new Exception('Cannot update php.ini');
        }
    }

    /**
     * @param string $pickleSection
     *
     * @return string
     */
    protected function rebuildPickleParts($pickleSection, array $dlls_add, array $dlls_del = [])
    {
        $lines = explode("\n", $pickleSection);
        $new = [];

        /* First add the lines for exts that are requested to be added. */
        foreach ($dlls_add as $dll) {
            $new[] = $this->buildDllIniLine($dll);
        }

        /* Then, go over the existing lines, restore those that are not
            requested to be deleted and not already added. */
        foreach ($lines as $l) {
            $l = trim($l);
            if (strpos($l, 'extension') !== 0) {
                continue;
            }
            [, $dllname] = explode('=', $l);

            if (in_array(trim($dllname), $dlls_add)) {
                /* don't create a duplicated item */
                continue;
            }
            if (in_array(trim($dllname), $dlls_del)) {
                /* don't restore as it should be deleted */
                continue;
            }
            $new[] = $l;
        }

        sort($new);

        return implode("\n", $new);
    }

    protected function setupPickleSectionPositions()
    {
        $posHeader = strpos($this->raw, self::PICKLE_HEADER);
        if ($posHeader === false) {
            /* no pickle section here yet */
            $this->pickleHeaderStartPos = strlen($this->raw);

            return;
        }

        $this->pickleHeaderStartPos = $posHeader;
        $this->pickleHeaderEndPos = $this->pickleHeaderStartPos + strlen(self::PICKLE_HEADER);

        $posFooter = strpos($this->raw, self::PICKLE_FOOTER);
        if ($posFooter === false) {
            /* This is bad, no end of section marker, will have to lookup. The strategy is
                - look for the last extension directve after the header
                - extension directives are expected to come one after another one per line
                - comments are not expected inbetveen
                - mark the next pos after the last extension directive as the footer pos
            */
            $pos = $this->pickleHeaderEndPos;
            do {
                $pos = strpos($this->raw, 'extension', $pos);
                if ($pos !== false) {
                    $this->pickleFooterStartPos = $pos;
                    $pos++;
                }
            } while ($pos !== false);

            $this->pickleFooterStartPos = strpos($this->raw, "\n", $this->pickleFooterStartPos);
        } else {
            $this->pickleFooterStartPos = $posFooter;
            $this->pickleFooterEndPos = $this->pickleFooterStartPos + strlen(self::PICKLE_FOOTER);
        }
    }

    protected function buildDllIniLine($dll)
    {
        return 'extension=' . $dll;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle;

use Pickle\Engine\PHP;

class Engine
{
    protected static $instance;

    public static function factory($phpcli = null)
    {
        if (self::$instance === null) {
            if (!$phpcli) {
                $phpcli = PHP_BINARY;
            }
            self::$instance = new PHP($phpcli);
        }

        return self::$instance;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;

class Any extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        throw new Exception('Unsupported package type');
    }

    public function getType()
    {
        throw new Exception('Unsupported package type');
    }

    protected function prepare()
    {
        throw new Exception('Unsupported package type');
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;

class SrcDir extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        /* Override target, otherwise we'd need to copy ext root each time */
        $target = realpath($this->path);

        $exe = DefaultExecutor::factory($this);

        return $exe->execute($target, $no_convert, $versionOverrideOverride);
    }

    public function getType()
    {
        return Type::SRC_DIR;
    }

    protected function prepare()
    {
        $this->url = $this->path;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Exception;
use Pickle\Base\Interfaces;
use Pickle\Engine;
use Pickle\Package\PHP;

class DefaultExecutor implements Interfaces\Package\Convey\DefaultExecutor
{
    protected $command;

    public function __construct(Interfaces\Package\Convey\Command $command)
    {
        $this->command = $command;
    }

    public static function factory(Interfaces\Package\Convey\Command $command)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':

            return new PHP\Convey\Command\DefaultExecutor($command);
        }

        return new self($command);
    }

    public function execute($target, $no_convert, $versionOverride)
    {
        throw new Exception('Default executor cannot be used without concrete implementation');
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Composer\IO\ConsoleIO;
use Pickle\Package\PHP\Convey\Command\Pecl;

class Factory
{
    /**
     * @param $type
     * @param $path
     *
     * @return \Pickle\Base\Abstracts\Package\Convey\Command
     */
    public static function getCommand($type, $path, ConsoleIO $io)
    {
        switch ($type) {
            case Type::PICKLE:
                return new Pickle($path, $io);
            case Type::PECL:
                return new Pecl($path, $io);

            case Type::GIT:
                return new Git($path, $io);

            case Type::TGZ:
                return new Tgz($path, $io);

            case Type::SRC_DIR:
                return new SrcDir($path, $io);
        }

        return new Any($path, $io);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Composer\Package\Version\VersionParser;

class Type
{
    public const PICKLE = 'pickle';

    public const PECL = 'pecl';

    public const GIT = 'git';

    public const TGZ = 'tgz';

    public const SRC_DIR = 'srcdir';

    public const ANY = 'any';

    public static function match($regs, $arg, &$matches)
    {
        foreach ($regs as $reg) {
            $ret = preg_match($reg, $arg, $matches);
            if ($ret > 0) {
                return $ret;
            }
        }

        return 0;
    }

    public static function determinePickle($arg, &$matches)
    {
        $versionParser = new VersionParser();
        $res = $versionParser->parseNameVersionPairs([$arg]);
        $argPrefix = substr($arg, 0, 1);
        if ($argPrefix == '/' || $argPrefix == '.') {
            return 0;
        }
        $matches = [
            'package' => $res[0]['name'],
            'version' => $res[0]['version'] ?? '',
        ];

        return 1;
    }

    public static function determinePecl($arg, &$matches)
    {
        $reg0 = '#^
            (?:pecl/)?
            (?<package>\w+)
            (?:
                \-(?<stability>beta|stable|alpha)
            )?
        $#x';

        $reg1 = '#^
            (?:pecl/)?
            (?<package>\w+)
            (?:
                (\-|@)(?<version>(?:\d+(?:\.\d+){1,2})|(?:[1-2]\d{3}[0-1]\d[0-3]\d{1}))
            )?
        $#x';

        return self::match([$reg0, $reg1], $arg, $matches);
    }

    public static function determineGit($arg, &$matches)
    {
        $reg0 = '#^
            (?:git|https|http|ssh|rsync|file?)(://|@).*?(/|\:)
            (?P<package>[a-zA-Z0-9\-_]+)
            (?:
                (?:\.git|)
                (?:\#(?P<reference>.*?)|)
            )?
        $#x';

        return self::match([$reg0], $arg, $matches);
    }

    public static function determine($path, $remote)
    {
        if (substr($path, -4) == '.tgz' || substr($path, -7) == '.tar.gz') {
            return self::TGZ;
        }
        $matches = null;
        if ($remote && self::determinePecl($path, $matches) > 0) {
            return self::PECL;
        }
        if ($remote && self::determineGit($path, $matches) > 0) {
            return self::GIT;
        }
        if (!$remote && is_dir($path)) {
            return self::SRC_DIR;
        }
        if (self::determinePickle($path, $matches) > 0) {
            return self::PICKLE;
        }

        return self::ANY;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Composer\Downloader\GitDownloader;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Package\Version\VersionParser;
use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;
use Pickle\Config;
use Pickle\Package;

class Pickle extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    /**
     * @var string
     */
    protected $type;

    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        $this->fetch($target);

        $exe = DefaultExecutor::factory($this);

        return $exe->execute($target, $no_convert, $versionOverrideOverride);
    }

    public function getType()
    {
        return Type::GIT;
    }

    protected function fetchPackageJson()
    {
        $extensionJson = @file_get_contents('http://localhost:8080/json/' . $this->name . '.json');
        if (!$extensionJson) {
            /** @var array $http_response_header */
            $status = $http_response_header[0] ?? '';
            if (strpos($status, '404') !== false) {
                throw new Exception("cannot find {$this->name}");
            }
            if ($status) {
                throw new Exception("http error while loading information for {$this->name}: " . $status);
            }
            throw new Exception("http error while loading information for {$this->name}: unknown error");
        }

        return json_decode($extensionJson, true);
    }

    protected function prepare()
    {
        $matches = null;
        if (Type::determinePickle($this->path, $matches) < 1) {
            throw new Exception('Not a pickle git URI');
        }

        $this->name = $matches['package'];

        $extension = $this->fetchPackageJson();

        $versionParser = new VersionParser();
        if ($matches['version'] == '') {
            $versions = array_keys($extension['packages'][$this->name]);
            if (count($versions) > 1) {
                $versionToUse = $versions[1];
            } else {
                $versionToUse = $versions[0];
            }
        } else {
            $versionConstraints = $versionParser->parseConstraints($matches['version']);

            /* versions are sorted decreasing */
            foreach (array_keys($extension['packages'][$this->name]) as $version) {
                $constraint = new VersionConstraint('=', $versionParser->normalize($version));
                if ($versionConstraints->matches($constraint)) {
                    $versionToUse = $version;
                    break;
                }
            }
        }

        $package = $extension['packages'][$this->name][$versionToUse];
        $this->version = $versionToUse;
        $this->normalizedVersion = $versionParser->normalize($versionToUse);

        $this->name = $matches['package'];
        $this->prettyVersion = $this->version;
        $this->url = $package['source']['url'];
        $this->reference = $package['source']['reference'];
        $this->type = $package['source']['type'];
    }

    protected function fetch($target)
    {
        $package = Package::factory($this->name, $this->version, $this->prettyVersion);

        $package->setSourceType($this->type);
        $package->setSourceUrl($this->url);
        $package->setSourceReference($this->version);
        $package->setRootDir($target);

        $downloader = new GitDownloader($this->io, new Config());
        if ($downloader !== null) {
            $downloader->download($package, $target);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;
use Pickle\Config;
use Pickle\Downloader\TGZDownloader;
use Pickle\Package;

class Tgz extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        $this->fetch($target);

        $exe = DefaultExecutor::factory($this);

        return $exe->execute($target, $no_convert, $versionOverrideOverride);
    }

    public function getType()
    {
        return Type::TGZ;
    }

    protected function prepare()
    {
        $this->name = basename($this->path);
        $this->version = 'unknown';
        $this->prettyVersion = 'unknown';
        $this->url = $this->path;
    }

    protected function fetch($target)
    {
        $package = Package::factory($this->name, $this->version, $this->prettyVersion);

        $package->setDistUrl($this->url);
        $package->setRootDir($target);

        $downloader = new TGZDownloader($this->io, new Config());
        if ($downloader !== null) {
            $downloader->download($package, $target);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Convey\Command;

use Composer\Downloader\GitDownloader;
use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;
use Pickle\Config;
use Pickle\Package;

class Git extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        $this->fetch($target);

        $exe = DefaultExecutor::factory($this);

        return $exe->execute($target, $no_convert, $versionOverrideOverride);
    }

    public function getType()
    {
        return Type::GIT;
    }

    protected function prepare()
    {
        $matches = null;
        if (Type::determineGit($this->path, $matches) < 1) {
            throw new Exception('Not valid git URI');
        }

        $this->name = $matches['package'];
        $this->version = $matches['reference'] ?? 'master';
        $this->prettyVersion = $this->version;
        $this->url = preg_replace('/#.*$/', '', $this->path);
    }

    protected function getComposerHome()
    {
        $composerHome = getenv('COMPOSER_HOME');
        if (!$composerHome) {
            if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
                $composerHome = getenv('APP_DATA') . 'Local/Composer';
            } else {
                $composerHome = getenv('HOME') . '/.config/composer';
            }
        }
        return $composerHome;
    }

    protected function fetch($target)
    {
        $package = Package::factory($this->name, $this->version, $this->prettyVersion);

        $package->setSourceType('git');

        $package->setSourceUrl($this->url);
        $package->setSourceReference($this->version);
        $package->setRootDir($target);
        $config = new Config();

        $config->merge(['config' => [
            'secure-http' => false,
            'home' => $this->getComposerHome(),
        ],
        ]);
        $downloader = new GitDownloader($this->io, $config);
        $downloader->download($package, $target);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Engine;
use Pickle\Package\PHP;

class Validate
{
    public static function factory($path, $cb)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                return new PHP\Command\Validate($path, $cb);

            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Engine;
use Pickle\Package\PHP;

class Install
{
    public static function factory($path)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
                    return new PHP\Command\Install\Windows\Binary($path);
                }
                    throw new Exception(
                        'On Unix Build::factory() functionality should be used to implememnt installation, '
                        . 'except you really need to install a binary.'
                    );

            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Engine;
use Pickle\Package\PHP;

class Convert
{
    public static function factory($path, $cb)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                return new PHP\Command\Convert($path, $cb);

            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Engine;
use Pickle\Package\PHP;

class Info
{
    public static function factory($path, $cb)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                return new PHP\Command\Info($path, $cb);

            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Base\Interfaces;
use Pickle\Engine;
use Pickle\Package\PHP;

class Build
{
    public static function factory(Interfaces\Package $package, $optionValue)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
                    return new PHP\Command\Build\Windows($package, $optionValue);
                }
                    return new PHP\Command\Build\Unix($package, $optionValue);

            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Command;

use Exception;
use Pickle\Engine;
use Pickle\Package\PHP;

class Release
{
    public static function factory($path, $cb, $noConvert = false, $binary = false)
    {
        $engine = Engine::factory();

        switch ($engine->getName()) {
            case 'php':
                if ($binary) {
                    if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
                        return new PHP\Command\Release\Windows\Binary($path, $cb, $noConvert);
                    }
                    throw new Exception('Binary packaging not implemented for this platform, use the build system of your favourite package manager');
                } else {
                    return new PHP\Command\Release($path, $cb, $noConvert);
                }

                // no break
            default:
                throw new Exception("Unsupported engine '{$engine->getName()}'. Implement it!");
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package;

use Composer\IO\ConsoleIO;
use Exception;
use Pickle\Base\Util;
use Pickle\Package\Convey\Command\Factory;
use Pickle\Package\Convey\Command\Type;

class Convey
{
    protected $command;

    public function __construct($path, ConsoleIO $io)
    {
        if (!$path) {
            throw new Exception('Path cannot be empty');
        }

        $type = Type::determine($path, (realpath($path) === false));
        $this->command = Factory::getCommand($type, $path, $io);
    }

    public function deliver($target = '', $no_convert = false, $versionOverride = null)
    {
        $target = $target ? realpath($target) : Util\TmpDir::get() . DIRECTORY_SEPARATOR . $this->command->getName();

        return $this->command->execute($target, $no_convert, $versionOverride);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util\Windows;

use Exception;
use Pickle\Base\Archive;
use Pickle\Base\Util;
use Pickle\Base\Util\FileOps;
use RuntimeException;
use Symfony\Component\Console\Output\OutputInterface as OutputInterface;

class DependencyLib
{
    use FileOps;

    public const DLL_MAP_URL = 'https://windows.php.net/downloads/pecl/deps/dllmapping.json';

    public const DEPLISTER_URL = 'https://windows.php.net/downloads/pecl/tools/deplister.exe';

    public const DEPS_URL = 'https://windows.php.net/downloads/pecl/deps';

    private $dllMap;

    private $php;

    private $progress;

    private $output;

    private $fetchedZips = [];

    public function __construct(\Pickle\Base\Interfaces\Engine $php)
    {
        $this->php = $php;
        $this->checkDepListerExe();
        $this->fetchDllMap();
    }

    public function getZipUrlsForDll($binary, $ignore_installed = false)
    {
        $dll = $this->getDllsForBinary($binary);
        $packages = [];
        foreach ($this->dllMap as $pkg_name => $pkg) {
            foreach ($dll as $dll_name => $dll_installed) {
                if (in_array($dll_name, $pkg)) {
                    if ($ignore_installed && $dll_installed) {
                        continue;
                    }
                    $packages[] = $pkg_name;
                    continue 2;
                }
            }
        }

        return $packages;
    }

    public function resolveForBin($dll, $resolve_multiple_cb = null)
    {
        $dep_zips = $this->getZipUrlsForDll($dll, false);

        if (count($dep_zips) == 1) {
            $dep_zip = $dep_zips[0];

            if (in_array($dep_zip, $this->fetchedZips)) {
                return true;
            }
        } elseif (count($dep_zips) > 1) {
            foreach ($dep_zips as $dep_zip) {
                /* The user has already picked one here, ignore it. */
                if (in_array($dep_zip, $this->fetchedZips)) {
                    return true;
                }
            }
            if ($resolve_multiple_cb !== null) {
                $dep_zip = $resolve_multiple_cb($dep_zips);
            } else {
                throw new Exception("Multiple choice for dependencies, couldn't resolve");
            }
        } else {
            /* That might be not quite true, as we might just not have the
           corresponding dependency package. However it's fetched from
           the PECL build dependencies, no extension build should have
           been exist if there's no dependency package uploaded. */
            return true;
        }

        return $this->resolveForZip($dep_zip, $resolve_multiple_cb);
    }

    public function resolveForZip($zip_name, $resolve_multiple_cb = null)
    {
        if (in_array($zip_name, $this->fetchedZips)) {
            return true;
        }

        $url = self::DEPS_URL . "/{$zip_name}";
        $path = $this->download($url);
        try {
            $this->uncompress($path);
            $lst = $this->copyFiles();
        } catch (Exception $e) {
            $this->cleanup();
            throw new Exception($e->getMessage());
        }
        $this->cleanup();
        $this->fetchedZips[] = $zip_name;

        foreach ($lst as $bin) {
            $this->resolveForBin($bin, $resolve_multiple_cb);
        }

        return true;
    }

    public function setProgress($progress)
    {
        $this->progress = $progress;
    }

    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;
    }

    private function fetchDllMap()
    {
        $dllMap = null;

        if ($this->dllMap === null) {
            $opts = [
                'http' => [
                    'header' => 'User-Agent: pickle',
                ],
            ];
            $context = stream_context_create($opts);
            $data = @file_get_contents(self::DLL_MAP_URL, false, $context);
            if (!$data) {
                throw new RuntimeException('Cannot fetch the DLL mapping file');
            }
            $dllMap = json_decode($data);
            if (!$dllMap) {
                throw new RuntimeException('Cannot parse the DLL mapping file');
            }
        }
        $compiler = $this->php->getCompiler();
        $architecture = $this->php->getArchitecture();
        if (!isset($dllMap->{$compiler}->{$architecture})) {
            /* Just for the case the given compiler/arch set isn't defined in the dllmap,
           or we've got a corrupted file, or ...
           The dllMap property should be ensured an array. */
            $this->dllMap = [];
        } else {
            $this->dllMap = $dllMap->{$compiler}->{$architecture};
        }

        return true;
    }

    private function checkDepListerExe()
    {
        $ret = exec('deplister.exe ' . $this->php->getPath() . ' .');
        if (empty($ret)) {
            $depexe = @file_get_contents(self::DEPLISTER_URL);
            if (!$depexe) {
                throw new RuntimeException('Cannot fetch deplister.exe');
            }
            $dir = dirname($this->php->getPath());
            $path = $dir . DIRECTORY_SEPARATOR . 'deplister.exe';
            if (!@file_put_contents($path, $depexe)) {
                throw new RuntimeException('Cannot copy deplister.exe to ' . $dir);
            }
        }
    }

    private function getDllsForBinary($binary)
    {
        $out = [];
        $ret = exec('deplister.exe ' . escapeshellarg($binary) . ' .', $out);
        if (empty($ret) || !$ret) {
            throw new RuntimeException('Error while running deplister.exe');
        }
        $dlls = [];
        foreach ((array) $out as $l) {
            [$dllname, $found] = explode(',', $l);
            $found = trim($found);
            $dllname = trim($dllname);
            $dlls[$dllname] = $found == 'OK' ? true : false;
        }

        return $dlls;
    }

    private function copyFiles()
    {
        $ret = [];
        $DLLs = glob($this->tempDir . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . '*.dll');

        /* Copying ALL files from the zip, not just required. */
        foreach ($DLLs as $dll) {
            $dll = realpath($dll);
            $basename = basename($dll);
            $dest = dirname($this->php->getPath()) . DIRECTORY_SEPARATOR . $basename;
            $success = @copy($dll, dirname($this->php->getPath()) . '/' . $basename);
            if (!$success) {
                throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
            }

            $ret[] = $dest;
        }

        return $ret;
    }

    private function download($url)
    {
        $output = $this->output;
        $progress = $this->progress;

        $ctx = stream_context_create(
            [],
            [
                'notification' => function ($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) use ($output, $progress) {
                    switch ($notificationCode) {
                        case STREAM_NOTIFY_FILE_SIZE_IS:
                            $progress->start($output, $bytesMax);
                            break;
                        case STREAM_NOTIFY_PROGRESS:
                            $progress->setCurrent($bytesTransferred);
                            break;
                    }
                },
            ]
        );
        $output->writeln("downloading {$url} ");
        $fileContents = file_get_contents($url, false, $ctx);
        $progress->finish();
        if (!$fileContents) {
            throw new Exception('Cannot fetch <' . $url . '>');
        }
        $tmpdir = Util\TmpDir::get();
        $path = $tmpdir . DIRECTORY_SEPARATOR . basename($url);
        if (!file_put_contents($path, $fileContents)) {
            throw new Exception('Cannot save temporary file <' . $path . '>');
        }

        return $path;
    }

    private function uncompress($zipFile)
    {
        $this->createTempDir();
        $this->cleanup();
        $zipArchiveClass = Archive\Factory::getUnzipperClassName();
        $zipArchive = new $zipArchiveClass($zipFile);
        /** @var \Pickle\Base\Interfaces\Archive\Unzipper $zipArchive */
        $this->output->writeln('Extracting archives...');
        $zipArchive->extractTo($this->tempDir);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util;

use Composer\Semver\VersionParser;
use Pickle\Base\Interfaces;

class Dumper
{
    /**
     * @param \Pickle\Base\Interfaces\Package $package
     *
     * @return array
     */
    public function dump(Interfaces\Package $package, $with_version = true)
    {
        $data = [];

        $data['name'] = $package->getPrettyName();

        if ($with_version) {
            $data['version'] = $this->getVersion($package);
        }

        $data['type'] = $package->getType();

        if ($license = $package->getLicense()) {
            $data['license'] = $license;
        }

        if ($authors = $package->getAuthors()) {
            $data['authors'] = $authors;
        }

        if ($description = $package->getDescription()) {
            $data['description'] = $description;
        }

        if ($support = $package->getSupport()) {
            $data['support'] = $support;
        }

        if ($extra = $package->getExtra()) {
            $data['extra'] = $extra;
        }

        return $data;
    }

    private function getVersion(Interfaces\Package $package): string
    {
        $version = $package->getPrettyVersion();
        $version_stability = VersionParser::parseStability($version);
        $stability = $package->getStability();

        if ($stability === 'beta' && $version_stability === 'stable') {
            return $version . '-' . $stability;
        }
        return $version;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util\JSON;

use Pickle\Base\Interfaces;
use Pickle\Package\Util;

class Dumper
{
    /**
     * @param \Pickle\Base\Interfaces\Package $package
     *
     * @return string
     */
    public function dump(Interfaces\Package $package, $with_version = true)
    {
        return json_encode((new Util\Dumper())->dump($package, $with_version), JSON_PRETTY_PRINT);
    }

    /**
     * @param \Pickle\Base\Interfaces\Package $package
     * @param string $path
     */
    public function dumpToFile(Interfaces\Package $package, $path, $with_version = true)
    {
        file_put_contents($path, $this->dump($package, $with_version));
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util\JSON;

use Composer\Package\Loader\LoaderInterface;
use Exception;
use InvalidArgumentException;
use RuntimeException;

class Loader
{
    protected $loader;

    public function __construct(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    /**
     * @param string $path
     *
     * @return \Pickle\Base\Interfaces\Package
     */
    public function load($path)
    {
        if (is_file($path) === false) {
            throw new InvalidArgumentException('File not found: ' . $path);
        }

        $json = @json_decode(file_get_contents($path));

        if ($json === false) {
            $error = error_get_last();
            $exception = null;

            if ($error !== null) {
                $exception = new Exception($error['message'], $error['type']);
            }

            throw new RuntimeException('Failed to read ' . $path, 0, $exception);
        }

        $this->validate($json);

        return $this->loader->load((array) $json);
    }

    protected function validate($json)
    {
        $schema = json_decode(file_get_contents(__DIR__ . '/../../../../vendor/composer/composer/res/composer-schema.json'));
        $validator = new \JsonSchema\Validator();
        $validator->check($json, $schema);

        if ($validator->isValid() === false) {
            $message = '';

            foreach ($validator->getErrors() as $error) {
                $message .= sprintf('[%s] %s', $error['property'], $error['message']) . PHP_EOL;
            }

            throw new RuntimeException($message);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util;

use Composer\Package\Loader\LoaderInterface;
use Composer\Package\Version\VersionParser;
use DateTime;
use DateTimeZone;
use Exception;
use Pickle\Base\Interfaces;
use Pickle\Package;
use UnexpectedValueException;

class Loader implements LoaderInterface
{
    protected $versionParser;

    protected $loadOptions;

    public function __construct(?VersionParser $parser = null, $loadOptions = false)
    {
        $this->versionParser = $parser ?: new VersionParser();
        $this->loadOptions = $loadOptions;
    }

    /**
     * @param string $package
     *
     * @return \Pickle\Base\Interfaces\Package $package
     */
    public function load(array $config, $package = 'Pickle\Base\Interfaces\Package')
    {
        if (isset($config['version'])) {
            $version = $this->versionParser->normalize($config['version']);
            $package = Package::factory($config['name'], $version, $config['version'], true);
        } else {
            $package = Package::factory($config['name'], '', '', true);
        }

        if (isset($config['type']) && $config['type'] != 'extension') {
            throw new UnexpectedValueException($package->getName() . ' is not a extension(s) package');
        }
        $package->setType('extension');

        $this->setPackageSource($package, $config);
        $this->setPackageDist($package, $config);
        $this->setPackageReleaseDate($package, $config);
        $this->setPackageStability($package, $config);
        $this->setPackageExtra($package, $config);
        $this->setPackageDescription($package, $config);
        $this->setPackageHomepage($package, $config);
        $this->setPackageKeywords($package, $config);
        $this->setPackageLicense($package, $config);
        $this->setPackageAuthors($package, $config);
        $this->setPackageSupport($package, $config);

        return $package;
    }

    protected function setPackageStability(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'stability', 'string')) {
            $package->setStability($config['stability']);
        }
    }

    protected function setPackageExtra(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'extra', 'array')) {
            $package->setExtra($config['extra']);
        }
    }

    protected function setPackageDescription(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'description', 'string')) {
            $package->setDescription($config['description']);
        }
    }

    protected function setPackageHomepage(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'homepage', 'string')) {
            $package->setHomepage($config['homepage']);
        }
    }

    protected function setPackageKeywords(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'keywords', 'array')) {
            $package->setKeywords($config['keywords']);
        }
    }

    protected function setPackageLicense(Interfaces\Package $package, array $config)
    {
        if (!empty($config['license'])) {
            $package->setLicense(is_array($config['license']) ? $config['license'] : [$config['license']]);
        }
    }

    protected function setPackageAuthors(Interfaces\Package $package, array $config)
    {
        if ($this->isValid($config, 'authors', 'array')) {
            $package->setAuthors($config['authors']);
        }
    }

    protected function setPackageSupport(Interfaces\Package $package, array $config)
    {
        if (isset($config['support'])) {
            $package->setSupport($config['support']);
        }
    }

    protected function isValid($config, $key, $type = 'any')
    {
        switch ($type) {
            case 'string':
                return isset($config[$key]) && !empty($config[$key]) && is_string($config[$key]);

            case 'array':
                return isset($config[$key]) && !empty($config[$key]) && is_array($config[$key]);
        }

        return false;
    }

    protected function setPackageSource(Interfaces\Package $package, array $config)
    {
        if (!isset($config['source'])) {
            return;
        }

        if (!isset($config['source']['type']) || !isset($config['source']['url']) || !isset($config['source']['reference'])) {
            throw new UnexpectedValueException(sprintf(
                "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.",
                $config['name'],
                json_encode($config['source'])
            ));
        }
        $package->setSourceType($config['source']['type']);
        $package->setSourceUrl($config['source']['url']);
        $package->setSourceReference($config['source']['reference']);
        if (isset($config['source']['mirrors'])) {
            $package->setSourceMirrors($config['source']['mirrors']);
        }
    }

    protected function setPackageDist(Interfaces\Package $package, array $config)
    {
        if (!isset($config['dist'])) {
            return;
        }

        if (!isset($config['dist']['type'])
            || !isset($config['dist']['url'])) {
            throw new UnexpectedValueException(sprintf(
                "Package %s's dist key should be specified as "
                . "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.",
                $config['name'],
                json_encode($config['dist'])
            ));
        }

        $package->setDistType($config['dist']['type']);
        $package->setDistUrl($config['dist']['url']);
        $package->setDistReference($config['dist']['reference'] ?? null);
        $package->setDistSha1Checksum($config['dist']['shasum'] ?? null);
        if (isset($config['dist']['mirrors'])) {
            $package->setDistMirrors($config['dist']['mirrors']);
        }
    }

    protected function setPackageReleaseDate(Interfaces\Package $package, array $config)
    {
        if (empty($config['time'])) {
            return;
        }

        $time = ctype_digit($config['time']) ? '@' . $config['time'] : $config['time'];

        try {
            $date = new DateTime($time, new DateTimeZone('UTC'));
            $package->setReleaseDate($date);
        } catch (Exception $e) {
            // don't crash if time is incorrect
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\Util\Header;

use Composer\Package\Version\VersionParser;
use Exception;
use Pickle\Base\Interfaces;
use Pickle\Package\Util;

class Version
{
    protected $package;

    protected $header;

    protected $version;

    protected $macroNameRegex;

    public function __construct(Interfaces\Package $package)
    {
        $this->package = $package;
        $this->macroNameRegex = '(?:PHP_)?' . strtoupper($this->package->getSimpleName()) . '_(?:EXT_)?VERSION';

        $this->version = $this->getVersionFromHeader();
    }

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

    public function fileHasVersionMacro($fname)
    {
        return $this->getVersionFromHeaderFile($fname) !== null;
    }

    public function getVersionFromHeader()
    {
        $headers = self::rglob($this->package->getSourceDir() . DIRECTORY_SEPARATOR . '*.h');
        foreach ($headers as $header) {
            $macroValue = $this->getVersionFromHeaderFile($header);
            if ($macroValue !== null) {
                return $macroValue;
            }
        }
        throw new Exception("Couldn't parse or find the version defined in the {$this->macroNameRegex} macro");
    }

    public function updateJSON()
    {
        if ($this->package->getPrettyVersion() == $this->version) {
            /* Don't touch, it's the same. */
            return;
        }

        $dumper = new Util\Dumper();
        $composer_json = $this->package->getRootDir() . DIRECTORY_SEPARATOR . 'composer.json';

        $this->package->replaceVersion((new VersionParser())->normalize($this->version), $this->version);

        $len = file_put_contents($composer_json, json_encode($dumper->dump($this->package), JSON_PRETTY_PRINT));

        if (!$len) {
            throw new Exception("Failed to update '{$composer_json}'");
        }
    }

    /**
     * Extract the macro value from a header file.
     *
     * @throws Exception if we couldn't read the file
     *
     * @return string|null return NULL if the macro couldn't be found, the macro value otherwise
     */
    protected function getVersionFromHeaderFile(string $headerFilePath): ?string
    {
        $headerFileContents = file_get_contents($headerFilePath);
        if ($headerFileContents === false) {
            throw new Exception("Could not read header file {$headerFilePath}");
        }

        return $this->getVersionFromHeaderFileContents($headerFileContents);
    }

    /**
     * Extract the macro value from the contents of a header file.
     *
     * @return string|null return NULL if the macro couldn't be found, the macro value otherwise
     */
    protected function getVersionFromHeaderFileContents(string $headerFileContents): ?string
    {
        // Match versions surrounded by quotes and versions without quotes
        $versionMatcher = '(".*"|.*\b)';
        $pat = ',define\s+' . $this->macroNameRegex . '\s+' . $versionMatcher . ',i';
        $result = null;

        return preg_match($pat, $headerFileContents, $result) ? trim($result[1], '"') : null;
    }

    private function rglob($pattern, $flags = 0)
    {
        $files = glob($pattern, $flags);
        foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
            $files = array_merge($files, self::rglob($dir . '/' . basename($pattern), $flags));
        }
        return $files;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Convey\Command;

use Pickle\Base\Interfaces;
use Pickle\Package\PHP\Util\PackageXml;
use RuntimeException;

class DefaultExecutor implements Interfaces\Package\Convey\DefaultExecutor
{
    public function __construct(Interfaces\Package\Convey\Command $command)
    {
    }

    public function execute($target, $no_convert, $versionOverride)
    {
        $jsonLoader = new \Pickle\Package\Util\JSON\Loader(new \Pickle\Package\Util\Loader());
        $pickle_json = $target . DIRECTORY_SEPARATOR . 'composer.json';
        $package = null;

        if (file_exists($pickle_json)) {
            $package = $jsonLoader->load($pickle_json);
        }

        if ($package === null && $no_convert) {
            throw new RuntimeException('XML package are not supported. Please convert it before install');
        }

        if ($package === null) {
            $pkgXml = new PackageXml($target, $versionOverride);
            $pkgXml->dump();

            $jsonPath = $pkgXml->getJsonPath();
            unset($package);

            $package = $jsonLoader->load($jsonPath);
        }

        $package->setRootDir($target);
        $package->updateVersion($versionOverride);

        return $package;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Convey\Command;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;
use Pickle\Base\Pecl\WebsiteFactory;
use Pickle\Config;
use Pickle\Downloader\PECLDownloader;
use Pickle\Engine;
use Pickle\Package\Convey\Command\Type;

class Pecl extends Abstracts\Package\Convey\Command implements Interfaces\Package\Convey\Command
{
    public function execute($target, $no_convert, $versionOverrideOverride)
    {
        $this->fetch($target);

        $exe = new DefaultExecutor($this);

        return $exe->execute($target, $no_convert, $versionOverrideOverride);
    }

    public function getType()
    {
        return Type::PECL;
    }

    protected function prepare()
    {
        $engine = Engine::factory();
        $matches = null;
        if (Type::determinePecl($this->path, $matches) < 1) {
            throw new Exception('Not valid PECL URI');
        }

        if ($engine->getName() != 'php') {
            throw new Exception('PECL is only supported with PHP');
        }

        $this->name = $matches['package'];
        $this->url = WebsiteFactory::getWebsite()->getBaseUrl() . '/get/' . $matches['package'];

        if (isset($matches['stability']) && $matches['stability'] !== '') {
            $this->stability = $matches['stability'];
            $this->url .= '-' . $matches['stability'];
        } else {
            $this->stability = 'stable';
        }

        if (isset($matches['version']) && $matches['version'] !== '') {
            $this->url .= '/' . $matches['version'];
            $this->prettyVersion = $matches['version'];
            $this->version = $matches['version'];
        } else {
            $this->version = 'latest';
            $this->prettyVersion = 'latest-' . $this->stability;
        }
    }

    protected function fetch($target)
    {
        $package = \Pickle\Package::factory($this->name, $this->version, $this->prettyVersion);
        $package->setDistUrl($this->url);

        $package->setRootDir($target);

        $config = new Config();
        $config->merge([
            'config' => [
                'secure-http' => !WebsiteFactory::getWebsite()->isUnsafe(),
            ],
        ]);
        $downloader = new PECLDownloader($this->io, $config);
        $downloader->download($package, $target);

        unset($package, $downloader);
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command\Install\Windows;

use DOMDocument;
use Exception;
use Pickle\Base\Archive;
use Pickle\Base\Pecl\WebsiteFactory;
use Pickle\Base\Util;
use Pickle\Base\Util\FileOps;
use Pickle\Engine;
use Symfony\Component\Console\Output\OutputInterface as OutputInterface;

class Binary
{
    use FileOps;

    private $php;

    private $extName;

    private $extVersion;

    private $progress;

    private $output;

    private $extDll;

    /**
     * @param string $ext
     */
    public function __construct($ext)
    {
        // used only if only the extension name is given
        if (strpos('//', $ext) !== false) {
            $this->extensionPeclExists();
        }

        $this->extName = $ext;
        $this->php = Engine::factory();
    }

    public function setProgress($progress)
    {
        $this->progress = $progress;
    }

    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     *  1. check if ext exists
     *  2. check if given version requested
     *  2.1 yes? check if builds available
     *  2.2 no? get latest version+build.
     *
     * @throws Exception
     */
    public function install()
    {
        [$this->extName, $this->extVersion] = $this->getInfoFromPecl();
        $url = $this->fetchZipName();
        $pathArchive = $this->download($url);
        $this->uncompress($pathArchive);
        $this->copyFiles();
        $this->cleanup();
        $this->updateIni();
    }

    public function getExtDllPaths()
    {
        $ret = [];

        foreach ($this->extDll as $dll) {
            $ret[] = $this->php->getExtensionDir() . DIRECTORY_SEPARATOR . $dll;
        }

        return $ret;
    }

    private function extensionPeclExists()
    {
        $url = WebsiteFactory::getWebsite()->getBaseUrl() . '/get/' . $this->extName;
        $headers = get_headers($url, 1);
        $status = $headers[0];
        if (strpos($status, '404')) {
            throw new Exception("Extension <{$this->extName}> cannot be found");
        }
    }

    /**
     * @param string $url
     */
    private function findInLinks($url, $toFind)
    {
        $page = @file_get_contents($url);
        $opts = [
            'http' => [
                'header' => 'User-Agent: pickle',
            ],
        ];
        $context = stream_context_create($opts);
        $page = @file_get_contents($url, false, $context);
        if (!$page) {
            return false;
        }
        $dom = new DOMDocument();
        $dom->loadHTML($page);
        $links = $dom->getElementsByTagName('a');
        if (!$links) {
            return false;
        }

        foreach ($links as $link) {
            if ($link->nodeValue[0] == '[') {
                continue;
            }
            $value = trim($link->nodeValue);
            if ($toFind == $value) {
                return $value;
            }
        }

        return false;
    }

    /**
     * @throws Exception
     *
     * @return string
     */
    private function fetchZipName()
    {
        $phpVc = $this->php->getCompiler();
        $phpArch = $this->php->getArchitecture();
        $phpZts = $this->php->getZts() ? '-ts' : '-nts';
        $phpVersion = $this->php->getMajorVersion() . '.' . $this->php->getMinorVersion();
        $pkgVersion = $this->extVersion;
        $extName = strtolower($this->extName);
        $baseUrl = 'https://windows.php.net/downloads/pecl/releases/';

        if ($this->findInLinks($baseUrl . $extName, $pkgVersion) === false) {
            throw new Exception('Binary for <' . $extName . '-' . $pkgVersion . '> cannot be found');
        }

        $fileToFind = 'php_' . $extName . '-' . $pkgVersion . '-' . $phpVersion . $phpZts . '-' . $phpVc . '-' . $phpArch . '.zip';
        $fileUrl = $this->findInLinks($baseUrl . $extName . '/' . $pkgVersion, $fileToFind);

        if (!$fileUrl) {
            throw new Exception('Binary for <' . $fileToFind . '> cannot be found');
        }
        return $baseUrl . $extName . '/' . $pkgVersion . '/' . $fileToFind;
    }

    /**
     * @param string $zipFile
     *
     * @throws Exception
     */
    private function uncompress($zipFile)
    {
        $this->createTempDir($this->extName);
        $this->cleanup();
        $zipClass = Archive\Factory::getUnzipperClassName();
        $zipArchive = $zipClass($zipFile);
        /** @var \Pickle\Base\Interfaces\Archive\Unzipper $zipArchive */
        $this->output->writeln('Extracting archives...');
        $zipArchive->extractTo($this->tempDir);
    }

    /**
     * @param string $url
     *
     * @throws Exception
     *
     * @return string
     */
    private function download($url)
    {
        $progress = $this->progress;
        $progress->setOverwrite(true);
        $ctx = stream_context_create(
            [
                'http' => [
                    'header' => 'User-Agent: pickle',
                ],
            ],
            [
                'notification' => function ($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) use ($progress) {
                    switch ($notificationCode) {
                        case STREAM_NOTIFY_RESOLVE:
                        case STREAM_NOTIFY_AUTH_REQUIRED:
                        case STREAM_NOTIFY_COMPLETED:
                        case STREAM_NOTIFY_FAILURE:
                        case STREAM_NOTIFY_AUTH_RESULT:
                            break;

                        case STREAM_NOTIFY_REDIRECTED:
                            break;

                        case STREAM_NOTIFY_CONNECT:
                            break;

                        case STREAM_NOTIFY_FILE_SIZE_IS:
                            $progress->start($bytesMax);
                            break;

                        case STREAM_NOTIFY_MIME_TYPE_IS:
                            break;

                        case STREAM_NOTIFY_PROGRESS:
                            $progress->setProgress($bytesTransferred);
                            break;
                    }
                },
            ]
        );
        if ($this->output) {
            $this->output->writeln("downloading {$url} ");
        }
        $fileContents = file_get_contents($url, false, $ctx);
        $progress->finish();
        if (!$fileContents) {
            throw new Exception('Cannot fetch <' . $url . '>');
        }
        $tmpdir = Util\TmpDir::get();
        $path = $tmpdir . '/' . $this->extName . '.zip';
        if (!file_put_contents($path, $fileContents)) {
            throw new Exception('Cannot save temporary file <' . $path . '>');
        }

        return $path;
    }

    /**
     * @throws Exception
     */
    private function copyFiles()
    {
        $DLLs = glob($this->tempDir . '/*.dll');
        $this->extDll = [];
        foreach ($DLLs as $dll) {
            $dll = realpath($dll);
            $basename = basename($dll);
            $dest = $this->php->getExtensionDir() . DIRECTORY_SEPARATOR . $basename;
            if (substr($basename, 0, 4) == 'php_') {
                $this->extDll[] = $basename;
                $this->output->writeln("copying {$dll} to " . $dest . "\n");
                $success = copy($dll, $this->php->getExtensionDir() . '/' . $basename);
                if (!$success) {
                    throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
                }
            } else {
                $success = copy($dll, dirname($this->php->getPath()) . '/' . $basename);
                if (!$success) {
                    throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
                }
            }
        }
    }

    /**
     * @throws Exception
     */
    private function updateIni()
    {
        $ini = \Pickle\Engine\Ini::factory($this->php);
        $ini->updatePickleSection($this->extDll);
    }

    /**
     * @throws Exception
     *
     * @return array
     */
    private function getInfoFromPecl()
    {
        $baseUrl = WebsiteFactory::getWebsite()->getBaseUrl();
        $url = $baseUrl . '/get/' . $this->extName;
        $headers = get_headers($url);

        if (strpos($headers[0], '404') !== false) {
            throw new Exception('Cannot find extension <' . $this->extName . '>');
        }
        $headerPkg = null;
        foreach ($headers as $header) {
            if (strpos($header, 'tgz') !== false) {
                $headerPkg = $header;
                break;
            }
        }
        if ($headerPkg === null) {
            throw new Exception('Cannot find extension <' . $this->extName . '>');
        }
        $m = null;
        if (!preg_match('|=(.*)\\.[a-z0-9]{2,3}$|', $headerPkg, $m)) {
            throw new Exception("Invalid response from {$baseUrl}");
        }
        $packageFullname = $m[1];

        [$name, $version] = explode('-', $packageFullname);
        if ($name == '' || $version == '') {
            throw new Exception("Invalid response from {$baseUrl}");
        }

        return [$name, $version];
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command;

use Pickle\Base\Interfaces;
use Pickle\Package\PHP\Util\PackageXml;

class Validate implements Interfaces\Package\Validate
{
    protected $path;

    protected $cb;

    public function __construct($path, $cb = null)
    {
        $this->path = $path;
        $this->cb = $cb;
    }

    public function process()
    {
        $pkgXml = new PackageXml($this->path);
        $package = $pkgXml->getPackage();

        if ($this->cb) {
            $cb = $this->cb;
            $cb($package);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command;

use Pickle\Package\PHP\Util\PackageXml;

class Convert
{
    protected $path;

    protected $cb;

    public function __construct($path, $cb = null)
    {
        $this->path = $path;
        $this->cb = $cb;
    }

    public function process()
    {
        $path = rtrim($this->path, '/\\');

        $pkgXml = new PackageXml($path);
        $package = $pkgXml->getPackage();
        $package->setRootDir($path);

        $pkgXml->convertChangeLog();
        $pkgXml->dump();

        if ($this->cb) {
            $cb = $this->cb;
            $cb($package);
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command;

use Pickle\Base\Interfaces;

class Info implements Interfaces\Package\Info
{
    protected $package;

    protected $cb;

    public function __construct(Interfaces\Package $package, $cb = null)
    {
        $this->package = $package;
        $this->cb = $cb;
    }

    public function show()
    {
        if ($this->cb) {
            $cb = $this->cb;
            $cb($this);
        }
    }

    public function getPackage()
    {
        return $this->package;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command\Release\Windows;

use Closure;
use Exception;
use Pickle\Base\Archive;
use Pickle\Base\Interfaces;
use Pickle\Package\PHP\Util\PackageJson;
use Pickle\Package\Util\Header;

class Binary implements Interfaces\Package\Release
{
    /**
     * @var \Pickle\Base\Interfaces\Package
     */
    protected $pkg;

    /**
     * @var Closure
     */
    protected $cb;

    /**
     * @var bool
     */
    protected $noConvert = false;

    /**
     * @var Interfaces\Package\Build
     */
    protected $build;

    /**
     * Constructor.
     *
     * @param string $path
     * @param Closure $cb
     * @param bool $noConvert
     */
    public function __construct($path, $cb = null, $noConvert = false)
    {
        $this->pkg = $this->readPackage($path);
        $this->cb = $cb;
        $this->noConvert = $noConvert;
    }

    public function __destruct()
    {
        $this->composerJsonBak($this->pkg, true);
    }

    /**
     * Create package.
     */
    public function create(array $args = [])
    {
        if (!isset($args['build']) || !($args['build'] instanceof Interfaces\Package\Build)) {
            throw new Exception('Invalid or NULL object passed as Interfaces\\Package\\Build');
        }
        $this->build = $build = $args['build'];

        $info = $build->getInfo();

        $tmp_dir = $build->getTempDir();

        $tmp = $build->getLog('configure');
        $m = null;
        if (preg_match(',Build dir:\\s+([\\:\\-\\.0-9a-zA-Z\\\\_]+),', $tmp, $m)) {
            if (preg_match(',^[a-z]\\:\\\\,i', $m[1]) && is_dir($m[1])) {
                /* Parsed the fully qualified path */
                $build_dir = $m[1];
            } else {
                /* otherwise construct */
                $build_dir = $tmp_dir . DIRECTORY_SEPARATOR . $m[1];
            }
        } else {
            $build_dir = $info['arch'] == 'x86' ? $tmp_dir : $tmp_dir . DIRECTORY_SEPARATOR . 'x64';
            $build_dir .= DIRECTORY_SEPARATOR . (($info['is_release'] ?? true) ? 'Release' : 'Debug');
            $build_dir .= ($info['thread_safe'] ? '_TS' : '');
        }

        /* Various file paths to pack. */
        $composer_json = $this->pkg->getRootDir() . DIRECTORY_SEPARATOR . 'composer.json';

        if (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'LICENSE')) {
            $license = $tmp_dir . DIRECTORY_SEPARATOR . 'LICENSE';
        } elseif (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'COPYING')) {
            $license = $tmp_dir . DIRECTORY_SEPARATOR . 'COPYING';
        } elseif (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'LICENSE.md')) {
            $license = $tmp_dir . DIRECTORY_SEPARATOR . 'LICENSE.md';
        } elseif (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'COPYING.md')) {
            $license = $tmp_dir . DIRECTORY_SEPARATOR . 'COPYING.md';
        } else {
            throw new Exception("Couldn't find LICENSE");
        }

        $readme = null;
        if (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'README')) {
            $readme = $tmp_dir . DIRECTORY_SEPARATOR . 'README';
        } elseif (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'README.md')) {
            $readme = $tmp_dir . DIRECTORY_SEPARATOR . 'README.md';
        }

        /* pack the outcome */
        $zip_name = $this->getZipBaseName($build) . '.zip';

        $zipClass = Archive\Factory::getZipperClassName();
        $zip = new $zipClass($zip_name, Interfaces\Archive\Zipper::FLAG_CREATE_OVERWRITE);
        /** @var \Pickle\Base\Interfaces\Archive\Zipper $zip */
        $ext_dll_found = false;
        $ext_names = $this->getMultiExtensionNames();
        foreach ($ext_names as $ext_name) {
            $dll_file = $build_dir . DIRECTORY_SEPARATOR . 'php_' . $ext_name . '.dll';

            if (!file_exists($dll_file)) {
                continue;
            }
            $ext_dll_found = true;
            $zip->addFileWithoutPath($dll_file);

            $pdb_file = $build_dir . DIRECTORY_SEPARATOR . 'php_' . $ext_name . '.pdb';
            if (file_exists($pdb_file)) {
                $zip->addFileWithoutPath($pdb_file);
            }
        }

        if (!$ext_dll_found) {
            throw new Exception("Couldn't find extension DLL");
        }

        $zip->addFileWithoutPath($composer_json);
        $zip->addFileWithoutPath($license);
        if ($readme) {
            $zip->addFileWithoutPath($readme);
        }
    }

    public function packLog(?Interfaces\Package\Build $build = null)
    {
        if (!$build) {
            $build = $this->build;
        }

        $path = $this->getZipBaseName($build) . '-logs.zip';

        $build->packLog($path);

        return realpath($path);
    }

    public function getMultiExtensionNames()
    {
        $info = $this->build->getInfo();
        $ext_names = [$info['name']];

        /* config.w32 can contain multiple EXTENTION definitions, which would lead to
         multiple DLLs be built. */
        $config_w32_path = $this->build->getPackage()->getSourceDir() . DIRECTORY_SEPARATOR . 'config.w32';
        $config_w32 = file_get_contents($config_w32_path);
        $m = null;
        if (preg_match_all("/EXTENSION\\s*\\(\\s*('|\")([a-z0-9_]+)('|\")\\s*,/Sm", $config_w32, $m, PREG_SET_ORDER)) {
            foreach ($m as $r) {
                if (!in_array($r[2], $ext_names)) {
                    $ext_names[] = $r[2];
                }
            }
        }

        return $ext_names;
    }

    protected function composerJsonBak(Interfaces\Package $pkg, $restore = false)
    {
        $composer_json_orig = $pkg->getRootDir() . DIRECTORY_SEPARATOR . 'composer.json';
        $composer_json_bak = $pkg->getRootDir() . DIRECTORY_SEPARATOR . '.composer.json.orig';

        if ($restore) {
            if (file_exists($composer_json_bak)) {
                if (!copy($composer_json_bak, $composer_json_orig)) {
                    throw new Exception('Failed to restore composer.json');
                }
            }
        } else {
            if (file_exists($composer_json_orig)) {
                if (!copy($composer_json_orig, $composer_json_bak)) {
                    throw new Exception('Failed to backup composer.json');
                }
            }
        }
    }

    protected function readPackage($path)
    {
        $package = PackageJson::readPackage($path, $this->noConvert);
        $this->composerJsonBak($package);

        /* For the binary release, json badly need the version informatio
           to show the meta info. If there's ever a binary release support
           for other platforms, this will need to be done, probably. */
        (new Header\Version($package))->updateJSON();

        return $package;
    }

    protected function getZipBaseName(Interfaces\Package\Build $build)
    {
        $info = $build->getInfo();

        return 'php_' . $info['name'] . '-'
            . $info['version'] . '-'
            . $info['php_major'] . '.'
            . $info['php_minor'] . '-'
            . ($info['thread_safe'] ? 'ts' : 'nts') . '-'
            . $info['compiler'] . '-'
            . $info['arch'];
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command\Build;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;

class Unix extends Abstracts\Package\Build implements Interfaces\Package\Build
{
    public function prepare()
    {
        $this->phpize();
    }

    public function phpize()
    {
        $backCwd = getcwd();
        chdir($this->pkg->getSourceDir());

        $res = $this->runCommand('phpize');
        chdir($backCwd);
        if (!$res) {
            throw new Exception('phpize failed');
        }
    }

    public function configure($opts = null)
    {
        $backCwd = getcwd();
        chdir($this->tempDir);

        $configureOptions = $opts ?: $this->prepareConfigOpts();

        $res = $this->runCommand($this->pkg->getSourceDir() . '/configure ' . $configureOptions);
        chdir($backCwd);
        if (!$res) {
            throw new Exception('configure failed, see log at ' . $this->tempDir . '\config.log');
        }
    }

    public function make()
    {
        $backCwd = getcwd();
        chdir($this->tempDir);
        $res = $this->runCommand('make');
        chdir($backCwd);

        if (!$res) {
            throw new Exception('make failed');
        }
    }

    public function install()
    {
        $backCwd = getcwd();
        chdir($this->tempDir);
        $res = $this->runCommand('make install');
        chdir($backCwd);
        if (!$res) {
            throw new Exception('make install failed');
        }
    }

    public function getInfo()
    {
        return [];
    }

    protected function prepareConfigOpts()
    {
        $configureOptions = '';
        foreach ($this->options as $name => $option) {
            if ($option->type === 'enable') {
                $option->input === true ? 'enable' : 'disable';
            } elseif ($option->type == 'disable') {
                $option->input === false ? 'enable' : 'disable';
            } elseif ($option->type === 'with') {
                if ($option->input == 'yes' || $option->input == '1' || $option->type === true) {
                    $configureOptions .= ' --with-' . $name;
                } elseif ($option->input == 'no' || $option->input == '0' || $option->type === false) {
                    $configureOptions .= ' --without-' . $name;
                } else {
                    $configureOptions .= ' --with-' . $name . '=' . $option->input;
                }
            }
        }

        $this->appendPkgConfigureOptions($configureOptions);

        return $configureOptions;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command\Build;

use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Interfaces;
use Pickle\Engine;

class Windows extends Abstracts\Package\Build implements Interfaces\Package\Build
{
    public function prepare()
    {
        if (!file_exists('c:\\php-sdk\\bin')) {
            throw new Exception('PHP SDK not found');
        }
        putenv('path=c:\\php-sdk\\bin;' . getenv('path'));

        if (!$this->runCommand('phpsdk_setvars')) {
            throw new Exception('phpsdk_setvars failed');
        }

        $this->phpize();
    }

    public function phpize()
    {
        $backCwd = getcwd();
        chdir($this->pkg->getSourceDir());

        $res = $this->runCommand('phpize');
        chdir($backCwd);
        if (!$res) {
            throw new Exception('phpize failed');
        }
    }

    public function configure($opts = null)
    {
        /* duplicate src tree to do not pollute repo or src dir */
        $this->copySrcDir($this->pkg->getSourceDir(), $this->tempDir);
        $backCwd = getcwd();
        chdir($this->tempDir);

        $configureOptions = $opts ?: $this->prepareConfigOpts();

        $res = $this->runCommand($this->pkg->getSourceDir() . '/configure ' . $configureOptions);
        chdir($backCwd);
        if (!$res) {
            throw new Exception('configure failed, see log at ' . $this->tempDir . '\config.log');
        }

        /* This post check is required for the case when config.w32 doesn't
           bail out on error but silently disables an extension. In this
           case we won't see any bad exit status. */
        $opts = $this->pkg->getConfigureOptions();
        $ext = key($opts);
        if (preg_match(',\|\s+' . preg_quote($ext) . '\s+\|\s+shared\s+\|,Sm', $this->getlog('configure')) < 1) {
            throw new Exception("failed to configure the '{$ext}' extension");
        }
    }

    public function make()
    {
        $backCwd = getcwd();
        chdir($this->tempDir);
        $res = $this->runCommand('nmake');
        chdir($backCwd);

        if (!$res) {
            throw new Exception('nmake failed');
        }
    }

    public function install()
    {
        $backCwd = getcwd();
        chdir($this->tempDir);

        /* Record the produced DLL filenames. */
        $files = (array) glob('*/*/php_*.dll');
        $files = array_merge($files, glob('*/php_*.dll'));
        $dlls = [];
        foreach ($files as $file) {
            $dlls[] = basename($file);
        }

        $res = $this->runCommand('nmake install');
        chdir($backCwd);
        if (!$res) {
            throw new Exception('nmake install failed');
        }

        $ini = \Pickle\Engine\Ini::factory(Engine::factory());
        $ini->updatePickleSection($dlls);
    }

    public function getInfo()
    {
        $info = [];
        $info = array_merge($info, $this->getInfoFromPhpizeLog());
        $info = array_merge($info, $this->getInfoFromConfigureLog());
        $m = null;
        if (!preg_match(',(.+)/(.+),', $info['name'], $m)) {
            $info['vendor'] = null;
        } else {
            $info['name'] = $m[2];
            $info['vendor'] = $m[1];
        }

        return $info;
    }

    protected function prepareConfigOpts()
    {
        $configureOptions = '--enable-debug-pack';
        foreach ($this->options as $name => $option) {
            $decision = null;
            if ($option->type === 'enable') {
                $decision = $option->input === true ? 'enable' : 'disable';
            } elseif ($option->type == 'disable') {
                $decision = $option->input === false ? 'enable' : 'disable';
            }

            if ($decision !== null) {
                $configureOptions .= ' --' . $decision . '-' . $name;
            }
        }

        $php_prefix = dirname(Engine::factory()->getPath());
        $configureOptions .= " --with-prefix={$php_prefix}";

        $this->appendPkgConfigureOptions($configureOptions);

        return $configureOptions;
    }

    protected function getInfoFromPhpizeLog()
    {
        $ret = [
            'php_major' => null,
            'php_minor' => null,
            'php_patch' => null,
        ];

        $tmp = $this->getLog('phpize');
        $m = null;
        if (!preg_match(",Rebuilding configure.js[\n\r\\d:]+\\s+(.+)[\n\r]+,", $tmp, $m)) {
            throw new Exception("Couldn't determine PHP development SDK path");
        }
        $sdk = $m[1];

        $ver_header = file_get_contents("{$sdk}/include/main/php_version.h");

        if (!preg_match(',PHP_MAJOR_VERSION\\s+(\\d+),', $ver_header, $m)) {
            throw new Exception("Couldn't determine PHP_MAJOR_VERSION");
        }
        $ret['php_major'] = $m[1];

        if (!preg_match(',PHP_MINOR_VERSION\\s+(\\d+),', $ver_header, $m)) {
            throw new Exception("Couldn't determine PHP_MINOR_VERSION");
        }
        $ret['php_minor'] = $m[1];

        if (!preg_match(',PHP_RELEASE_VERSION\\s+(\\d+),', $ver_header, $m)) {
            throw new Exception("Couldn't determine PHP_RELEASE_VERSION");
        }
        $ret['php_patch'] = $m[1];

        return $ret;
    }

    protected function getInfoFromConfigureLog()
    {
        $info = [
            'thread_safe' => null,
            'compiler' => null,
            'arch' => null,
            'version' => null,
            'name' => null,
            'is_release' => null,
        ];

        $tmp = $this->getLog('configure');
        $m = null;
        if (!preg_match(',Build type\\s+\\|\\s+([a-zA-Z]+),', $tmp, $m)) {
            throw new Exception("Couldn't determine the build thread safety");
        }
        $info['is_release'] = $m[1] === 'Release';

        if (!preg_match(',Thread Safety\\s+\\|\\s+([a-zA-Z]+),', $tmp, $m)) {
            throw new Exception("Couldn't determine the build thread safety");
        }
        $info['thread_safe'] = strtolower($m[1]) == 'yes';

        if (!preg_match(',Compiler\\s+\\|\\s+MSVC(\\d+),', $tmp, $m)) {
            throw new Exception('Currently only MSVC is supported');
        }
        $info['compiler'] = 'vc' . $m[1];

        if (!preg_match(',Architecture\\s+\\|\\s+([a-zA-Z0-9]+),', $tmp, $m)) {
            throw new Exception("Couldn't determine the build architecture");
        }
        $info['arch'] = $m[1];

        $info['version'] = $this->getPackage()->getPrettyVersion();
        $info['name'] = $this->getPackage()->getName();

        return $info;
    }

    /**
     * @param string $src
     */
    private function copySrcDir($src, $dest)
    {
        foreach (scandir($src) as $file) {
            $srcfile = rtrim($src, '/') . '/' . $file;
            $destfile = rtrim($dest, '/') . '/' . $file;
            if (!is_readable($srcfile)) {
                continue;
            }
            if ($file != '.' && $file != '..') {
                if (is_dir($srcfile)) {
                    if (!is_dir($destfile)) {
                        mkdir($destfile);
                    }
                    $this->copySrcDir($srcfile, $destfile);
                } else {
                    copy($srcfile, $destfile);
                }
            }
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Command;

use Closure;
use Composer\Package\Version\VersionParser;
use Phar;
use PharData;
use Pickle\Base\Interfaces;
use Pickle\Package\PHP\Util\PackageJson;
use Pickle\Package\Util\Header;

class Release implements Interfaces\Package\Release
{
    /**
     * @var \Pickle\Base\Interfaces\Package
     */
    protected $pkg;

    /**
     * @var Closure
     */
    protected $cb;

    /**
     * @var bool
     */
    protected $noConvert = false;

    /**
     * Constructor.
     *
     * @param string $path
     * @param Closure $cb
     * @param bool $noConvert
     */
    public function __construct($path, $cb = null, $noConvert = false)
    {
        $this->pkg = $this->readPackage($path);
        $this->cb = $cb;
        $this->noConvert = $noConvert;
    }

    /**
     * Create package.
     */
    public function create(array $args = [])
    {
        $archBasename = $this->pkg->getSimpleName() . '-' . $this->pkg->getPrettyVersion();

        /* Work around bug  #67417 [NEW]: ::compress modifies archive basename
        creates temp file and rename it */
        $tempName = getcwd() . '/pkl-tmp.tar';
        if (file_exists($tempName)) {
            unlink($tempName);
        }
        $arch = new PharData($tempName);
        $pkgDir = $this->pkg->getRootDir();

        foreach ($this->pkg->getFiles() as $file) {
            if (is_file($file)) {
                $name = str_replace($pkgDir, '', $file);
                $arch->addFile($file, $name);
            }
        }
        if (file_exists($tempName)) {
            @unlink($tempName . '.gz');
        }
        $arch->compress(Phar::GZ);
        unset($arch);

        rename($tempName . '.gz', $archBasename . '.tgz');
        unlink($tempName);

        if ($this->cb) {
            $cb = $this->cb;
            $cb($this->pkg);
        }
    }

    public function packLog()
    {
        /* pass, no logging seems to be happening here yet */
    }

    protected function readPackage($path)
    {
        $package = PackageJson::readPackage($path, $this->noConvert);
        $package->setRootDir(realpath($path));

        /* We're not adding any versions into the composer.json for the source release.
           Instead we just set the package version and that's it. The version is to be
           contained in the extension sources, so no need to maintain it more than once.
           */
        $version = new Header\Version($package);
        $package->replaceVersion((new VersionParser())->normalize($version), $version);

        return $package;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP;

use CallbackFilterIterator;
use Exception;
use Pickle\Base\Abstracts;
use Pickle\Base\Util\GitIgnore;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

class Package extends Abstracts\Package implements \Pickle\Base\Interfaces\Package
{
    /**
     * @var string Package's root directory
     */
    protected $path;

    /**
     * Get the package's root directory.
     *
     * @return string
     */
    public function getRootDir()
    {
        return $this->path;
    }

    /**
     * Get the package's root directory.
     *
     * @return string
     */
    public function getSourceDir()
    {
        $path = $this->getRootDir();
        $release = $path . DIRECTORY_SEPARATOR . $this->getPrettyName() . '-' . $this->getPrettyVersion();

        if (is_dir($release)) {
            $path = $release;
        }

        /* Do subdir search */
        if (!$this->extConfigIsIn($path)) {
            $path = $this->locateSourceDirByExtConfig($path);

            if ($path === null) {
                throw new Exception('config*.(m4|w32) not found');
            }
        }

        return $path;
    }

    /**
     * Set the package's source directory, containing config.m4/config.w32.
     *
     * @param string $path
     */
    public function setRootDir($path)
    {
        $this->path = $path;
    }

    public function setStability($stability)
    {
        $this->stability = $stability;
    }

    /**
     * @return array
     */
    public function getConfigureOptions()
    {
        $options = [];

        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            $config_file = $this->getSourceDir() . '/config.w32';

            if (!file_exists($config_file)) {
                throw new Exception('cnofig.w32 not found');
            }

            $config = file_get_contents($config_file);

            $options = array_merge(
                $this->fetchArgWindows('ARG_WITH', $config),
                $this->fetchArgWindows('ARG_ENABLE', $config)
            );
        } else {
            $configs = glob($this->getSourceDir() . '/' . 'config*.m4');

            if (!empty($configs)) {
                foreach ($configs as $config) {
                    $options = array_merge($options, $this->getConfigureOptionsFromFile($config));
                }
            }
        }

        return $options;
    }

    public function getConfigureOptionsFromFile($file)
    {
        $config = file_get_contents($file);

        return array_merge(
            $this->fetchArg('PHP_ARG_WITH', $config),
            $this->fetchArgAc('AC_ARG_WITH', $config),
            $this->fetchArg('PHP_ARG_ENABLE', $config),
            $this->fetchArgAc('AC_ARG_ENABLE', $config)
        );
    }

    /**
     * Get files, will not return gitignore files.
     *
     * @return CallbackFilterIterator
     */
    public function getFiles()
    {
        return new CallbackFilterIterator(
            new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($this->getSourceDir())
            ),
            new GitIgnore($this)
        );
    }

    public function getVersionFromHeader()
    {
        $headers = glob($this->path . DIRECTORY_SEPARATOR . '*.h');
        $version_define = 'PHP_' . strtoupper($this->getSimpleName()) . '_VERSION';
        foreach ($headers as $header) {
            $contents = @file_get_contents($header);
            if (!$contents) {
                throw new Exception("Cannot read header <{$header}>");
            }
            $pos_version = strpos($contents, $version_define);
            if ($pos_version !== false) {
                $nl = strpos($contents, "\n", $pos_version);
                $version_line = trim(substr($contents, $pos_version, $nl - $pos_version));
                [$version_define, $version] = explode(' ', $version_line);
                $version = trim(str_replace('"', '', $version));
                break;
            }
        }
        if (empty($version)) {
            throw new Exception('No ' . $version_define . ' can be found');
        }

        return [trim($version_define), $version];
    }

    /**
     * @param string $which
     * @param string $config
     *
     * @return array
     */
    protected function fetchArgWindows($which, $config)
    {
        $next = 0;
        $options = [];
        $type = strpos($which, 'ENABLE') !== false ? 'enable' : 'with';
        while (false !== ($s = strpos($config, $which, $next))) {
            $s = strpos($config, '(', $s);
            $e = strpos($config, ')', $s + 1);
            $option = substr($config, $s + 1, $e - $s);

            $elems = explode(',', $option);
            array_walk($elems, function (&$a) {
                $a = str_replace([')', "'"], ['', ''], $a);
                $a = trim($a);
            });

            @[$name, $prompt, $default] = $elems;
            $name = str_replace('"', '', $name);
            $options[$name] = (object) [
                'prompt' => $prompt,
                'type' => $type,
                'default' => $default,
            ];
            $next = $e + 1;
        }

        return $options;
    }

    protected function isWithOrEnable($option, $type)
    {
        if ($type == 'enable') {
            $default = (strpos($option, '-disable-') !== false) ? true : false;
        } elseif ($type == 'with') {
            $default = (strpos($option, '-without-') !== false) ? true : false;
        }
        return $default;
    }

    /**
     * @param string $which
     * @param string $config
     *
     * @return array
     */
    protected function fetchArgAc($which, $config)
    {
        $next = 0;
        $options = [];
        $type = strpos($which, 'ENABLE') !== false ? 'enable' : 'with';
        while (false !== ($s = strpos($config, $which, $next))) {
            $default = true;
            $s = strpos($config, '(', $s);
            $e = strpos($config, ')', $s + 1);
            $option = substr($config, $s + 1, $e - $s);

            $default = $this->isWithOrEnable($option, $type);
            [$name, $desc] = explode(',', $option);

            $desc = preg_replace('/\s+/', ' ', trim($desc));
            $desc = trim(substr($desc, 1, strlen($desc) - 2));
            $s_a = strpos($desc, ' ');
            $desc = trim(substr($desc, $s_a));

            $options[$name] = (object) [
                'prompt' => $desc,
                'type' => $type,
                'default' => $default,
            ];

            $next = $e + 1;
        }

        return $options;
    }

    /**
     * @param string $which
     * @param string $config
     *
     * @return array
     */
    protected function fetchArg($which, $config)
    {
        $next = 0;
        $options = [];

        $type = strpos($which, 'ENABLE') !== false ? 'enable' : 'with';
        while (false !== ($s = strpos($config, $which, $next))) {
            $default = 'y';
            $s = strpos($config, '(', $s);
            $e = strpos($config, ')', $s + 1);
            $option = substr($config, $s + 1, $e - $s);

            [$name, $desc] = explode(',', $option);

            /* Description can be part of the 3rd argument */
            if (empty($desc) || $desc === '[]') {
                [$name, , $desc] = explode(',', $option);
                $desc = preg_replace('/\s+/', ' ', trim($desc));
                $desc = trim(substr($desc, 1, strlen($desc) - 2));
                $desc = trim(str_replace(['[', ']'], ['', ''], $desc));
                $s_a = strpos($desc, ' ');
                $desc = trim(substr($desc, $s_a));
            }

            $default = $this->isWithOrEnable($option, $type);
            $name = str_replace(['[', ']'], ['', ''], $name);
            $options[$name] = (object) [
                'prompt' => trim($desc),
                'type' => $type,
                'default' => $default,
            ];
            $next = $e + 1;
        }

        return $options;
    }

    protected function extConfigIsIn($path)
    {
        if (defined('PHP_WINDOWS_VERSION_MAJOR') !== false) {
            return file_exists(realpath($path) . DIRECTORY_SEPARATOR . 'config.w32');
        }
        $r = glob("{$path}/config*.m4");

        return is_array($r) && !empty($r);
    }

    protected function locateSourceDirByExtConfig($path)
    {
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($it as $fl_obj) {
            if ($fl_obj->isFile() && preg_match(',config*.(m4|w32),', $fl_obj->getBasename())) {
                return $fl_obj->getPath();
            }
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Util;

use InvalidArgumentException;
use RuntimeException;
use StdClass;

class ConvertChangeLog
{
    private $path;

    private $changelog;

    /**
     * @param string $path
     */
    public function __construct($path)
    {
        if (is_file($path) === false) {
            throw new InvalidArgumentException('File not found: ' . $path);
        }

        $this->path = $path;
    }

    public function parse()
    {
        $xml = @simplexml_load_file($this->path);

        $changelog = [];
        $current = new StdClass();
        $current->date = $xml->date;
        $current->time = $xml->time;
        $current->version = new StdClass();
        $current->version->release = $xml->version->release;
        $current->stability = new StdClass();
        $current->stability->release = $xml->stability->release;
        $current->notes = $xml->notes;

        $changelog[] = $current;
        if (isset($xml->changelog->release)) {
            foreach ($xml->changelog->release as $release) {
                $changelog[] = $release;
            }
        }
        $this->changelog = $changelog;
    }

    public function generateReleaseFile()
    {
        if (empty($this->changelog)) {
            return;
        }

        $contents = '';
        foreach ($this->changelog as $cl) {
            $contents .= 'Version: ' . $cl->version->release . "\n"
                     . 'Date: ' . $cl->date . ' ' . $cl->time . "\n"
                     . 'Stability: ' . $cl->stability->release . "\n"
                     . "\n"
                     . 'notes: ' . $cl->notes . "\n"
                     . "\n"
                     . "\n"
                     . "\n";
        }

        if (file_put_contents(dirname($this->path) . DIRECTORY_SEPARATOR . 'RELEASES', $contents) === false) {
            throw new RuntimeException('cannot save RELEASE file in <' . dirname($this->path) . DIRECTORY_SEPARATOR . 'RELEASES>');
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Util\XML;

use Composer\Package\Loader\LoaderInterface;
use Exception;
use InvalidArgumentException;
use Pickle\Package\Util\Header;
use RuntimeException;
use SimpleXMLElement;

class Loader
{
    protected $loader;

    public function __construct(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    /**
     * @param string $path
     *
     * @return \Pickle\Base\Interfaces\Package
     */
    public function load($path, $versionOverride = null)
    {
        if (is_file($path) === false) {
            throw new InvalidArgumentException('File not found: ' . $path);
        }

        $xml = @simplexml_load_file($path);

        if ($xml === false) {
            $error = error_get_last();
            $exception = null;

            if ($error !== null) {
                $exception = new Exception($error['message'], $error['type']);
            }

            throw new RuntimeException('Failed to read ' . $path, 0, $exception);
        }

        $this->validate($xml);

        $package = [
            'name' => (string) $xml->name,
            'version' => (string) $versionOverride === '' ? (string) $xml->version->release : $versionOverride,
            'stability' => (string) $xml->stability->release,
            'description' => (string) $xml->summary,
        ];

        if (!isset($xml->providesextension)) {
            throw new Exception('not a PHP extension package.xml, providesextension tag missing');
        }

        $authors = [];
        foreach ([$xml->lead, $xml->developer, $xml->contributor, $xml->helper] as $devs) {
            foreach ($devs as $dev) {
                $authors[] = $dev;
            }
        }

        if (empty($authors) === false) {
            $package['authors'] = [];

            foreach ($authors as $author) {
                $tmp = [
                    'name' => trim((string) $author->name),
                    'email' => trim((string) $author->email),
                ];
                if ($tmp['name'] === '') {
                    unset($tmp['name']);
                }
                if ($tmp['email'] === '') {
                    unset($tmp['email']);
                }
                if ($tmp !== []) {
                    $package['authors'][] = $tmp;
                }
            }
        }

        $opts = $configureOptions = [];

        if (isset($xml->extsrcrelease->configureoption)) {
            $opts = $xml->extsrcrelease->configureoption;
        }

        foreach ($opts as $opt) {
            $name = trim($opt['name']);
            $default = trim($opt['default']);
            $prompt = trim($opt['prompt']);

            $configureOptions[$name] = [
                'default' => $default,
                'prompt' => $prompt,
            ];
        }

        if (empty($configureOptions) === false) {
            $package['extra'] = ['configure-options' => $configureOptions];
        }

        if (isset($xml->license)) {
            $package['license'] = (string) $xml->license;
        }
        $package['type'] = 'extension';

        $ret_pkg = $this->loader->load($package);
        if (!$ret_pkg) {
            throw new Exception("Package from '{$path}' failed to load.");
        }
        $ret_pkg->setRootDir(dirname($path));

        if ($versionOverride !== '') {
            $src_ver = $versionOverride ?? (string) new Header\Version($ret_pkg);
            if ($src_ver !== $ret_pkg->getPrettyVersion()) {
                throw new Exception("Version mismatch - '" . $src_ver . "' != '" . $ret_pkg->getPrettyVersion() . "' in source vs. XML");
            }
        }
        $ret_pkg->setType('extension');

        return $ret_pkg;
    }

    protected function validate(SimpleXMLElement $xml)
    {
        if (version_compare($xml['version'], '2.0') === -1) {
            throw new RuntimeException('Unsupported package.xml version, 2.0 or later only is supported');
        }

        if (!isset($xml->providesextension)) {
            throw new RuntimeException('Only extension packages are supported');
        }
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Util;

use Exception;
use Pickle\Package;
use RuntimeException;

class PackageJson
{
    public static function readPackage($path, $noconvert)
    {
        $jsonLoader = new Package\Util\JSON\Loader(new Package\Util\Loader());
        $package = null;

        if (file_exists($path . DIRECTORY_SEPARATOR . 'composer.json')) {
            $package = $jsonLoader->load($path . DIRECTORY_SEPARATOR . 'composer.json');
        }

        if ($package === null && $noconvert) {
            throw new RuntimeException('XML package are not supported. Please convert it before install');
        }

        if ($package === null) {
            try {
                $pkgXml = new PackageXml($path);
                $pkgXml->dump();

                $jsonPath = $pkgXml->getJsonPath();

                $package = $jsonLoader->load($jsonPath);
            } catch (Exception $e) {
                /* pass for now, be compatible */
            }
        }

        if ($package === null) {
            /* Just ensure it's correct, */
            throw new Exception("Couldn't read package info at '{$path}'");
        }

        $package->setRootDir(realpath($path));

        return $package;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
<?php

/*
 * Pickle
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2015-2015, Pickle community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Pickle\Package\PHP\Util;

use Exception;
use InvalidArgumentException;
use Pickle\Package;
use Pickle\Package\Util\Header;
use Pickle\Package\Util\JSON\Dumper;

class PackageXml
{
    protected $xmlPath;

    protected $jsonPath;

    protected $package;

    protected $versionOverride;

    public function __construct($path, $versionOverride = null)
    {
        $names = ['package2.xml', 'package.xml'];

        foreach ($names as $fl) {
            $xml = $path . DIRECTORY_SEPARATOR . $fl;
            if (is_file($xml) === true) {
                $this->xmlPath = $xml;
                break;
            }
        }

        if (!$this->xmlPath) {
            throw new InvalidArgumentException("The path '{$path}' doesn't contain package.xml");
        }

        $this->jsonPath = $path . DIRECTORY_SEPARATOR . 'composer.json';
        $this->versionOverride = $versionOverride;
    }

    public function load()
    {
        $loader = new Package\PHP\Util\XML\Loader(new Package\Util\Loader());
        $this->package = $loader->load($this->xmlPath, $this->versionOverride);

        if (!$this->package) {
            throw new Exception("Failed to load '{$this->xmlPath}'");
        }

        return $this->package;
    }

    public function convertChangeLog()
    {
        if (!$this->package) {
            $this->load();
        }

        $convertCl = new ConvertChangeLog($this->xmlPath);
        $convertCl->parse();
        $convertCl->generateReleaseFile();
    }

    public function dump($fname = null)
    {
        if (!$this->package) {
            $this->load();
        }

        if ($fname) {
            $this->jsonPath = $fname;
        }

        if ($this->versionOverride !== '') {
            $version = $this->versionOverride ?? (string) new Header\Version($this->package);
            if ($version !== $this->package->getPrettyVersion()) {
                throw new Exception("Version mismatch - '" . $version . "' != '" . $this->package->getVersion() . '. in source vs JSON');
            }
        }

        $dumper = new Dumper();
        $dumper->dumpToFile($this->package, $this->jsonPath, true);
    }

    public function getPackage()
    {
        if (!$this->package) {
            $this->load();
        }

        return $this->package;
    }

    public function getXmlPath()
    {
        return $this->xmlPath;
    }

    public function getJsonPath()
    {
        return $this->jsonPath;
    }
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","name":"Package","type":"object","additionalProperties":false,"required":["name","description"],"properties":{"name":{"type":"string","description":"Package name, including 'vendor-name\/' prefix."},"type":{"description":"Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.","type":"string","pattern":"^[a-z0-9-]+$"},"target-dir":{"description":"DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.","type":"string"},"description":{"type":"string","description":"Short package description."},"keywords":{"type":"array","items":{"type":"string","description":"A tag\/keyword that this package relates to."}},"homepage":{"type":"string","description":"Homepage URL for the project.","format":"uri"},"readme":{"type":"string","description":"Relative path to the readme document."},"version":{"type":"string","description":"Package version, see https:\/\/getcomposer.org\/doc\/04-schema.md#version for more info on valid schemes.","pattern":"^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?|^dev-"},"time":{"type":"string","description":"Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format."},"license":{"type":["string","array"],"description":"License name. Or an array of license names."},"authors":{"$ref":"#\/definitions\/authors"},"require":{"type":"object","description":"This is a hash of package name (keys) and version constraints (values) that are required to run this package.","additionalProperties":{"type":"string"}},"replace":{"type":"object","description":"This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.","additionalProperties":{"type":"string"}},"conflict":{"type":"object","description":"This is a hash of package name (keys) and version constraints (values) that conflict with this package.","additionalProperties":{"type":"string"}},"provide":{"type":"object","description":"This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.","additionalProperties":{"type":"string"}},"require-dev":{"type":"object","description":"This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).","additionalProperties":{"type":"string"}},"suggest":{"type":"object","description":"This is a hash of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).","additionalProperties":{"type":"string"}},"config":{"type":"object","description":"Composer options.","properties":{"process-timeout":{"type":"integer","description":"The timeout in seconds for process executions, defaults to 300 (5mins)."},"use-include-path":{"type":"boolean","description":"If true, the Composer autoloader will also look for classes in the PHP include path."},"preferred-install":{"type":["string","object"],"description":"The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or a hash of {\"pattern\": \"preference\"}."},"notify-on-install":{"type":"boolean","description":"Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true."},"github-protocols":{"type":"array","description":"A list of protocols to use for github.com clones, in priority order, defaults to [\"git\", \"https\", \"http\"].","items":{"type":"string"}},"github-oauth":{"type":"object","description":"A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.","additionalProperties":{"type":"string"}},"gitlab-oauth":{"type":"object","description":"A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.","additionalProperties":{"type":"string"}},"gitlab-token":{"type":"object","description":"A hash of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.","additionalProperties":{"type":"string"}},"bearer":{"type":"object","description":"A hash of domain name => bearer authentication token, for example {\"example.com\":\"<token>\"}.","additionalProperties":{"type":"string"}},"disable-tls":{"type":"boolean","description":"Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini."},"secure-http":{"type":"boolean","description":"Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative."},"cafile":{"type":"string","description":"A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."},"capath":{"type":"string","description":"If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory."},"http-basic":{"type":"object","description":"A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.","additionalProperties":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","description":"The username used for HTTP Basic authentication"},"password":{"type":"string","description":"The password used for HTTP Basic authentication"}}}},"store-auths":{"type":["string","boolean"],"description":"What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt."},"platform":{"type":"object","description":"This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.","additionalProperties":{"type":"string"}},"vendor-dir":{"type":"string","description":"The location where all packages are installed, defaults to \"vendor\"."},"bin-dir":{"type":"string","description":"The location where all binaries are linked, defaults to \"vendor\/bin\"."},"data-dir":{"type":"string","description":"The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes."},"cache-dir":{"type":"string","description":"The location where all caches are located, defaults to \"~\/.composer\/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows."},"cache-files-dir":{"type":"string","description":"The location where files (zip downloads) are cached, defaults to \"{$cache-dir}\/files\"."},"cache-repo-dir":{"type":"string","description":"The location where repo (git\/hg repo clones) are cached, defaults to \"{$cache-dir}\/repo\"."},"cache-vcs-dir":{"type":"string","description":"The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}\/vcs\"."},"cache-ttl":{"type":"integer","description":"The default cache time-to-live, defaults to 15552000 (6 months)."},"cache-files-ttl":{"type":"integer","description":"The cache time-to-live for files, defaults to the value of cache-ttl."},"cache-files-maxsize":{"type":["string","integer"],"description":"The cache max size for the files cache, defaults to \"300MiB\"."},"bin-compat":{"enum":["auto","full"],"description":"The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)."},"discard-changes":{"type":["string","boolean"],"description":"The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"."},"autoloader-suffix":{"type":"string","description":"Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated."},"optimize-autoloader":{"type":"boolean","description":"Always optimize when dumping the autoloader."},"prepend-autoloader":{"type":"boolean","description":"If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."},"classmap-authoritative":{"type":"boolean","description":"If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."},"apcu-autoloader":{"type":"boolean","description":"If true, the Composer autoloader will check for APCu and use it to cache found\/not-found classes when the extension is enabled, defaults to false."},"github-domains":{"type":"array","description":"A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].","items":{"type":"string"}},"github-expose-hostname":{"type":"boolean","description":"Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname."},"gitlab-domains":{"type":"array","description":"A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].","items":{"type":"string"}},"use-github-api":{"type":"boolean","description":"Defaults to true.  If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."},"archive-format":{"type":"string","description":"The default archiving format when not provided on cli, defaults to \"tar\"."},"archive-dir":{"type":"string","description":"The default archive path when not provided on cli, defaults to \".\"."},"htaccess-protect":{"type":"boolean","description":"Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories."},"sort-packages":{"type":"boolean","description":"Defaults to false. If set to true, Composer will sort packages when adding\/updating a new dependency."},"lock":{"type":"boolean","description":"Defaults to true. If set to false, Composer will not create a composer.lock file."}}},"extra":{"type":["object","array"],"description":"Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.","additionalProperties":true},"autoload":{"$ref":"#\/definitions\/autoload"},"autoload-dev":{"type":"object","description":"Description of additional autoload rules for development purpose (eg. a test suite).","properties":{"psr-0":{"type":"object","description":"This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.","additionalProperties":{"type":["string","array"],"items":{"type":"string"}}},"psr-4":{"type":"object","description":"This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.","additionalProperties":{"type":["string","array"],"items":{"type":"string"}}},"classmap":{"type":"array","description":"This is an array of directories that contain classes to be included in the class-map generation process."},"files":{"type":"array","description":"This is an array of files that are always required on every request."}}},"archive":{"type":["object"],"description":"Options for creating package archives for distribution.","properties":{"exclude":{"type":"array","description":"A list of patterns for paths to exclude or include if prefixed with an exclamation mark."}}},"repositories":{"type":["object","array"],"description":"A set of additional repositories where packages can be found.","additionalProperties":{"anyOf":[{"$ref":"#\/definitions\/repository"},{"type":"boolean","enum":[false]}]},"items":{"anyOf":[{"$ref":"#\/definitions\/repository"},{"type":"object","additionalProperties":{"type":"boolean","enum":[false]},"minProperties":1,"maxProperties":1}]}},"minimum-stability":{"type":["string"],"description":"The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.","pattern":"^dev|alpha|beta|rc|RC|stable$"},"prefer-stable":{"type":["boolean"],"description":"If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages."},"bin":{"type":["string","array"],"description":"A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).","items":{"type":"string"}},"include-path":{"type":["array"],"description":"DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.","items":{"type":"string"}},"scripts":{"type":["object"],"description":"Script listeners that will be executed before\/after some events.","properties":{"pre-install-cmd":{"type":["array","string"],"description":"Occurs before the install command is executed, contains one or more Class::method callables or shell commands."},"post-install-cmd":{"type":["array","string"],"description":"Occurs after the install command is executed, contains one or more Class::method callables or shell commands."},"pre-update-cmd":{"type":["array","string"],"description":"Occurs before the update command is executed, contains one or more Class::method callables or shell commands."},"post-update-cmd":{"type":["array","string"],"description":"Occurs after the update command is executed, contains one or more Class::method callables or shell commands."},"pre-status-cmd":{"type":["array","string"],"description":"Occurs before the status command is executed, contains one or more Class::method callables or shell commands."},"post-status-cmd":{"type":["array","string"],"description":"Occurs after the status command is executed, contains one or more Class::method callables or shell commands."},"pre-package-install":{"type":["array","string"],"description":"Occurs before a package is installed, contains one or more Class::method callables or shell commands."},"post-package-install":{"type":["array","string"],"description":"Occurs after a package is installed, contains one or more Class::method callables or shell commands."},"pre-package-update":{"type":["array","string"],"description":"Occurs before a package is updated, contains one or more Class::method callables or shell commands."},"post-package-update":{"type":["array","string"],"description":"Occurs after a package is updated, contains one or more Class::method callables or shell commands."},"pre-package-uninstall":{"type":["array","string"],"description":"Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands."},"post-package-uninstall":{"type":["array","string"],"description":"Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands."},"pre-autoload-dump":{"type":["array","string"],"description":"Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands."},"post-autoload-dump":{"type":["array","string"],"description":"Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands."},"post-root-package-install":{"type":["array","string"],"description":"Occurs after the root-package is installed, contains one or more Class::method callables or shell commands."},"post-create-project-cmd":{"type":["array","string"],"description":"Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands."}}},"scripts-descriptions":{"type":["object"],"description":"Descriptions for custom commands, shown in console help.","additionalProperties":{"type":"string"}},"support":{"type":"object","properties":{"email":{"type":"string","description":"Email address for support.","format":"email"},"issues":{"type":"string","description":"URL to the issue tracker.","format":"uri"},"forum":{"type":"string","description":"URL to the forum.","format":"uri"},"wiki":{"type":"string","description":"URL to the wiki.","format":"uri"},"irc":{"type":"string","description":"IRC channel for support, as irc:\/\/server\/channel.","format":"uri"},"chat":{"type":"string","description":"URL to the support chat.","format":"uri"},"source":{"type":"string","description":"URL to browse or download the sources.","format":"uri"},"docs":{"type":"string","description":"URL to the documentation.","format":"uri"},"rss":{"type":"string","description":"URL to the RSS feed.","format":"uri"}}},"funding":{"type":"array","description":"A list of options to fund the development and maintenance of the package.","items":{"type":"object","properties":{"type":{"type":"string","description":"Type of funding or platform through which funding is possible."},"url":{"type":"string","description":"URL to a website with details on funding and a way to fund the package.","format":"uri"}}}},"non-feature-branches":{"type":["array"],"description":"A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.","items":{"type":"string"}},"abandoned":{"type":["boolean","string"],"description":"Indicates whether this package has been abandoned, it can be boolean or a package name\/URL pointing to a recommended alternative. Defaults to false."},"_comment":{"type":["array","string"],"description":"A key to store comments in"}},"definitions":{"authors":{"type":"array","description":"List of authors that contributed to the package. This is typically the main maintainers, not the full list.","items":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","description":"Full name of the author."},"email":{"type":"string","description":"Email address of the author.","format":"email"},"homepage":{"type":"string","description":"Homepage URL for the author.","format":"uri"},"role":{"type":"string","description":"Author's role in the project."}}}},"autoload":{"type":"object","description":"Description of how the package can be autoloaded.","properties":{"psr-0":{"type":"object","description":"This is a hash of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.","additionalProperties":{"type":["string","array"],"items":{"type":"string"}}},"psr-4":{"type":"object","description":"This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.","additionalProperties":{"type":["string","array"],"items":{"type":"string"}}},"classmap":{"type":"array","description":"This is an array of directories that contain classes to be included in the class-map generation process."},"files":{"type":"array","description":"This is an array of files that are always required on every request."},"exclude-from-classmap":{"type":"array","description":"This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"\/test\/\", \"\/tests\/\", \"\/Tests\/\"]"}}},"repository":{"type":"object","anyOf":[{"$ref":"#\/definitions\/composer-repository"},{"$ref":"#\/definitions\/vcs-repository"},{"$ref":"#\/definitions\/path-repository"},{"$ref":"#\/definitions\/artifact-repository"},{"$ref":"#\/definitions\/pear-repository"},{"$ref":"#\/definitions\/package-repository"}]},"composer-repository":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["composer"]},"url":{"type":"string"},"options":{"type":"object","additionalProperties":true},"allow_ssl_downgrade":{"type":"boolean"},"force-lazy-providers":{"type":"boolean"}}},"vcs-repository":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["vcs","github","git","gitlab","git-bitbucket","hg","hg-bitbucket","fossil","perforce","svn"]},"url":{"type":"string"},"no-api":{"type":"boolean"},"secure-http":{"type":"boolean"},"svn-cache-credentials":{"type":"boolean"},"trunk-path":{"type":["string","boolean"]},"branches-path":{"type":["string","boolean"]},"tags-path":{"type":["string","boolean"]},"package-path":{"type":"string"},"depot":{"type":"string"},"branch":{"type":"string"},"unique_perforce_client_name":{"type":"string"},"p4user":{"type":"string"},"p4password":{"type":"string"}}},"path-repository":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["path"]},"url":{"type":"string"},"options":{"type":"object","properties":{"symlink":{"type":["boolean","null"]}},"additionalProperties":true}}},"artifact-repository":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["artifact"]},"url":{"type":"string"}}},"pear-repository":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["pear"]},"url":{"type":"string"},"vendor-alias":{"type":"string"}}},"package-repository":{"type":"object","required":["type","package"],"properties":{"type":{"type":"string","enum":["package"]},"package":{"oneOf":[{"$ref":"#\/definitions\/inline-package"},{"type":"array","items":{"$ref":"#\/definitions\/inline-package"}}]}}},"inline-package":{"type":"object","required":["name","version"],"properties":{"name":{"type":"string","description":"Package name, including 'vendor-name\/' prefix."},"type":{"type":"string"},"target-dir":{"description":"DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.","type":"string"},"description":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"homepage":{"type":"string","format":"uri"},"version":{"type":"string"},"time":{"type":"string"},"license":{"type":["string","array"]},"authors":{"$ref":"#\/definitions\/authors"},"require":{"type":"object","additionalProperties":{"type":"string"}},"replace":{"type":"object","additionalProperties":{"type":"string"}},"conflict":{"type":"object","additionalProperties":{"type":"string"}},"provide":{"type":"object","additionalProperties":{"type":"string"}},"require-dev":{"type":"object","additionalProperties":{"type":"string"}},"suggest":{"type":"object","additionalProperties":{"type":"string"}},"extra":{"type":["object","array"],"additionalProperties":true},"autoload":{"$ref":"#\/definitions\/autoload"},"archive":{"type":["object"],"properties":{"exclude":{"type":"array"}}},"bin":{"type":["string","array"],"description":"A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).","items":{"type":"string"}},"include-path":{"type":["array"],"description":"DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.","items":{"type":"string"}},"source":{"type":"object","required":["type","url","reference"],"properties":{"type":{"type":"string"},"url":{"type":"string"},"reference":{"type":"string"},"mirrors":{"type":"array"}}},"dist":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string"},"url":{"type":"string"},"reference":{"type":"string"},"shasum":{"type":"string"},"mirrors":{"type":"array"}}}},"additionalProperties":true}}}{"$schema":"http:\/\/json-schema.org\/draft-04\/schema#","description":"A representation of packages metadata.","type":"object","oneOf":[{"required":["packages"]},{"required":["providers"]},{"required":["provider-includes","providers-url"]}],"properties":{"packages":{"type":["object","array"],"description":"A hashmap of package names in the form of <vendor>\/<name>.","additionalProperties":{"$ref":"#\/definitions\/versions"}},"providers-url":{"type":"string","description":"Endpoint to retrieve provider data from, e.g. '\/p\/%package%$%hash%.json'."},"provider-includes":{"type":"object","description":"A hashmap of provider listings.","additionalProperties":{"$ref":"#\/definitions\/provider"}},"providers":{"type":"object","description":"A hashmap of package names in the form of <vendor>\/<name>.","additionalProperties":{"$ref":"#\/definitions\/provider"}},"notify-batch":{"type":"string","description":"Endpoint to call after multiple packages have been installed, e.g. '\/downloads\/'."},"search":{"type":"string","description":"Endpoint that provides search capabilities, e.g. '\/search.json?q=%query%&type=%type%'."},"warning":{"type":"string","description":"A message that will be output by Composer as a warning when this source is consulted."}},"definitions":{"versions":{"type":"object","description":"A hashmap of versions and their metadata.","additionalProperties":{"$ref":"#\/definitions\/version"}},"version":{"type":"object","oneOf":[{"$ref":"#\/definitions\/package"},{"$ref":"#\/definitions\/metapackage"}]},"package-base":{"properties":{"name":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"},"version_normalized":{"type":"string","description":"Normalized version, optional but can save computational time on client side."},"autoload":{"type":"object"},"require":{"type":"object"},"replace":{"type":"object"},"conflict":{"type":"object"},"provide":{"type":"object"},"time":{"type":"string"}},"additionalProperties":true},"package":{"allOf":[{"$ref":"#\/definitions\/package-base"},{"properties":{"dist":{"type":"object"},"source":{"type":"object"}}},{"oneOf":[{"required":["name","version","source"]},{"required":["name","version","dist"]}]}]},"metapackage":{"allOf":[{"$ref":"#\/definitions\/package-base"},{"properties":{"type":{"type":"string","enum":["metapackage"]}},"required":["name","version","type"]}]},"provider":{"type":"object","properties":{"sha256":{"type":"string","description":"Hash value that can be used to validate the resource."}}}}}##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue Oct 26 03:12:05 2021 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt).  This file can be found in the mozilla source tree:
## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.28.
## SHA256: bb36818a81feaa4cca61101e6d6276cd09e972efcb08112dfed846918ca41d7f
##


GlobalSign Root CA
==================
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----

GlobalSign Root CA - R2
=======================
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----

Entrust.net Premium 2048 Secure Server CA
=========================================
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----

Baltimore CyberTrust Root
=========================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----

Comodo AAA Services root
========================
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----

QuoVadis Root CA 2
==================
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----

QuoVadis Root CA 3
==================
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----

Security Communication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
FL39vmwLAw==
-----END CERTIFICATE-----

XRamp Global CA Root
====================
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----

Go Daddy Class 2 CA
===================
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
vZ8=
-----END CERTIFICATE-----

Starfield Class 2 CA
====================
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----

DigiCert Assured ID Root CA
===========================
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----

DigiCert Global Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

DigiCert High Assurance EV Root CA
==================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
-----END CERTIFICATE-----

SwissSign Gold CA - G2
======================
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----

SwissSign Silver CA - G2
========================
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----

SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----

Secure Global CA
================
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----

COMODO Certification Authority
==============================
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
-----END CERTIFICATE-----

Network Solutions Certificate Authority
=======================================
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
-----END CERTIFICATE-----

COMODO ECC Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----

Certigna
========
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

Cybertrust Global Root
======================
-----BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----END CERTIFICATE-----

ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----

certSIGN ROOT CA
================
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----

NetLock Arany (Class Gold) Főtanúsítvány
========================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

Hongkong Post Root CA 1
=======================
-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
-----END CERTIFICATE-----

SecureSign RootCA11
===================
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----

Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
LXpUq3DDfSJlgnCW
-----END CERTIFICATE-----

GlobalSign Root CA - R3
=======================
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
kpeDMdmztcpHWD9f
-----END CERTIFICATE-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----

Izenpe.com
==========
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----

Go Daddy Root Certificate Authority - G2
========================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
-----END CERTIFICATE-----

Starfield Root Certificate Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----

Starfield Services Root Certificate Authority - G2
==================================================
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
-----END CERTIFICATE-----

AffirmTrust Commercial
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----

AffirmTrust Networking
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----

AffirmTrust Premium
===================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
-----END CERTIFICATE-----

AffirmTrust Premium ECC
=======================
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
eQ==
-----END CERTIFICATE-----

Certum Trusted Network CA
=========================
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----

TWCA Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----

Security Communication RootCA2
==============================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
+T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----

EC-ACC
======
-----BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
5EI=
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2011
=======================================================
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----

Actalis Authentication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----

Buypass Class 2 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
rJgWVqA=
-----END CERTIFICATE-----

Buypass Class 3 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
Cp/HuZc=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 3
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 2009
==============================
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 EV 2009
=================================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
w9y4AyHqnxbxLFS1
-----END CERTIFICATE-----

CA Disig Root R2
================
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----

ACCVRAIZ1
=========
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
EfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----

TWCA Global Root CA
===================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
-----END CERTIFICATE-----

TeliaSonera Root CA v1
======================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----

E-Tugra Certification Authority
===============================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w
DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls
ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw
NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx
QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl
cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD
DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd
hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K
CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g
ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ
BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0
E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz
rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq
jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5
dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB
/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG
MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK
kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO
XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807
VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo
a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc
dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV
KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT
Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0
8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G
C7TbO6Orb1wdtn7os4I07QZcJA==
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 2
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
-----END CERTIFICATE-----

Atos TrustedRoot 2011
=====================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

QuoVadis Root CA 1 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE
PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm
PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6
Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN
ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l
g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV
7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX
9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f
iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg
t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI
hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3
GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct
Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP
+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh
3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa
wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6
O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0
FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV
hMJKzRwuJIczYOXD
-----END CERTIFICATE-----

QuoVadis Root CA 2 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh
ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY
NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t
oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o
MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l
V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo
L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ
sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD
6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh
lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI
hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K
pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9
x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz
dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X
U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw
mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD
zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN
JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr
O3jtZsSOeWmD3n+M
-----END CERTIFICATE-----

QuoVadis Root CA 3 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286
IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL
Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe
6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3
I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U
VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7
5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi
Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM
dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt
rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI
hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS
t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ
TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du
DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib
Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD
hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX
0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW
dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2
PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----

DigiCert Assured ID Root G2
===========================
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw
MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH
35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq
bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw
VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP
YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn
lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO
w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv
0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz
d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW
hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M
jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----

DigiCert Assured ID Root G3
===========================
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb
RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs
KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF
UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy
YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy
1vUhZscv6pZjamVFkpUBtA==
-----END CERTIFICATE-----

DigiCert Global Root G2
=======================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx
MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ
kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO
3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV
BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM
UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu
5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr
F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U
WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH
QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/
iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

DigiCert Global Root G3
=======================
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD
VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw
MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O
YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp
Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y
3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34
VOKa5Vt8sycX
-----END CERTIFICATE-----

DigiCert Trusted Root G4
========================
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw
HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp
pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o
k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa
vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm
mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH
dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8
oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY
ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr
yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy
7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah
ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN
5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb
/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa
5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK
G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP
82Z+
-----END CERTIFICATE-----

COMODO RSA Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn
dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ
FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+
5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG
x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX
2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL
OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3
sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C
GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5
WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt
rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+
nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg
tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW
sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp
pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA
zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq
ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52
7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I
LaZRfyHBNVOFBkpdn627G190
-----END CERTIFICATE-----

USERTrust RSA Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz
0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j
Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O
+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq
/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE
Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM
lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8
yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW
FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ
7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ
Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM
8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi
FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi
yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c
J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw
sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx
Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl
OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV
MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF
JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q=
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

Staat der Nederlanden EV Root CA
================================
-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M
MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl
cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk
SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW
O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r
0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8
Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV
XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr
08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV
0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd
74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx
fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa
ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu
c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq
5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN
b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN
f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi
5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4
WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK
DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy
eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg==
-----END CERTIFICATE-----

IdenTrust Commercial Root CA 1
==============================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS
b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES
MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB
IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld
hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/
mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi
1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C
XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl
3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy
NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV
WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg
xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix
uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI
hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg
ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt
ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV
YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX
feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro
kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe
2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz
Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R
cGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----

IdenTrust Public Sector Root CA 1
=================================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv
ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV
UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS
b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy
P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6
Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI
rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf
qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS
mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn
ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh
LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v
iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL
4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw
DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A
mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt
GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt
m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx
NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4
Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI
ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC
ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ
3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----

Entrust Root Certification Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy
bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug
b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw
HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx
OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP
/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz
HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU
s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y
TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx
AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6
0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z
iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi
nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+
vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO
e4pIb4tF9g==
-----END CERTIFICATE-----

Entrust Root Certification Authority - EC1
==========================================
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx
FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn
YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs
LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg
dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy
AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef
9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h
vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8
kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----

CFCA EV ROOT
============
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE
CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB
IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw
MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD
DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV
BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD
7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN
uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW
ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7
xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f
py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K
gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol
hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ
tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf
BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q
ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua
4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG
E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX
BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn
aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy
PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX
kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----

OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG
EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw
MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds
b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX
scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP
rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk
9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o
Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg
GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD
dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0
VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui
HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----

SZAFIR ROOT CA2
===============
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV
BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ
BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD
VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q
qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK
DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE
2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ
ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi
ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC
AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5
O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67
oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul
4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6
+/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----

Certum Trusted Network CA 2
===========================
-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE
BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1
bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ
TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB
IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9
7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o
CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b
Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p
uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130
GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ
9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB
Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye
hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM
BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW
Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA
L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo
clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM
pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb
w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo
J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm
ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX
is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7
zAYspsbiDrW5viSP
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2015
=======================================================
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT
BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0
aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx
MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg
QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV
BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw
MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv
bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh
iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+
6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd
FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr
i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F
GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2
fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu
iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI
hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+
D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM
d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y
d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn
82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb
davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F
Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt
J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa
JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q
p/UsQu0yrbYhnr68
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions ECC RootCA 2015
===========================================================
-----BEGIN CERTIFICATE-----
MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0
aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw
MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj
IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD
VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290
Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP
dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK
Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA
GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn
dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----

ISRG Root X1
============
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM
================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT
AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw
MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD
TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf
qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr
btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL
j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou
08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw
WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT
tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ
47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC
ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa
i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o
dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s
D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ
j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT
Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW
+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7
Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d
8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm
5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG
rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----

Amazon Root CA 1
================
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1
MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH
FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ
gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t
dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce
VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3
DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM
CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy
8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa
2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2
xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----

Amazon Root CA 2
================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1
MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4
kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp
N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9
AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd
fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx
kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS
btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0
Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN
c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+
3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw
DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA
A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE
YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW
xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ
gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW
aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV
Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3
KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi
JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=
-----END CERTIFICATE-----

Amazon Root CA 3
================
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB
f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr
Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43
rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc
eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==
-----END CERTIFICATE-----

Amazon Root CA 4
================
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN
/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri
83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA
MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1
AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----

TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
=============================================
-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT
D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr
IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g
TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp
ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD
VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt
c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth
bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11
IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8
6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc
wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0
3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9
WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU
ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc
lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R
e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j
q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----

GDCA TrustAUTH R5 ROOT
======================
-----BEGIN CERTIFICATE-----
MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw
BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD
DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow
YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs
AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p
OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr
pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ
9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ
xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM
R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ
D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4
oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx
9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9
H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35
6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd
+PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ
HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD
F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ
8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv
/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT
aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----

TrustCor RootCert CA-1
======================
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx
MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu
YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe
VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy
dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq
jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4
pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0
JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h
gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw
/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j
BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5
mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf
ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C
qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P
3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk=
-----END CERTIFICATE-----

TrustCor RootCert CA-2
======================
-----BEGIN CERTIFICATE-----
MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w
DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT
eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0
eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy
MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h
bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U
cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0
IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb
ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk
RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1
oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb
XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1
/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q
jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP
eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg
rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh
8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU
2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h
Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp
kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv
2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3
S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw
PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv
DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU
RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE
xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX
RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ
-----END CERTIFICATE-----

TrustCor ECA-1
==============
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw
N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5
MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y
IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR
MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23
xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc
p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+
fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj
YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL
f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u
/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F
hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs
J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC
jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g==
-----END CERTIFICATE-----

SSL.com Root Certification Authority RSA
========================================
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM
BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x
MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw
MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM
LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C
Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8
P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge
oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp
k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z
fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ
gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2
UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8
1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s
bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr
dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf
ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl
u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq
erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj
MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ
vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI
Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y
wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI
WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----

SSL.com Root Certification Authority ECC
========================================
-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv
BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy
MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+
8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR
hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT
jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW
e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z
5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority RSA R2
==============================================
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u
MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD
VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh
hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w
cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO
Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+
B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh
CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim
9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto
RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm
JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48
+qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp
qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1
++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx
Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G
guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz
OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7
CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq
lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR
rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1
hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX
9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority ECC
===========================================
-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy
BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw
MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM
LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy
3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O
BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe
5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GlobalSign Root CA - R6
=======================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX
R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i
YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss
grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE
3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF
vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM
PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+
azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O
WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy
CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP
0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN
b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV
HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0
lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY
BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym
Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr
3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1
0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T
uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK
oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t
JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----

OISTE WISeKey Global Root GC CA
===============================
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD
SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo
MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa
Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL
ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr
VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab
NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E
AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----

GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx
9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r
aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW
r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM
LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly
4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr
06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om
3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu
JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM
BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv
fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm
ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b
gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq
4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr
tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo
pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0
sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql
CFF1pkgl
-----END CERTIFICATE-----

GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk
k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo
7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI
m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm
dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu
ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz
cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl
aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy
5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM
BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ
+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw
c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da
WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r
n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu
Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ
7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs
gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld
o/DUhgkC
-----END CERTIFICATE-----

GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU
Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej
QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP
0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0
glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa
KaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----

GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa
6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj
QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV
2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI
N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x
zPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----

UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x
NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU
cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT
oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV
8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS
h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o
LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/
R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe
KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa
4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc
OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97
8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo
5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A
Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9
yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX
c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo
jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk
bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x
ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn
RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A==
-----END CERTIFICATE-----

UCA Extended Validation Root
============================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u
IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G
A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs
iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF
Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu
eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR
59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH
0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR
el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv
B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth
WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS
NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS
3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL
BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM
aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4
dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb
+7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW
F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi
GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc
GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi
djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr
dhh2n1ax
-----END CERTIFICATE-----

Certigna Root CA
================
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE
BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ
MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda
MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz
MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX
stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz
KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8
JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16
XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq
4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej
wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ
lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI
jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/
/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy
dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h
LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl
cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt
OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP
TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq
7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3
4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd
8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS
6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY
tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS
aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde
E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----

emSign Root CA - G1
===================
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET
MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx
ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk
aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN
LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1
cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW
DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ
6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH
hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2
vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q
NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q
+Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih
U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----

emSign ECC Root CA - G3
=======================
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG
A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4
MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11
ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc
58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr
MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D
CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7
jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----

emSign Root CA - C1
===================
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx
EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp
Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD
ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up
ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/
Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX
OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V
I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms
lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+
XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp
/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1
NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9
wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ
BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----

emSign ECC Root CA - C3
=======================
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG
A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF
Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD
ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd
6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9
SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA
B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA
MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU
ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----

Hongkong Post Root CA 3
=======================
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG
A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK
Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2
MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv
bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX
SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz
iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf
jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim
5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe
sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj
0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/
JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u
y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h
+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG
xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID
AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN
AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw
W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld
y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov
+BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc
eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw
9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7
nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY
hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
dBb9HxEGmpv0
-----END CERTIFICATE-----

Entrust Root Certification Authority - G4
=========================================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D
umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ
e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q
jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht
7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA
JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
kcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----

Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4
MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6
thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB
eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM
+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf
Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR
eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----

Microsoft RSA Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg
UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw
NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml
7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e
S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7
1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+
dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F
yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS
MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr
lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ
0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ
ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og
6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80
dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk
+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex
/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy
AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW
ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE
7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT
c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D
5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E
-----END CERTIFICATE-----

e-Szigno Root CA 2017
=====================
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw
DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt
MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa
Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE
CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp
Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx
s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv
vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA
tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO
svxyqltZ+efcMQ==
-----END CERTIFICATE-----

certSIGN Root CA G2
===================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw
EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy
MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH
TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05
N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk
abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg
wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp
dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh
ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732
jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf
95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc
z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL
iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB
ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB
/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5
8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5
BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW
atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU
Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M
NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N
0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc=
-----END CERTIFICATE-----

Trustwave Global Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29
zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf
LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq
stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o
WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+
OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40
Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE
uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm
+9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj
ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H
PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H
ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla
4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R
vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd
zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O
856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH
Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu
3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP
29FpHOTKyeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----

Trustwave Global ECC P256 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1
NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj
43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm
P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt
0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz
RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----

Trustwave Global ECC P384 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4
NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH
Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr
/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn
ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl
CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw==
-----END CERTIFICATE-----

NAVER Global Root Certification Authority
=========================================
-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG
A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD
DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4
NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT
UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb
UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW
+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7
XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2
aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4
Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z
VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B
A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai
cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy
YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV
HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK
21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB
jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx
hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg
E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH
D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ
A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY
qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
kpzNNIaRkPpkUZ3+/uul9XXeifdy
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM SERVIDORES SEGUROS
===================================
-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
-----END CERTIFICATE-----

GlobalSign Root R46
===================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
QEUxeCp6
-----END CERTIFICATE-----

GlobalSign Root E46
===================
-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----

GLOBALTRUST 2020
================
-----BEGIN CERTIFICATE-----
MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
-----END CERTIFICATE-----

ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----

Certum EC-384 CA
================
-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----

Certum Trusted Root CA
======================
-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----

TunTrust Root CA
================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----

HARICA TLS RSA Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
-----END CERTIFICATE-----

HARICA TLS ECC Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----
Copyright (C) 2016 Composer

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{"name":"composer\/ca-bundle","description":"Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.","type":"library","license":"MIT","keywords":["cabundle","cacert","certificate","ssl","tls"],"authors":[{"name":"Jordi Boggiano","email":"j.boggiano@seld.be","homepage":"http:\/\/seld.be"}],"support":{"irc":"irc:\/\/irc.freenode.org\/composer","issues":"https:\/\/github.com\/composer\/ca-bundle\/issues"},"require":{"ext-openssl":"*","ext-pcre":"*","php":"^5.3.2 || ^7.0 || ^8.0"},"require-dev":{"symfony\/phpunit-bridge":"^4.2 || ^5","phpstan\/phpstan":"^0.12.55","psr\/log":"^1.0","symfony\/process":"^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"},"autoload":{"psr-4":{"Composer\\CaBundle\\":"src"}},"autoload-dev":{"psr-4":{"Composer\\CaBundle\\":"tests"}},"extra":{"branch-alias":{"dev-main":"1.x-dev"}},"scripts":{"test":"SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit","phpstan":"vendor\/bin\/phpstan analyse"}}composer/ca-bundle
==================

Small utility library that lets you find a path to the system CA bundle,
and includes a fallback to the Mozilla CA bundle.

Originally written as part of [composer/composer](https://github.com/composer/composer),
now extracted and made available as a stand-alone library.


Installation
------------

Install the latest version with:

```bash
$ composer require composer/ca-bundle
```


Requirements
------------

* PHP 5.3.2 is required but using the latest version of PHP is highly recommended.


Basic usage
-----------

### `Composer\CaBundle\CaBundle`

- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback
- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file
- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use
- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse()
- `CaBundle::reset()`: Resets the static caches


#### To use with curl

```php
$curl = curl_init("https://example.org/");

$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
    curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile);
} else {
    curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile);
}

$result = curl_exec($curl);
```

#### To use with php streams

```php
$opts = array(
    'http' => array(
        'method' => "GET"
    )
);

$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
    $opts['ssl']['capath'] = $caPathOrFile;
} else {
    $opts['ssl']['cafile'] = $caPathOrFile;
}

$context = stream_context_create($opts);
$result = file_get_contents('https://example.com', false, $context);
```

#### To use with Guzzle

```php
$client = new \GuzzleHttp\Client([
    \GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()
]);
```

License
-------

composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details.
<?php

error_reporting(E_ALL);
ini_set('display_errors', 1);

// application.php
function includeIfExists($file)
{
    return file_exists($file) ? include $file : false;
}

if ((!$loader = includeIfExists(__DIR__ . '/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__ . '/../../../autoload.php'))) {
    echo 'You must set up the project dependencies, run the following commands:' . PHP_EOL
        . 'curl -sS https://getcomposer.org/installer | php' . PHP_EOL
        . 'php composer.phar install' . PHP_EOL;

    exit(1);
}

if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
    $name = '
   ____  _      _    _
  |  _ \(_) ___| | _| | ___
  | |_) | |/ __| |/ / |/ _ \
  |  __/| | (__|   <| |  __/
  |_|   |_|\___|_|\_\_|\___| ';
} else {
    $name = '
 ██████╗ ██╗ ██████╗██╗  ██╗██╗     ███████╗
 ██╔══██╗██║██╔════╝██║ ██╔╝██║     ██╔════╝
 ██████╔╝██║██║     █████╔╝ ██║     █████╗
 ██╔═══╝ ██║██║     ██╔═██╗ ██║     ██╔══╝
 ██║     ██║╚██████╗██║  ██╗███████╗███████╗
 ╚═╝     ╚═╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚══════╝ ';
}

$application = new Pickle\Console\Application($name);
$application->run();
؆yAeC���%�ݍy^��GBMB