#!/usr/bin/env php 7�� codecept.phar2vendor/phpseclib/phpseclib/phpseclib/bootstrap.php��b+\�8vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php"��b"b���1vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php�e��b�e�P2m�1vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php����b�����0vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php���b�S;�1vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php�]��b�]c��7�8vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.phpk���bk���M��2vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.phpX0��bX0�F��5vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php���b��1Ƶ�7vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php�V��b�VҤ�٤7vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php;J��b;J72v�6vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php[m��b[m��#Ф2vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.phpp��bp�w���3vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php$���b$�!�6�8vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.phpA��bA�� M�2vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php����b���lݕ�2vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.phpA���bA��#�Z�2vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php� ��b� A~=�3vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.phpv8��bv8=��2vendor/phpseclib/phpseclib/phpseclib/File/X509.php�t��b�t��*�2vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php�"��b�"�2F�:vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php���b�_*ё�2vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php�c��b�c�.��9vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php��b���]�Bvendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.phpo ��bo �令%vendor/webmozart/assert/src/Mixin.php����b��En=�8vendor/webmozart/assert/src/InvalidArgumentException.phpr��br�tV��&vendor/webmozart/assert/src/Assert.php�l��b�l��4ڤ2vendor/php-webdriver/webdriver/lib/WebDriverBy.php[��b[� �(�5vendor/php-webdriver/webdriver/lib/WebDriverAlert.php���b�Z=�A�=vendor/php-webdriver/webdriver/lib/WebDriverSearchContext.php���b��\�Τ<vendor/php-webdriver/webdriver/lib/WebDriverCapabilities.php=��b=�7Q�6vendor/php-webdriver/webdriver/lib/WebDriverAction.phpb��bb����Gvendor/php-webdriver/webdriver/lib/AbstractWebDriverCheckboxOrRadio.php���b�Ӆ�Ӥ-vendor/php-webdriver/webdriver/lib/Cookie.php� ��b� T7�5vendor/php-webdriver/webdriver/lib/Net/URLChecker.phpn��bnQ���6vendor/php-webdriver/webdriver/lib/WebDriverRadios.phpn��bn��h��Cvendor/php-webdriver/webdriver/lib/WebDriverNavigationInterface.php���b�v �ä6vendor/php-webdriver/webdriver/lib/WebDriverSelect.php���b��_��:vendor/php-webdriver/webdriver/lib/WebDriverNavigation.php���b�f���4vendor/php-webdriver/webdriver/lib/WebDriverKeys.php� ��b� �� �8vendor/php-webdriver/webdriver/lib/WebDriverPlatform.php��b����9vendor/php-webdriver/webdriver/lib/JavaScriptExecutor.php���b���⌤7vendor/php-webdriver/webdriver/lib/WebDriverOptions.php���b���5vendor/php-webdriver/webdriver/lib/WebDriverPoint.php_��b_����?vendor/php-webdriver/webdriver/lib/WebDriverSelectInterface.php@��b@�_��4vendor/php-webdriver/webdriver/lib/WebDriverWait.php;��b;�����Avendor/php-webdriver/webdriver/lib/Remote/UselessFileDetector.php���b��k&��>vendor/php-webdriver/webdriver/lib/Remote/RemoteWebElement.php>$��b>$�e5Ѥ?vendor/php-webdriver/webdriver/lib/Remote/RemoteTouchScreen.php:��b:`e4��<vendor/php-webdriver/webdriver/lib/Remote/RemoteKeyboard.php��byg�ҤAvendor/php-webdriver/webdriver/lib/Remote/RemoteExecuteMethod.phpR��bR��m�;vendor/php-webdriver/webdriver/lib/Remote/DriverCommand.php3��b3B��ڤAvendor/php-webdriver/webdriver/lib/Remote/RemoteTargetLocator.php� ��b� X/�Dvendor/php-webdriver/webdriver/lib/Remote/CustomWebDriverCommand.phpm��bm���Kvendor/php-webdriver/webdriver/lib/Remote/Service/DriverCommandExecutor.php���b�`��Cvendor/php-webdriver/webdriver/lib/Remote/Service/DriverService.php� ��b� ��2�Evendor/php-webdriver/webdriver/lib/Remote/WebDriverCapabilityType.php(��b(tc)Ѥ>vendor/php-webdriver/webdriver/lib/Remote/WebDriverCommand.php^��b^uzϤ:vendor/php-webdriver/webdriver/lib/Remote/FileDetector.phpu��bu�����Avendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.phpr>��br>��n�;vendor/php-webdriver/webdriver/lib/Remote/ExecuteMethod.php���b�t5f �?vendor/php-webdriver/webdriver/lib/Remote/LocalFileDetector.php���b�n.p��Bvendor/php-webdriver/webdriver/lib/Remote/WebDriverBrowserType.php���b�1P_�=vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.phpI&��bI&[�o0�9vendor/php-webdriver/webdriver/lib/Remote/RemoteMouse.phpO��bO�����:vendor/php-webdriver/webdriver/lib/Remote/RemoteStatus.php��b=��]�<vendor/php-webdriver/webdriver/lib/Remote/JsonWireCompat.php;��b;9-$��?vendor/php-webdriver/webdriver/lib/Remote/WebDriverResponse.php���b���r-�Avendor/php-webdriver/webdriver/lib/Remote/DesiredCapabilities.php���b���3ۤAvendor/php-webdriver/webdriver/lib/WebDriverExpectedCondition.phpJ��bJ��6vendor/php-webdriver/webdriver/lib/WebDriverWindow.phpU ��bU �#?��8vendor/php-webdriver/webdriver/lib/WebDriverKeyboard.php���b��e�%�?vendor/php-webdriver/webdriver/lib/WebDriverHasInputDevices.php���b��Dh�=vendor/php-webdriver/webdriver/lib/WebDriverTargetLocator.phpb��bb�$�^�Avendor/php-webdriver/webdriver/lib/Chrome/ChromeDriverService.php���b������;vendor/php-webdriver/webdriver/lib/Chrome/ChromeOptions.php}��b}��Bvendor/php-webdriver/webdriver/lib/Chrome/ChromeDevToolsDriver.php@��b@)��|�:vendor/php-webdriver/webdriver/lib/Chrome/ChromeDriver.phpK��bK�Flp�;vendor/php-webdriver/webdriver/lib/Support/XPathEscaper.php���b� d��Jvendor/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriver.php���b�s���Kvendor/php-webdriver/webdriver/lib/Support/Events/EventFiringWebElement.php���b�3=+�Tvendor/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriverNavigation.phpL��bLs[ޤBvendor/php-webdriver/webdriver/lib/Internal/WebDriverLocatable.php���b��Fz�8vendor/php-webdriver/webdriver/lib/WebDriverTimeouts.php���b��D� �=vendor/php-webdriver/webdriver/lib/WebDriverEventListener.phpr��br��"��9vendor/php-webdriver/webdriver/lib/WebDriverDimension.php���b��Z�,�:vendor/php-webdriver/webdriver/lib/WebDriverDispatcher.phpK��bKЭ@��;vendor/php-webdriver/webdriver/lib/Local/LocalWebDriver.php���b�Տ%��7vendor/php-webdriver/webdriver/lib/WebDriverElement.php���b�OI���8vendor/php-webdriver/webdriver/lib/WebDriverUpAction.php���b�.��r�Mvendor/php-webdriver/webdriver/lib/Exception/InvalidCookieDomainException.phpu��bu@8s�Evendor/php-webdriver/webdriver/lib/Exception/NoAlertOpenException.phpo��bo��W�Bvendor/php-webdriver/webdriver/lib/Exception/ExpectedException.phpj��bj�j驤Pvendor/php-webdriver/webdriver/lib/Exception/ElementNotInteractableException.phpx��bx��w�Nvendor/php-webdriver/webdriver/lib/Exception/UnsupportedOperationException.phpv��bv:����Fvendor/php-webdriver/webdriver/lib/Exception/UnknownErrorException.phpn��bn�8�L�Fvendor/php-webdriver/webdriver/lib/Exception/NoSuchCookieException.phpo��boe?h�Gvendor/php-webdriver/webdriver/lib/Exception/ScriptTimeoutException.phpo��bo3�C��Svendor/php-webdriver/webdriver/lib/Exception/IMEEngineActivationFailedException.php{��b{�F���Evendor/php-webdriver/webdriver/lib/Exception/NoSuchAlertException.phpm��bmW� �Bvendor/php-webdriver/webdriver/lib/Exception/NoStringException.phpj��bj�u�q�Jvendor/php-webdriver/webdriver/lib/Exception/InvalidSessionIdException.phps��bs��>��Evendor/php-webdriver/webdriver/lib/Exception/XPathLookupException.phpm��bmaTW�Fvendor/php-webdriver/webdriver/lib/Exception/NoSuchWindowException.phpn��bn�>���Nvendor/php-webdriver/webdriver/lib/Exception/ElementNotSelectableException.php���b��:� �Mvendor/php-webdriver/webdriver/lib/Exception/UnexpectedAlertOpenException.phpu��bu�����Ivendor/php-webdriver/webdriver/lib/Exception/JavascriptErrorException.phpq��bqi.��Mvendor/php-webdriver/webdriver/lib/Exception/InvalidElementStateException.phpv��bvs�H�Kvendor/php-webdriver/webdriver/lib/Exception/ElementNotVisibleException.phps��bs�-m�Ovendor/php-webdriver/webdriver/lib/Exception/StaleElementReferenceException.phpw��bw�ޢR�Jvendor/php-webdriver/webdriver/lib/Exception/IndexOutOfBoundsException.phpr��br ���Gvendor/php-webdriver/webdriver/lib/Exception/UnknownMethodException.phpo��bo��j8�Ovendor/php-webdriver/webdriver/lib/Exception/UnrecognizedExceptionException.phpt��bt�tmW�Ivendor/php-webdriver/webdriver/lib/Exception/InvalidSelectorException.phpq��bqv�Kr�Avendor/php-webdriver/webdriver/lib/Exception/TimeoutException.phpi��bi����Hvendor/php-webdriver/webdriver/lib/Exception/NoSuchDocumentException.phps��bsM�bФOvendor/php-webdriver/webdriver/lib/Exception/UnableToCaptureScreenException.phpw��bwL}�>�Qvendor/php-webdriver/webdriver/lib/Exception/ElementClickInterceptedException.phpz��bz�hZS�Fvendor/php-webdriver/webdriver/lib/Exception/NoCollectionException.phpn��bn3��Gvendor/php-webdriver/webdriver/lib/Exception/NoSuchElementException.phpo��boHA�\�Jvendor/php-webdriver/webdriver/lib/Exception/NoSuchCollectionException.phpr��br�_�g�Hvendor/php-webdriver/webdriver/lib/Exception/UnknownCommandException.phpp��bp�����Kvendor/php-webdriver/webdriver/lib/Exception/UnexpectedTagNameException.php2��b2l̃�Mvendor/php-webdriver/webdriver/lib/Exception/InsecureCertificateException.phpv��bv}���Nvendor/php-webdriver/webdriver/lib/Exception/UnexpectedJavascriptException.php|��b|��ۤHvendor/php-webdriver/webdriver/lib/Exception/NoScriptResultException.phpp��bp����Gvendor/php-webdriver/webdriver/lib/Exception/WebDriverCurlException.phpl��bl�'_�Hvendor/php-webdriver/webdriver/lib/Exception/NoStringLengthException.phpp��bp⬣�Kvendor/php-webdriver/webdriver/lib/Exception/SessionNotCreatedException.phps��bs~j��Gvendor/php-webdriver/webdriver/lib/Exception/UnknownServerException.phpr��br��-�Ivendor/php-webdriver/webdriver/lib/Exception/IMENotAvailableException.phpq��bq�(Oe�Ivendor/php-webdriver/webdriver/lib/Exception/InvalidArgumentException.phpq��bqZ ��Jvendor/php-webdriver/webdriver/lib/Exception/DriverServerDiedException.php.��b.�e��Evendor/php-webdriver/webdriver/lib/Exception/NoSuchFrameException.phpm��bm�Κ�Lvendor/php-webdriver/webdriver/lib/Exception/InvalidCoordinatesException.phpt��bt� �ܤCvendor/php-webdriver/webdriver/lib/Exception/WebDriverException.php���b����O�Fvendor/php-webdriver/webdriver/lib/Exception/NoSuchDriverException.phpn��bn����Ovendor/php-webdriver/webdriver/lib/Exception/MoveTargetOutOfBoundsException.phpw��bw( �~�Ivendor/php-webdriver/webdriver/lib/Exception/NoStringWrapperException.phpq��bqdK�w�Kvendor/php-webdriver/webdriver/lib/Exception/UnableToSetCookieException.phps��bs�����Evendor/php-webdriver/webdriver/lib/Exception/NullPointerException.phpm��bm&#��:vendor/php-webdriver/webdriver/lib/WebDriverCheckboxes.php���b�_�EK�<vendor/php-webdriver/webdriver/lib/Firefox/FirefoxDriver.php���b��xC�Cvendor/php-webdriver/webdriver/lib/Firefox/FirefoxDriverService.phpJ��bJ>�� �=vendor/php-webdriver/webdriver/lib/Firefox/FirefoxOptions.php���b���g��Avendor/php-webdriver/webdriver/lib/Firefox/FirefoxPreferences.phpk��bkB�r�=vendor/php-webdriver/webdriver/lib/Firefox/FirefoxProfile.phpT��bT��'�Nvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchScreen.php���b�s'f�Ovendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollAction.php���b�A-6B�Rvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDoubleTapAction.php��b��Q��Lvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTapAction.php��b+"8�Mvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDownAction.php���b����Rvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverLongPressAction.php��bm%jq�Zvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollFromElementAction.php7��b7%dl��Yvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickFromElementAction.php|��b|`)Y�Nvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickAction.php���b�]c�Mvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverMoveAction.php���b��F�Nvendor/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchAction.phpm��bm��ߤDvendor/php-webdriver/webdriver/lib/Interactions/WebDriverActions.php ��b �a_�Ivendor/php-webdriver/webdriver/lib/Interactions/WebDriverTouchActions.php? ��b? �7Vc�Qvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyUpAction.php���b�D���Tvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSendKeysAction.php���b��� �Xvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAndHoldAction.php��bp�Az�Qvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAction.php ��b �[R_�Xvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMoveToOffsetAction.php���b�M��Wvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverDoubleClickAction.php��b�����Uvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseMoveAction.php��bT]�-�Wvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeysRelatedAction.php���b�8�� �Xvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverContextClickAction.php��b1�ӌ�Qvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseAction.php���b��T+�Svendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyDownAction.php���b��4v�Yvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverButtonReleaseAction.php��be�V$�Uvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSingleKeyAction.phpx��bx�4�3�Qvendor/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverCoordinates.phpa��ba�H.H�Lvendor/php-webdriver/webdriver/lib/Interactions/WebDriverCompositeAction.php���b�[��T�0vendor/php-webdriver/webdriver/lib/WebDriver.php��be����5vendor/php-webdriver/webdriver/lib/WebDriverMouse.php��b{��z�?vendor/php-webdriver/webdriver/lib/WebDriverCommandExecutor.php���b�HO٬�#vendor/predis/predis/src/Client.phpI��bI�ciȤ?vendor/predis/predis/src/Replication/MissingMasterException.php���b�����<vendor/predis/predis/src/Replication/ReplicationStrategy.phpm��bm���K�6vendor/predis/predis/src/Replication/RoleException.php���b�״�K�,vendor/predis/predis/src/Session/Handler.php���b�� �=�,vendor/predis/predis/src/ClientInterface.php���b�?۾�2vendor/predis/predis/src/Transaction/MultiExec.phpX��bX��Bo�Bvendor/predis/predis/src/Transaction/AbortedMultiExecException.php���b�5���7vendor/predis/predis/src/Transaction/MultiExecState.php���b�8�5�2vendor/predis/predis/src/Configuration/Options.phpl��bl BM�:vendor/predis/predis/src/Configuration/OptionInterface.php���b�v� �8vendor/predis/predis/src/Configuration/ClusterOption.php���b�f��:�7vendor/predis/predis/src/Configuration/PrefixOption.php���b��ፑ�;vendor/predis/predis/src/Configuration/OptionsInterface.php���b����6�<vendor/predis/predis/src/Configuration/ReplicationOption.php���b�z��%�;vendor/predis/predis/src/Configuration/ExceptionsOption.php-��b-�g��8vendor/predis/predis/src/Configuration/ProfileOption.phpz��bz6���Bvendor/predis/predis/src/Configuration/ConnectionFactoryOption.php��bm"Ƥ:vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php� ��b� �7��,vendor/predis/predis/src/Pipeline/Atomic.php= ��b= l�'��3vendor/predis/predis/src/Pipeline/FireAndForget.phpf��bf��j6�.vendor/predis/predis/src/Pipeline/Pipeline.php� ��b� 2�M��-vendor/predis/predis/src/Monitor/Consumer.phpA��bA�7*�,vendor/predis/predis/src/PubSub/Consumer.php ��b �����4vendor/predis/predis/src/PubSub/AbstractConsumer.phpd ��bd f{9��2vendor/predis/predis/src/PubSub/DispatcherLoop.php;��b;�%��4vendor/predis/predis/src/Profile/RedisVersion220.php���b�}$U��4vendor/predis/predis/src/Profile/RedisVersion200.php*��b*$�z�4vendor/predis/predis/src/Profile/RedisVersion280.php���b���_ۤ4vendor/predis/predis/src/Profile/RedisVersion320.phpF��bF�G.C�1vendor/predis/predis/src/Profile/RedisProfile.php��b�;���,vendor/predis/predis/src/Profile/Factory.php���b��I��5vendor/predis/predis/src/Profile/ProfileInterface.php\��b\�2z�4vendor/predis/predis/src/Profile/RedisVersion260.php��b)�\R�2vendor/predis/predis/src/Profile/RedisUnstable.php��b�Ǒ��4vendor/predis/predis/src/Profile/RedisVersion240.php(��b(M�`D�4vendor/predis/predis/src/Profile/RedisVersion300.php���b��֦�3vendor/predis/predis/src/ClientContextInterface.php���b�&�}�2vendor/predis/predis/src/NotSupportedException.phpa��ban�)�,vendor/predis/predis/src/PredisException.php^��b^���:vendor/predis/predis/src/Connection/AbstractConnection.phpQ ��bQ ���Avendor/predis/predis/src/Connection/PhpiredisStreamConnection.phpj��bj��� �Dvendor/predis/predis/src/Connection/AggregateConnectionInterface.php���b�lAu�Avendor/predis/predis/src/Connection/CompositeStreamConnection.php���b���a��8vendor/predis/predis/src/Connection/WebdisConnection.phpj��bj�P�̤;vendor/predis/predis/src/Connection/ConnectionException.php���b��-���2vendor/predis/predis/src/Connection/Parameters.phpE��bE�9��Dvendor/predis/predis/src/Connection/CompositeConnectionInterface.php��b]��;vendor/predis/predis/src/Connection/ConnectionInterface.php���b�^� M�/vendor/predis/predis/src/Connection/Factory.php" ��b" �^�Ť8vendor/predis/predis/src/Connection/StreamConnection.php���b�1���?vendor/predis/predis/src/Connection/NodeConnectionInterface.phpm��bmi��8vendor/predis/predis/src/Connection/FactoryInterface.php;��b;6.�5�Avendor/predis/predis/src/Connection/PhpiredisSocketConnection.phpv��bvZ�K4�;vendor/predis/predis/src/Connection/ParametersInterface.php���b�� Y֤Hvendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.phpQ��bQ�����Fvendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php���b��!��Bvendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php���b�N��?vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php@ ��b@ ]��W�Evendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php�'��b�' ��>vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php7#��b7#� ^ޤ6vendor/predis/predis/src/Cluster/StrategyInterface.php?��b?�"��3vendor/predis/predis/src/Cluster/PredisStrategy.php���b��s��@vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php���b�!)��/vendor/predis/predis/src/Cluster/Hash/CRC16.php� ��b� �E(��2vendor/predis/predis/src/Cluster/RedisStrategy.php���b� �m�4vendor/predis/predis/src/Cluster/ClusterStrategy.php�#��b�# @�9vendor/predis/predis/src/Cluster/Distributor/HashRing.php ��b J��ۤCvendor/predis/predis/src/Cluster/Distributor/EmptyRingException.phpl��bl�����Evendor/predis/predis/src/Cluster/Distributor/DistributorInterface.php���b���j��;vendor/predis/predis/src/Cluster/Distributor/KetamaRing.phpt��bt�]�@vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php_��b_�i"�7vendor/predis/predis/src/Protocol/ProtocolException.php���b���5�Evendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.php��b�,$��<vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php ��b ��{��9vendor/predis/predis/src/Protocol/Text/ResponseReader.php���b��0p�Dvendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php ��b :�d�Kvendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php���b� �wQ�?vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.php��b�W0�@vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php;��b;LI��Bvendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php`��b`��L�Nvendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.php���b�A�-(�Avendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.php@��b@%s�|�<vendor/predis/predis/src/Protocol/Text/RequestSerializer.phpQ��bQ��o��@vendor/predis/predis/src/Protocol/RequestSerializerInterface.php���b�n��=vendor/predis/predis/src/Protocol/ResponseReaderInterface.php���b���7�'vendor/predis/predis/src/Autoloader.php���b�윝��5vendor/predis/predis/src/Response/ServerException.phpb��bb��#��7vendor/predis/predis/src/Response/ResponseInterface.phpQ��bQyZ��=vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.phpU��bU;E�m�@vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php{��b{󮦽�8vendor/predis/predis/src/Response/Iterator/MultiBulk.php���b��#���4vendor/predis/predis/src/Response/ErrorInterface.php���b�`w���,vendor/predis/predis/src/Response/Status.phpR��bR��p|�+vendor/predis/predis/src/Response/Error.php���b��&�ˤ3vendor/predis/predis/src/Command/ConnectionEcho.php���b�Bg�5vendor/predis/predis/src/Command/ZSetRangeByScore.phpy��by`�®�/vendor/predis/predis/src/Command/HashLength.php���b���y�4vendor/predis/predis/src/Command/ZSetCardinality.php���b�UR�٤1vendor/predis/predis/src/Command/StringBitPos.php���b�����4vendor/predis/predis/src/Command/ZSetReverseRank.php���b�,��1vendor/predis/predis/src/Command/ListPushHead.php���b�9���9vendor/predis/predis/src/Command/HashIncrementByFloat.php���b�N`�+�-vendor/predis/predis/src/Command/HashKeys.php���b�d<���3vendor/predis/predis/src/Command/ZSetUnionStore.php���b��c�/�4vendor/predis/predis/src/Command/StringSetExpire.php���b��L,�1vendor/predis/predis/src/Command/ListPushTail.php���b����0vendor/predis/predis/src/Command/KeyExpireAt.php���b���^��4vendor/predis/predis/src/Command/HashIncrementBy.php���b�GHo��,vendor/predis/predis/src/Command/SetMove.php���b��RAS�9vendor/predis/predis/src/Command/ListPopFirstBlocking.phpf��bfɝ�դ5vendor/predis/predis/src/Command/ConnectionSelect.php���b��=3d�-vendor/predis/predis/src/Command/HashScan.php���b��7�4�1vendor/predis/predis/src/Command/PubSubPubsub.php ��b =�vΤ.vendor/predis/predis/src/Command/KeyRandom.php���b�}�3)�1vendor/predis/predis/src/Command/ServerObject.php���b�9V��3vendor/predis/predis/src/Command/StringBitCount.php���b�l� �8vendor/predis/predis/src/Command/ListPopLastPushHead.php���b���Ų�2vendor/predis/predis/src/Command/ScriptCommand.php���b���Ϥ>vendor/predis/predis/src/Command/StringSetMultiplePreserve.php���b�� �ɤ3vendor/predis/predis/src/Command/ServerFlushAll.php���b���ː�,vendor/predis/predis/src/Command/KeyDump.php���b��j�ߤ?vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.php ��b j_��.vendor/predis/predis/src/Command/ListIndex.php���b��۬ä/vendor/predis/predis/src/Command/ServerSave.php���b��{�̤8vendor/predis/predis/src/Command/ServerFlushDatabase.php���b�G����3vendor/predis/predis/src/Command/ConnectionAuth.php���b�On���,vendor/predis/predis/src/Command/HashSet.php���b�HK�ޤ4vendor/predis/predis/src/Command/PubSubSubscribe.php���b�Qv���.vendor/predis/predis/src/Command/KeyRename.php���b�E߇Τ1vendor/predis/predis/src/Command/ServerScript.php���b�AV�^�/vendor/predis/predis/src/Command/ListRemove.php���b���'��/vendor/predis/predis/src/Command/SetMembers.php���b�@����1vendor/predis/predis/src/Command/StringStrlen.php���b����?�3vendor/predis/predis/src/Command/ServerLastSave.php���b�e�WW�1vendor/predis/predis/src/Command/StringGetBit.php���b�B���/vendor/predis/predis/src/Command/HashExists.php���b�����9vendor/predis/predis/src/Command/KeyPreciseTimeToLive.php���b��0&��4vendor/predis/predis/src/Command/StringDecrement.php���b��KԤ9vendor/predis/predis/src/Command/SetIntersectionStore.phpZ��bZ�BW��,vendor/predis/predis/src/Command/KeyScan.php���b��p��.vendor/predis/predis/src/Command/ZSetCount.php���b��/+��4vendor/predis/predis/src/Command/HashSetMultiple.php���b�U���+vendor/predis/predis/src/Command/SetPop.php��b)�o¤4vendor/predis/predis/src/Command/HashSetPreserve.php���b�q�::�,vendor/predis/predis/src/Command/ZSetAdd.php\��b\�R���,vendor/predis/predis/src/Command/KeySort.phpY��bY���=�8vendor/predis/predis/src/Command/ListPopLastBlocking.php���b�{,��7vendor/predis/predis/src/Command/SetDifferenceStore.php���b� ��2vendor/predis/predis/src/Command/ServerSlaveOf.php4��b4Jg�Ӥ6vendor/predis/predis/src/Command/PubSubUnsubscribe.php���b���|�3vendor/predis/predis/src/Command/StringBitField.php���b�ٙ�Ӥ3vendor/predis/predis/src/Command/ServerInfoV26x.phpc��bcl���2vendor/predis/predis/src/Command/ServerCommand.php���b�k�Ӥ2vendor/predis/predis/src/Command/ListPushTailX.php���b�gjܟ�0vendor/predis/predis/src/Command/ListPopLast.php���b������:vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.php���b��! T�0vendor/predis/predis/src/Command/SetIsMember.php���b���5,�2vendor/predis/predis/src/Command/KeyTimeToLive.php���b�<:���5vendor/predis/predis/src/Command/TransactionMulti.php���b� U��5vendor/predis/predis/src/Command/TransactionWatch.php*��b*��3vendor/predis/predis/src/Command/ZSetRangeByLex.php���b�". �1vendor/predis/predis/src/Command/ListPopFirst.php���b�L���4vendor/predis/predis/src/Command/HashGetMultiple.php���b��П��6vendor/predis/predis/src/Command/StringDecrementBy.php���b���;��/vendor/predis/predis/src/Command/HashDelete.php���b��� ��4vendor/predis/predis/src/Command/StringIncrement.php���b�);wĤ3vendor/predis/predis/src/Command/SetCardinality.php���b�ͯ�!�-vendor/predis/predis/src/Command/ZSetScan.php���b��7�j�5vendor/predis/predis/src/Command/KeyPreciseExpire.php���b�|���-vendor/predis/predis/src/Command/ListTrim.php���b�� �Q�;vendor/predis/predis/src/Command/StringIncrementByFloat.php���b�q�놤/vendor/predis/predis/src/Command/ServerInfo.php��b��,x�6vendor/predis/predis/src/Command/GeospatialGeoDist.php���b�Nv@�5vendor/predis/predis/src/Command/HashStringLength.php���b��:|r�5vendor/predis/predis/src/Command/HyperLogLogCount.php���b�.=�^�4vendor/predis/predis/src/Command/SetRandomMember.php���b���`/�7vendor/predis/predis/src/Command/KeyPreciseExpireAt.php���b��!���5vendor/predis/predis/src/Command/HyperLogLogMerge.php���b���hs�,vendor/predis/predis/src/Command/KeyKeys.php���b�����3vendor/predis/predis/src/Command/ConnectionPing.php���b��<��5vendor/predis/predis/src/Command/CommandInterface.php���b�'�i��/vendor/predis/predis/src/Command/ServerEval.php���b��e�9�/vendor/predis/predis/src/Command/ListLength.php���b�c�9�2vendor/predis/predis/src/Command/SetDifference.php���b���p��9vendor/predis/predis/src/Command/ServerBackgroundSave.php���b���B�1vendor/predis/predis/src/Command/StringSetBit.php���b�:їB�2vendor/predis/predis/src/Command/ServerSlowlog.php���b��t��5vendor/predis/predis/src/Command/GeospatialGeoAdd.phpr��br�퓭�5vendor/predis/predis/src/Command/GeospatialGeoPos.phpj��bj�Yä:vendor/predis/predis/src/Command/ZSetReverseRangeByLex.php���b�c�Q�5vendor/predis/predis/src/Command/ZSetReverseRange.php���b�z�h5�4vendor/predis/predis/src/Command/TransactionExec.php���b�Z-25�,vendor/predis/predis/src/Command/KeyMove.php���b��X���4vendor/predis/predis/src/Command/SetIntersection.php���b������6vendor/predis/predis/src/Command/KeyRenamePreserve.php���b�.LT�?vendor/predis/predis/src/Command/PrefixableCommandInterface.php���b��ū�-vendor/predis/predis/src/Command/ZSetRank.php���b���h�3vendor/predis/predis/src/Command/ServerShutdown.php���b�����0vendor/predis/predis/src/Command/StringBitOp.php���b�� �.vendor/predis/predis/src/Command/KeyExists.php���b����h�@vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php���b��� �1vendor/predis/predis/src/Command/ZSetLexCount.php���b�J�-٤.vendor/predis/predis/src/Command/KeyDelete.php���b�}�aФ.vendor/predis/predis/src/Command/SetRemove.php���b�Wq��.vendor/predis/predis/src/Command/ZSetScore.php���b����Ť/vendor/predis/predis/src/Command/HashGetAll.php$��b$���Q�1vendor/predis/predis/src/Command/ServerConfig.phpP��bP��r�3vendor/predis/predis/src/Command/ServerSentinel.php���b��m�@�2vendor/predis/predis/src/Command/ServerEvalSHA.php���b�J�n�7vendor/predis/predis/src/Command/TransactionUnwatch.php���b������1vendor/predis/predis/src/Command/ServerClient.php���b�����/vendor/predis/predis/src/Command/ServerTime.php���b���쒤2vendor/predis/predis/src/Command/ServerMonitor.php���b��&#$�/vendor/predis/predis/src/Command/KeyPersist.php���b���ɽ�3vendor/predis/predis/src/Command/ConnectionQuit.php���b�I���/vendor/predis/predis/src/Command/HashValues.php���b�U F[�/vendor/predis/predis/src/Command/ZSetRemove.php���b�z}�.vendor/predis/predis/src/Command/ListRange.php���b����Ϥ6vendor/predis/predis/src/Command/GeospatialGeoHash.phpl��bl�q�a�;vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.php���b���F�/vendor/predis/predis/src/Command/RawCommand.php���b�6�eA�6vendor/predis/predis/src/Command/StringSetPreserve.php���b���F��-vendor/predis/predis/src/Command/SetUnion.php���b����X�7vendor/predis/predis/src/Command/TransactionDiscard.php���b����t�1vendor/predis/predis/src/Command/StringSubstr.php���b���!�/vendor/predis/predis/src/Command/KeyMigrate.php���b���[V�.vendor/predis/predis/src/Command/ZSetRange.php���b�q`�,vendor/predis/predis/src/Command/KeyType.php���b�����,vendor/predis/predis/src/Command/Command.php���b�o[���;vendor/predis/predis/src/Command/StringPreciseSetExpire.php���b�hv��@vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.php���b����=�,vendor/predis/predis/src/Command/SetScan.php���b���E�/vendor/predis/predis/src/Command/ListInsert.php���b�W�tK�/vendor/predis/predis/src/Command/KeyRestore.php���b�fI?ޤ3vendor/predis/predis/src/Command/HyperLogLogAdd.php���b������9vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.php���b��fb�6vendor/predis/predis/src/Command/StringIncrementBy.php���b��uS�<vendor/predis/predis/src/Command/ZSetReverseRangeByScore.php���b��-�Ť2vendor/predis/predis/src/Command/ListPushHeadX.php���b�_f�|�?vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.php���b�a @�3vendor/predis/predis/src/Command/StringGetRange.php���b�����=vendor/predis/predis/src/Command/PubSubSubscribeByPattern.php���b��t|�2vendor/predis/predis/src/Command/SetUnionStore.php���b�S�-��7vendor/predis/predis/src/Command/ServerDatabaseSize.php���b�8��2vendor/predis/predis/src/Command/PubSubPublish.php���b�����6vendor/predis/predis/src/Command/StringGetMultiple.php���b���)ͤ,vendor/predis/predis/src/Command/ListSet.php���b�}��1vendor/predis/predis/src/Command/StringGetSet.php���b����m�1vendor/predis/predis/src/Command/StringAppend.php���b���پ�:vendor/predis/predis/src/Command/ZSetIntersectionStore.php���b�,��_�.vendor/predis/predis/src/Command/KeyExpire.php���b��4 �.vendor/predis/predis/src/Command/StringGet.php���b�����.vendor/predis/predis/src/Command/StringSet.php���b���p�6vendor/predis/predis/src/Command/StringSetMultiple.php���b���d�8vendor/predis/predis/src/Command/GeospatialGeoRadius.php���b���͐�3vendor/predis/predis/src/Command/StringSetRange.php���b�e^���+vendor/predis/predis/src/Command/SetAdd.php���b��Ä��4vendor/predis/predis/src/Command/ZSetIncrementBy.php���b���K/�,vendor/predis/predis/src/Command/HashGet.php���b�@ؤAvendor/predis/predis/src/Command/Processor/ProcessorInterface.php���b�Ɛ�c�Avendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php���b��Fdo�=vendor/predis/predis/src/Command/Processor/ProcessorChain.phpa��baR帱�9vendor/predis/predis/src/Collection/Iterator/Keyspace.php���b��R�s�8vendor/predis/predis/src/Collection/Iterator/HashKey.php���b�Gl���8vendor/predis/predis/src/Collection/Iterator/ListKey.php���b�>�Ǥ=vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php���b�ߚ�)�Dvendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.php:��b:g��R�7vendor/predis/predis/src/Collection/Iterator/SetKey.php���b��t�3vendor/predis/predis/src/CommunicationException.phpC��bC�����,vendor/predis/predis/src/ClientException.phpZ��bZl�<�!vendor/predis/predis/autoload.phpW��bWf4C�5vendor/predis/predis/examples/replication_complex.php��b�SQ<�/vendor/predis/predis/examples/key_prefixing.php ��b �F�0�<vendor/predis/predis/examples/custom_cluster_distributor.php��bm)�Ӥ7vendor/predis/predis/examples/transaction_using_cas.php��b� �Ƥ(vendor/predis/predis/examples/shared.php���b��'I��7vendor/predis/predis/examples/debuggable_connection.php��b��ɤ=vendor/predis/predis/examples/redis_collections_iterators.phpR��bR3oy"�5vendor/predis/predis/examples/pipelining_commands.phpj��bj��Ҥ2vendor/predis/predis/examples/monitor_consumer.php���b��RU٤6vendor/predis/predis/examples/replication_sentinel.phpL��bL� ��1vendor/predis/predis/examples/dispatcher_loop.php���b���!1�4vendor/predis/predis/examples/replication_simple.php-��b-��ǿ�;vendor/predis/predis/examples/lua_scripting_abstraction.phpU��bU� $�1vendor/predis/predis/examples/pubsub_consumer.phpa��baϕ���:vendor/predis/predis/examples/executing_redis_commands.php(��b(N�Y�1vendor/predis/predis/examples/session_handler.php���b�� �I�Avendor/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php��b46�3vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php���b� n��Ovendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php���b���)�Evendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php��b���k�Avendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.phpj��bjc$�C�9vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.phpm��bm�a�ˤEvendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php���b��j�Q�Evendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php���b����2vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php��bKj]'�?vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.phpf��bfZ.)ȤLvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php[��b[%MQ��Fvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php���b���g��Bvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php��b›q��Lvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.phpn��bn���Rvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php���b�M��D�Jvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/ArrayObjectFilter.php���b�!\��;vendor/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php���b�6���Mvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php���b��s()�Rvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.phpC��bC ��A�Wvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php���b��h�>vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php_��b_|�Gؤ>vendor/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php"��b"�G���7vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php|��b|�a]ˤBvendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php��bL��t�Evendor/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.phpx��bx�4��5vendor/symfony/polyfill-intl-normalizer/bootstrap.php���b�P��D�7vendor/symfony/polyfill-intl-normalizer/bootstrap80.php���b�=�r��6vendor/symfony/polyfill-intl-normalizer/Normalizer.php���b�b�"q�Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpt.��bt. q�ܤXvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php����b��o�e)�Rvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php�=��b�=*�o?�Tvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.phpa���ba��R�}�Fvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php.��b.Qs$��.vendor/symfony/polyfill-intl-idn/bootstrap.php[��b[ΐ�*�)vendor/symfony/polyfill-intl-idn/Info.php���b��[��(vendor/symfony/polyfill-intl-idn/Idn.php�B��b�B���0vendor/symfony/polyfill-intl-idn/bootstrap80.php@��b@6�> �Lvendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php>��b>NچG�Gvendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php���b�8�t��Avendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php8���b8�M:�P�Mvendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php���b�;qf�=vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php���b�/�ɤ=vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php/f��b/f��-�<vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.phṗ��ḃ q]R�>vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php���b��d�@vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.phpK��bKë`Ҥ6vendor/symfony/css-selector/Parser/ParserInterface.php���b�{�偤:vendor/symfony/css-selector/Parser/Shortcut/HashParser.phps��bs��4a�;vendor/symfony/css-selector/Parser/Shortcut/ClassParser.phpw��bw���)�Avendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php���b�N���=vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php��b�%�+�2vendor/symfony/css-selector/Parser/TokenStream.phpF��bF&�T �:vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php���b��S-�Bvendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php��b� ,�Bvendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php���b���f��-vendor/symfony/css-selector/Parser/Reader.phpb��bb<ڤ<vendor/symfony/css-selector/Parser/Handler/StringHandler.phpa��baR9���<vendor/symfony/css-selector/Parser/Handler/NumberHandler.php ��b ]�.R�?vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php��bc�n�@vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php���b���o��@vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.phpU��bU�ۋ��=vendor/symfony/css-selector/Parser/Handler/CommentHandler.php��bՃ��:vendor/symfony/css-selector/Parser/Handler/HashHandler.php���b�:�P�,vendor/symfony/css-selector/Parser/Token.phpu��bu@!�Ҥ-vendor/symfony/css-selector/Parser/Parser.php@��b@�յ�4vendor/symfony/css-selector/CssSelectorConverter.php���b���1vendor/symfony/css-selector/Node/FunctionNode.php��b]뵣�1vendor/symfony/css-selector/Node/SelectorNode.php��b���[�.vendor/symfony/css-selector/Node/ClassNode.php���b�O���0vendor/symfony/css-selector/Node/ElementNode.php���b���*�2vendor/symfony/css-selector/Node/AttributeNode.php#��b#&�Ó�2vendor/symfony/css-selector/Node/NodeInterface.php���b�o"�F�0vendor/symfony/css-selector/Node/Specificity.php���b�"�&1�1vendor/symfony/css-selector/Node/NegationNode.php���b�ғ�1vendor/symfony/css-selector/Node/AbstractNode.phpN��bN�`�n�9vendor/symfony/css-selector/Node/CombinedSelectorNode.php���b�����/vendor/symfony/css-selector/Node/PseudoNode.php���b��(��-vendor/symfony/css-selector/Node/HashNode.php��bj)�&�8vendor/symfony/css-selector/Exception/ParseException.php���b��/�X�@vendor/symfony/css-selector/Exception/InternalErrorException.php���b��&O��>vendor/symfony/css-selector/Exception/SyntaxErrorException.phpn��bn�����Bvendor/symfony/css-selector/Exception/ExpressionErrorException.php���b�6�;�<vendor/symfony/css-selector/Exception/ExceptionInterface.php���b�B|���0vendor/symfony/css-selector/XPath/Translator.php��b =��9vendor/symfony/css-selector/XPath/TranslatorInterface.phpq��bqt_6��Jvendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.phpb ��bb �}��Bvendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php���b�ZƼ�Avendor/symfony/css-selector/XPath/Extension/FunctionExtension.php6 ��b6 4\��Avendor/symfony/css-selector/XPath/Extension/AbstractExtension.php���b�>:�=vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php���b����=vendor/symfony/css-selector/XPath/Extension/NodeExtension.php2��b2 'L|�Dvendor/symfony/css-selector/XPath/Extension/CombinationExtension.php���b�6�$n�Dvendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php���b�z��ä/vendor/symfony/css-selector/XPath/XPathExpr.php���b�̭�F�+vendor/symfony/polyfill-ctype/bootstrap.php2��b2�a�8�'vendor/symfony/polyfill-ctype/Ctype.php� ��b� ~�d��-vendor/symfony/polyfill-ctype/bootstrap80.phph��bhK�y��+vendor/symfony/polyfill-php73/bootstrap.php���b�AY8Ƥ'vendor/symfony/polyfill-php73/Php73.phpn��bnCs�l�?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php[��b[M�ܤ%vendor/symfony/browser-kit/Client.php�4��b�4�gc6�Evendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php���b��|9�?vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php���b�+��N�(vendor/symfony/browser-kit/CookieJar.php� ��b� �Vy��%vendor/symfony/browser-kit/Cookie.php,��b,�1�Y�&vendor/symfony/browser-kit/Request.php���b��I��.vendor/symfony/browser-kit/AbstractBrowser.phpx��bxQ 3Y�*vendor/symfony/browser-kit/HttpBrowser.php1 ��b1 �g#��&vendor/symfony/browser-kit/History.php���b�}0�?vendor/symfony/browser-kit/Exception/BadMethodCallException.php���b��Pa�'vendor/symfony/browser-kit/Response.phpw��bw f(�+vendor/symfony/polyfill-php80/bootstrap.php���b�����'vendor/symfony/polyfill-php80/Php80.php� ��b� [�k �*vendor/symfony/polyfill-php80/PhpToken.php��bM�I�;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php���b���Ф<vendor/symfony/polyfill-php80/Resources/stubs/ValueError.phpK��bK����:vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php���b�}�GG�Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.phpT��bT��O��<vendor/symfony/polyfill-php80/Resources/stubs/Stringable.phpb��bb�ћ<�.vendor/symfony/polyfill-mbstring/bootstrap.php���b�NZ^�-vendor/symfony/polyfill-mbstring/Mbstring.phptJ��btJ�ڟV�0vendor/symfony/polyfill-mbstring/bootstrap80.php� ��b� D� פ@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�T��b�T��+�@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php8[��b8[+R�*�Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php���b��y_��+vendor/symfony/process/ExecutableFinder.php���b���3a�"vendor/symfony/process/Process.php�e��b�e�Gp �&vendor/symfony/process/InputStream.php-��b-|�'vendor/symfony/process/ProcessUtils.php ��b ���¤;vendor/symfony/process/Exception/ProcessFailedException.phpx��bx��zy�=vendor/symfony/process/Exception/ProcessTimedOutException.php1��b1�'Z�3vendor/symfony/process/Exception/LogicException.php���b� ���=vendor/symfony/process/Exception/ProcessSignaledException.php��bYש�5vendor/symfony/process/Exception/RuntimeException.php���b���:�=vendor/symfony/process/Exception/InvalidArgumentException.php���b���+_�7vendor/symfony/process/Exception/ExceptionInterface.phpy��byqVXJ�.vendor/symfony/process/PhpExecutableFinder.php���b�xK�d�-vendor/symfony/process/Pipes/WindowsPipes.php( ��b( ���*vendor/symfony/process/Pipes/UnixPipes.php|��b|��/vendor/symfony/process/Pipes/PipesInterface.php���b�fQ� �.vendor/symfony/process/Pipes/AbstractPipes.php ��b ���%vendor/symfony/process/PhpProcess.php���b�MW�֤#vendor/symfony/finder/Gitignore.php��b�E�%vendor/symfony/finder/SplFileInfo.php���b�䵵7�3vendor/symfony/finder/Comparator/DateComparator.php��b��/vendor/symfony/finder/Comparator/Comparator.php���b������5vendor/symfony/finder/Comparator/NumberComparator.php���b���α� vendor/symfony/finder/Finder.php%+��b%+:�C��vendor/symfony/finder/Glob.php���b���t'�:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpz��bz�Z:��=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php���b���+�9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php���b��)P�<vendor/symfony/finder/Iterator/FilecontentFilterIterator.phpN��bN���z�Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php,��b,p̓��:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php���b����ؤ9vendor/symfony/finder/Iterator/FilenameFilterIterator.php���b��nTӤ;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php��b�*դ7vendor/symfony/finder/Iterator/CustomFilterIterator.phpa��ba�0�ä=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.phpy��by�\�¤/vendor/symfony/finder/Iterator/LazyIterator.phpQ��bQn]���5vendor/symfony/finder/Iterator/PathFilterIterator.php���b��B�G�3vendor/symfony/finder/Iterator/SortableIterator.phpo ��bo �n�>vendor/symfony/finder/Exception/DirectoryNotFoundException.php���b�a)��9vendor/symfony/finder/Exception/AccessDeniedException.php���b���s��Jvendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextContains.phpk��bk���p�Pvendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php���b������Fvendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextSame.phpb��bb�{�Dvendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorExists.phpx��bx��#vendor/symfony/dom-crawler/Form.php���b�n*/�6vendor/symfony/dom-crawler/Field/TextareaFormField.php���b��4�^�4vendor/symfony/dom-crawler/Field/ChoiceFormField.php��b�W���.vendor/symfony/dom-crawler/Field/FormField.php���b��YB�3vendor/symfony/dom-crawler/Field/InputFormField.php���b�!��O�2vendor/symfony/dom-crawler/Field/FileFormField.php��b����#vendor/symfony/dom-crawler/Link.php���b�v�ۮ�&vendor/symfony/dom-crawler/Crawler.php�S��b�Sx�^5�$vendor/symfony/dom-crawler/Image.php���b��� �0vendor/symfony/dom-crawler/FormFieldRegistry.php ��b ���r�1vendor/symfony/dom-crawler/AbstractUriElement.php ��b �P�2vendor/symfony/console/Event/ConsoleErrorEvent.php���b������4vendor/symfony/console/Event/ConsoleCommandEvent.php���b�W��6vendor/symfony/console/Event/ConsoleTerminateEvent.php{��b{ _y<�-vendor/symfony/console/Event/ConsoleEvent.php���b��/��,vendor/symfony/console/Output/NullOutput.php���b��0�f�1vendor/symfony/console/Output/OutputInterface.php���b�h��Ф/vendor/symfony/console/Output/ConsoleOutput.php� ��b� �K�0vendor/symfony/console/Output/BufferedOutput.php`��b`�蟗�5vendor/symfony/console/Output/TrimmedBufferOutput.php���b������8vendor/symfony/console/Output/ConsoleOutputInterface.php���b��,ݤ(vendor/symfony/console/Output/Output.php� ��b� ?�q�.vendor/symfony/console/Output/StreamOutput.phpv��bv�F_&�6vendor/symfony/console/Output/ConsoleSectionOutput.php� ��b� ppCl�#vendor/symfony/console/Terminal.php� ��b� Ո�Ť(vendor/symfony/console/ConsoleEvents.php���b��v�2vendor/symfony/console/Helper/InputAwareHelper.phpc��bc����'vendor/symfony/console/Helper/Table.php�F��b�F��+�0vendor/symfony/console/Helper/TableSeparator.php���b������2vendor/symfony/console/Helper/DescriptorHelper.phpe��be2��_�-vendor/symfony/console/Helper/ProgressBar.php�,��b�,�U���(vendor/symfony/console/Helper/Dumper.php��b��ܽ�+vendor/symfony/console/Helper/TableRows.php1��b1 O�+vendor/symfony/console/Helper/HelperSet.php~��b~��t�(vendor/symfony/console/Helper/Helper.php���b�L�V��1vendor/symfony/console/Helper/FormatterHelper.phpD��bD+��L�6vendor/symfony/console/Helper/DebugFormatterHelper.phpt��bt�R8��3vendor/symfony/console/Helper/ProgressIndicator.php��b��:��+vendor/symfony/console/Helper/TableCell.php+��b+����,vendor/symfony/console/Helper/TableStyle.php���b�F�C�0vendor/symfony/console/Helper/QuestionHelper.php�(��b�(�;�0�1vendor/symfony/console/Helper/HelperInterface.php���b����/vendor/symfony/console/Helper/ProcessHelper.php� ��b� �jLѤ7vendor/symfony/console/Helper/SymfonyQuestionHelper.php���b��פ2vendor/symfony/console/Question/ChoiceQuestion.php� ��b� �*n�8vendor/symfony/console/Question/ConfirmationQuestion.php��b�yń�,vendor/symfony/console/Question/Question.php ��b �`�6vendor/symfony/console/EventListener/ErrorListener.php&��b&X�뮤&vendor/symfony/console/Application.phpn��bn�����-vendor/symfony/console/Style/SymfonyStyle.php/&��b/&ZH��,vendor/symfony/console/Style/OutputStyle.phpX��bX����/vendor/symfony/console/Style/StyleInterface.php���b����/vendor/symfony/console/Tester/CommandTester.php���b��7���3vendor/symfony/console/Tester/ApplicationTester.php���b�ʰ�Y�-vendor/symfony/console/Tester/TesterTrait.php� ��b� n‡�/vendor/symfony/console/Logger/ConsoleLogger.php ��b � Q�.vendor/symfony/console/Command/ListCommand.php6��b6c��=�0vendor/symfony/console/Command/LockableTrait.php��b�����.vendor/symfony/console/Command/HelpCommand.php���b�G�?~�*vendor/symfony/console/Command/Command.php"��b"R�'`�=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php���b��m���?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpG��bGk{UĤ?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpG��bG?����Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php� ��b� :X&k�:vendor/symfony/console/Exception/MissingInputException.php���b��S ��?vendor/symfony/console/Exception/NamespaceNotFoundException.php���b���n��;vendor/symfony/console/Exception/InvalidOptionException.php���b���H�3vendor/symfony/console/Exception/LogicException.php���b��O\e�5vendor/symfony/console/Exception/RuntimeException.php���b���,6�=vendor/symfony/console/Exception/CommandNotFoundException.php���b��w���=vendor/symfony/console/Exception/InvalidArgumentException.php���b��̽Z�7vendor/symfony/console/Exception/ExceptionInterface.phpy��by�9[&�Fvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php���b�] '¤4vendor/symfony/console/Formatter/OutputFormatter.phpB��bB��ݤ=vendor/symfony/console/Formatter/OutputFormatterInterface.php���b�v�LդBvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php���b���G�9vendor/symfony/console/Formatter/OutputFormatterStyle.phpD��bDE�޳�>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php���b�F�x��4vendor/symfony/console/Descriptor/TextDescriptor.php�"��b�"�TƤ3vendor/symfony/console/Descriptor/XmlDescriptor.php/��b/c��ޤ0vendor/symfony/console/Descriptor/Descriptor.phpi��biR���8vendor/symfony/console/Descriptor/MarkdownDescriptor.php���b��S]��9vendor/symfony/console/Descriptor/DescriptorInterface.php���b��P~��<vendor/symfony/console/Descriptor/ApplicationDescription.php� ��b� �g���4vendor/symfony/console/Descriptor/JsonDescriptor.php|��b|YA��,vendor/symfony/console/Input/StringInput.php?��b?��ˤ.vendor/symfony/console/Input/InputArgument.php��b��& �+vendor/symfony/console/Input/ArrayInput.phpf ��bf �����9vendor/symfony/console/Input/StreamableInputInterface.php���b���B��*vendor/symfony/console/Input/ArgvInput.phpb��bbEcOi�&vendor/symfony/console/Input/Input.phpB ��bB S*��/vendor/symfony/console/Input/InputInterface.php���b�rrg�0vendor/symfony/console/Input/InputDefinition.php���b�4�9��,vendor/symfony/console/Input/InputOption.php8 ��b8 ��ˤ4vendor/symfony/console/Input/InputAwareInterface.php���b��O��vendor/symfony/yaml/Inline.php�L��b�Le7��'vendor/symfony/yaml/Tag/TaggedValue.phpC��bC*�ʤvendor/symfony/yaml/Dumper.php ��b 9�|�vendor/symfony/yaml/Escaper.php���b�b��K�+vendor/symfony/yaml/Command/LintCommand.php���b���"�!vendor/symfony/yaml/Unescaper.php��bn����0vendor/symfony/yaml/Exception/ParseException.php\��b\�e��2vendor/symfony/yaml/Exception/RuntimeException.php���b���$��/vendor/symfony/yaml/Exception/DumpException.phps��bs�Ƥ4vendor/symfony/yaml/Exception/ExceptionInterface.phpv��bv0ʤvendor/symfony/yaml/Parser.php�y��b�y����vendor/symfony/yaml/Yaml.phpI��bI\�]��+vendor/symfony/polyfill-php72/bootstrap.phpw��bwH�J3�'vendor/symfony/polyfill-php72/Php72.php���b���٤Fvendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php���b��ѡŤ3vendor/symfony/event-dispatcher-contracts/Event.php���b�A��W�Kvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php���b����Z�Bvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.phpe$��be$2ZK�9vendor/symfony/event-dispatcher/Debug/WrappedListener.php� ��b� �oQ�<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php#��b#�����4vendor/symfony/event-dispatcher/LegacyEventProxy.php��bn?�3vendor/symfony/event-dispatcher/EventDispatcher.php���b�Bhb��<vendor/symfony/event-dispatcher/EventSubscriberInterface.php���b�!jcK�Mvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php[��b[AqK�Kvendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.phpZ��bZE�y�>vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php� ��b� R|%��0vendor/symfony/event-dispatcher/GenericEvent.php���b���H��<vendor/symfony/event-dispatcher/EventDispatcherInterface.php���b���)vendor/symfony/event-dispatcher/Event.php��bЛJr�;vendor/symfony/service-contracts/ServiceSubscriberTrait.phpN��bN�iq��3vendor/symfony/service-contracts/ResetInterface.phpy��by��j�=vendor/symfony/service-contracts/ServiceProviderInterface.php���b��R�k�<vendor/symfony/service-contracts/Test/ServiceLocatorTest.php���b�W7|G�?vendor/symfony/service-contracts/ServiceSubscriberInterface.php���b���d �8vendor/symfony/service-contracts/ServiceLocatorTrait.php� ��b� K��Y�vendor/autoload.php}��b}͑��Pvendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php���b��#��Gvendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.phph��bh�c� �]vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php���b���u�]vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.phpv��bvX~B�Wvendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.phpy��by�m7��Avendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php� ��b� &z�^�Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php��bo�+}�Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php���b�J��Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php���b���e�Fvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php���b�{N��Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php@��b@5�3�Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php ��b �P�Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php���b�[�A��Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php ��b ���Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php���b�ǒ���Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php,��b,1�5��Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php���b�����;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php���b�f4���Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php���b�M����Xvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.phpa��ba qäYvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php���b�4~���\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php���b���+j�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php,"��b,"�-�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php� ��b� �w�l�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php\��b\_}��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php���b�%l���Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php� ��b� �_��Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php���b�� Q��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php� ��b� e� ��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php& ��b& 78���Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.phps��bsd ޤTvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php� ��b� ��=��Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php*��b*1�4N�Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php� ��b� �PFF�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.phpz��bz�f�~�]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpu��bu=h��Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpj��bj�>��Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpl��bl�2���Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.phpf��bf�����Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpl��blA!LפWvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpo��bop����^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php���b�u-#1�Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php���b�%���Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php���b�\� �Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.phpw��bwN-�[�Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php���b���G�Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpI��bI%|��=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.phps��bs���#�0vendor/laravel-zero/phar-updater/src/Updater.php� ��b� �J.�Cvendor/laravel-zero/phar-updater/src/Strategy/StrategyInterface.php.��b. c:�=vendor/laravel-zero/phar-updater/src/Strategy/ShaStrategy.phpc��bc�����@vendor/laravel-zero/phar-updater/src/Strategy/GithubStrategy.php� ��b� ��73�6vendor/laravel-zero/phar-updater/src/VersionParser.php& ��b& 'p�Gvendor/laravel-zero/phar-updater/src/Exception/HttpRequestException.phpo��boD��Fvendor/laravel-zero/phar-updater/src/Exception/FilesystemException.phpn��bn����Gvendor/laravel-zero/phar-updater/src/Exception/JsonParsingException.phpo��boZ�?�Cvendor/laravel-zero/phar-updater/src/Exception/RuntimeException.php���b���Ћ�Gvendor/laravel-zero/phar-updater/src/Exception/NoSignatureException.phpo��bo"}<�Kvendor/laravel-zero/phar-updater/src/Exception/InvalidArgumentException.php���b�b"1�Evendor/laravel-zero/phar-updater/src/Exception/ExceptionInterface.phpX��bXcu�,�>vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php���b��1�Dvendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php���b�YT��4vendor/phar-io/manifest/src/exceptions/Exception.phpp��bpBO��Jvendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php���b�\;/�Bvendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php���b��G#�Evendor/phar-io/manifest/src/exceptions/ElementCollectionException.php���b������Cvendor/phar-io/manifest/src/exceptions/ManifestElementException.php���b���m}�Kvendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php(��b(��Vt�Jvendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php���b�&_ä@vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php���b��ä6vendor/phar-io/manifest/src/ManifestDocumentMapper.php\ ��b\ x�囤-vendor/phar-io/manifest/src/values/Author.php���b�2�G�Ivendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php���b�^�D�;vendor/phar-io/manifest/src/values/CopyrightInformation.php���b�F:���.vendor/phar-io/manifest/src/values/Library.php���b��1[u�7vendor/phar-io/manifest/src/values/AuthorCollection.php���b�B��{�<vendor/phar-io/manifest/src/values/PhpVersionRequirement.php���b�#����Avendor/phar-io/manifest/src/values/BundledComponentCollection.phpH��bH|?D2�2vendor/phar-io/manifest/src/values/Requirement.php_��b_M�4J�?vendor/phar-io/manifest/src/values/AuthorCollectionIterator.phpW��bW��R�>vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php'��b'���5�*vendor/phar-io/manifest/src/values/Url.php���b��s��,vendor/phar-io/manifest/src/values/Email.php���b�gH�ɤ6vendor/phar-io/manifest/src/values/ApplicationName.php���b��^��<vendor/phar-io/manifest/src/values/RequirementCollection.php��b�`L�2vendor/phar-io/manifest/src/values/Application.php���b��g�0vendor/phar-io/manifest/src/values/Extension.php���b�=W�}�Dvendor/phar-io/manifest/src/values/RequirementCollectionIterator.php���b���0�/vendor/phar-io/manifest/src/values/Manifest.php���b��,�ܤ+vendor/phar-io/manifest/src/values/Type.phpj��bj���ؤ.vendor/phar-io/manifest/src/values/License.phpI��bI��Ӭ�7vendor/phar-io/manifest/src/values/BundledComponent.php���b�z��3vendor/phar-io/manifest/src/xml/ManifestElement.php'��b'��\�.vendor/phar-io/manifest/src/xml/PhpElement.php���b��A$��4vendor/phar-io/manifest/src/xml/CopyrightElement.php{��b{0�B[�;vendor/phar-io/manifest/src/xml/AuthorElementCollection.php���b�L�h2�2vendor/phar-io/manifest/src/xml/LicenseElement.php��b��,��4vendor/phar-io/manifest/src/xml/ManifestDocument.phpU��bU�gV�4vendor/phar-io/manifest/src/xml/ExtensionElement.php!��b!����.vendor/phar-io/manifest/src/xml/ExtElement.php���b���ͤ4vendor/phar-io/manifest/src/xml/ComponentElement.php��b>�G��3vendor/phar-io/manifest/src/xml/ContainsElement.php���b�CV��1vendor/phar-io/manifest/src/xml/AuthorElement.php��b�[���3vendor/phar-io/manifest/src/xml/RequiresElement.php���b��q�>vendor/phar-io/manifest/src/xml/ComponentElementCollection.php���b�Ԗ�Ѥ2vendor/phar-io/manifest/src/xml/BundlesElement.php ��b �i�>�8vendor/phar-io/manifest/src/xml/ExtElementCollection.php���b�Zb��5vendor/phar-io/manifest/src/xml/ElementCollection.php���b�#�*9�2vendor/phar-io/manifest/src/ManifestSerializer.php`��b`,u��.vendor/phar-io/manifest/src/ManifestLoader.php��b~����Bvendor/phar-io/version/src/exceptions/NoBuildMetaDataException.php���b������Evendor/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php���b�XbpԤ3vendor/phar-io/version/src/exceptions/Exception.php~��b~x��|�Jvendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php���b������Avendor/phar-io/version/src/exceptions/InvalidVersionException.php���b�|���Ovendor/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php���b�/O��,vendor/phar-io/version/src/BuildMetaData.php[��b[)>��&vendor/phar-io/version/src/Version.php��b/C�_�5vendor/phar-io/version/src/VersionConstraintValue.php��b ���/vendor/phar-io/version/src/PreReleaseSuffix.phpY��bY���i�,vendor/phar-io/version/src/VersionNumber.php/��b/f,w[�6vendor/phar-io/version/src/VersionConstraintParser.php? ��b? M�Q��Qvendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php��b�&q��Cvendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php���b����$�<vendor/phar-io/version/src/constraints/VersionConstraint.php���b�w��_�Dvendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php���b�� �Ivendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php���b��Xx�Pvendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php ��b ���Dvendor/phar-io/version/src/constraints/AbstractVersionConstraint.phpK��bKqB��?vendor/phar-io/version/src/constraints/AnyVersionConstraint.php���b���^��Avendor/phar-io/version/src/constraints/ExactVersionConstraint.phpj��bj<�1n�Gvendor/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php���b�'B��Bvendor/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php<��b<B́�Fvendor/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php���b�#�R,�Evendor/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.phpv��bv�"dp�Gvendor/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php��b�;���Evendor/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php���b�?�]�;vendor/phpspec/prophecy/src/Prophecy/Comparator/Factory.php���b��L��Fvendor/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.phpN��bN# X�8vendor/phpspec/prophecy/src/Prophecy/Util/StringUtil.php&��b&� �H�8vendor/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php) ��b) �ĤFvendor/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php���b��\��@vendor/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php���b����B�Avendor/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php���b���/c�>vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php��b�^-5�=vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php���b�жҤ0vendor/phpspec/prophecy/src/Prophecy/Prophet.php8 ��b8 7����Hvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php=��b=�Ḗ�Lvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php[��b[���j�Avendor/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php>��b>��mӤFvendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php��b�a�N�Mvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php6��b6#��x�Evendor/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php���b����ǤKvendor/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php���b�A ��Gvendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.phpR��bRM���Gvendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php��b��ФGvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php���b�ߖ�Kvendor/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php ��b ��vѤGvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php���b�>v�[�Gvendor/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.phpJ��bJ� ��Dvendor/phpspec/prophecy/src/Prophecy/Argument/Token/InArrayToken.phpn��bn�}�H�Evendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php ��b %&�|�Gvendor/phpspec/prophecy/src/Prophecy/Argument/Token/NotInArrayToken.phps��bs���Fvendor/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php���b�V]稤Cvendor/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.phpp��bp�ˤJvendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php���b�Њ/��@vendor/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php���b�75:?�@vendor/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php��b�d��Cvendor/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.phpz��bz���:vendor/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.phpb��bb�Z� �Cvendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.phpr��br+h�ڤRvendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php"��b"�!]��Nvendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php���b�ӥ�q�Hvendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.phpm��bmqLu��Tvendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php��biM:c�1vendor/phpspec/prophecy/src/Prophecy/Argument.php#��b#Y5�[�8vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php���b�M�4F�2vendor/phpspec/prophecy/src/Prophecy/Call/Call.php���b�(�>�>vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php���b��[K�Nvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php_��b_�~��Gvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php8��b8c�1��Ivendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php ��b �Rm��Jvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php� ��b� {�*�Pvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentTypeNode.php���b�8)SդNvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ReturnTypeNode.phpV��bVe�ݎ�Pvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php��b�DP�Lvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php��b6'ᬤLvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php���b�c#�ܤMvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php;��b;O�(��Fvendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php���b�x�¤>vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.phpt��bt�L��;vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php���b��lE5�8vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php���b�2B`�@vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.phpQ��bQo̊ʤHvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.phpV��bVl2I�[vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php���b��kS��Svendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php7��b7L#�Nvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php���b��6�ǤJvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php���b��P���Ovendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php��b� ���Pvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php���b���;�Lvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php���b�KK&N�Jvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php���b��-;�Lvendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php���b��� �Wvendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php���b��|D�Qvendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php���b� ���[vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php���b��R��Vvendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php���b��f~��Nvendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php���b�w�i@�Pvendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php��bR3�k�<vendor/phpspec/prophecy/src/Prophecy/Exception/Exception.php`��b`���Svendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php���b��@���Mvendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php���b��_�n�Svendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php���b�e� �Ovendor/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.phpV��bVh0J+�Wvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php���b� � �Pvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.phpu��bu�Тy�Kvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php���b�m��Ovendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.phpj��bjg��&�Uvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php���b���R�Uvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php���b� l �Jvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php���b� �'}�Rvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.phpN��bN�K]�Qvendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php;��b;��I}�Kvendor/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php���b�Șh��vendor/composer/installed.phph��bh5v�?�"vendor/composer/autoload_files.php~��b~��lä!vendor/composer/autoload_real.phpI��bI���%vendor/composer/autoload_classmap.php���b��U���vendor/composer/ClassLoader.php\��b\�Zu��#vendor/composer/autoload_static.php�6��b�6l�8X�"vendor/composer/platform_check.php ��b �t��!vendor/composer/autoload_psr4.php���b����'�%vendor/composer/InstalledVersions.phpk��bk���j�'vendor/composer/autoload_namespaces.php���b�~��t�*vendor/composer/ca-bundle/src/CaBundle.php�$��b�$�n��/vendor/softcreatr/jsonpath/src/AccessHelper.phpA ��bA L����6vendor/softcreatr/jsonpath/src/Filters/IndexFilter.phpR��bR3�qV�6vendor/softcreatr/jsonpath/src/Filters/SliceFilter.php,��b,��uE�;vendor/softcreatr/jsonpath/src/Filters/QueryMatchFilter.php� ��b� �DȢ�9vendor/softcreatr/jsonpath/src/Filters/AbstractFilter.php���b����<vendor/softcreatr/jsonpath/src/Filters/QueryResultFilter.php���b��=��8vendor/softcreatr/jsonpath/src/Filters/IndexesFilter.php���b�����:vendor/softcreatr/jsonpath/src/Filters/RecursiveFilter.phpf��bf�˥�0vendor/softcreatr/jsonpath/src/JSONPathLexer.php���b�T3�U�4vendor/softcreatr/jsonpath/src/JSONPathException.php��bahT�+vendor/softcreatr/jsonpath/src/JSONPath.php���b�t���0vendor/softcreatr/jsonpath/src/JSONPathToken.php���b�#�M�/vendor/psr/container/src/ContainerInterface.php���b�����7vendor/psr/container/src/NotFoundExceptionInterface.phpq��bqR���8vendor/psr/container/src/ContainerExceptionInterface.phpN��bN�L��1vendor/psr/http-message/src/ResponseInterface.php��b��0vendor/psr/http-message/src/RequestInterface.php���b��V���6vendor/psr/http-message/src/ServerRequestInterface.phpo��bo �$h�0vendor/psr/http-message/src/MessageInterface.php���b��)t �/vendor/psr/http-message/src/StreamInterface.php���b�h�\l�5vendor/psr/http-message/src/UploadedFileInterface.phpz��bz�9�b�,vendor/psr/http-message/src/UriInterface.php|��b|���k�.vendor/guzzlehttp/promises/src/EachPromise.php� ��b� e"\�5vendor/guzzlehttp/promises/src/TaskQueueInterface.php���b�]-Ɩ�(vendor/guzzlehttp/promises/src/Utils.phpz ��bz *�9E�*vendor/guzzlehttp/promises/src/Promise.phph��bh�� $�4vendor/guzzlehttp/promises/src/functions_include.phph��bh��ʏ�,vendor/guzzlehttp/promises/src/Coroutine.php���b�g�Gq�5vendor/guzzlehttp/promises/src/RejectionException.php���b���Y��%vendor/guzzlehttp/promises/src/Is.php:��b:ĭ-��2vendor/guzzlehttp/promises/src/RejectedPromise.php;��b;���3vendor/guzzlehttp/promises/src/FulfilledPromise.php��b{A-�8vendor/guzzlehttp/promises/src/CancellationException.phpd��bd,/��,vendor/guzzlehttp/promises/src/functions.php���b�D��6�'vendor/guzzlehttp/promises/src/Each.php3��b3C�(+�)vendor/guzzlehttp/promises/src/Create.php'��b'��mF�3vendor/guzzlehttp/promises/src/PromiseInterface.php ��b �&� �4vendor/guzzlehttp/promises/src/PromisorInterface.phpi��bi�42x�,vendor/guzzlehttp/promises/src/TaskQueue.php���b�����5vendor/guzzlehttp/promises/src/AggregateException.php���b�� ��+vendor/guzzlehttp/psr7/src/MessageTrait.php���b��uw�*vendor/guzzlehttp/psr7/src/LimitStream.php}��b}.~�#�$vendor/guzzlehttp/psr7/src/Utils.phpG��bG��N�+vendor/guzzlehttp/psr7/src/AppendStream.php� ��b� ��k�*vendor/guzzlehttp/psr7/src/UriResolver.php���b� ��Ф"vendor/guzzlehttp/psr7/src/Uri.phpD*��bD*����&vendor/guzzlehttp/psr7/src/Request.phpD ��bD ��!U�3vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php>��b>�9X�,vendor/guzzlehttp/psr7/src/CachingStream.php���b�d\`M�,vendor/guzzlehttp/psr7/src/InflateStream.php���b��^��+vendor/guzzlehttp/psr7/src/BufferStream.php7��b7���%�)vendor/guzzlehttp/psr7/src/PumpStream.phpu��bug�ʂ�0vendor/guzzlehttp/psr7/src/functions_include.php]��b]���ɤ%vendor/guzzlehttp/psr7/src/Header.php���b�(YDȤ.vendor/guzzlehttp/psr7/src/MultipartStream.phpa ��ba eA���$vendor/guzzlehttp/psr7/src/Query.php ��b �Y�O�,vendor/guzzlehttp/psr7/src/ServerRequest.php:��b:�V ��&vendor/guzzlehttp/psr7/src/Rfc7230.php���b���ݤ+vendor/guzzlehttp/psr7/src/UploadedFile.php���b�+aC�,vendor/guzzlehttp/psr7/src/UriComparator.php���b����+vendor/guzzlehttp/psr7/src/NoSeekStream.php=��b=%TH��,vendor/guzzlehttp/psr7/src/UriNormalizer.php� ��b� �� 7�&vendor/guzzlehttp/psr7/src/Message.php���b��5�_�,vendor/guzzlehttp/psr7/src/StreamWrapper.php���b�2?�[�'vendor/guzzlehttp/psr7/src/MimeType.phpS ��bS ����%vendor/guzzlehttp/psr7/src/Stream.php;��b;:#E�-vendor/guzzlehttp/psr7/src/DroppingStream.php0��b0�H��'vendor/guzzlehttp/psr7/src/FnStream.php���b�d���(vendor/guzzlehttp/psr7/src/functions.php= ��b= AC 9�'vendor/guzzlehttp/psr7/src/Response.php ��b ?�$A�-vendor/guzzlehttp/psr7/src/LazyOpenStream.php���b��:�@�'vendor/guzzlehttp/guzzle/src/Client.php#��b#Y��X�1vendor/guzzlehttp/guzzle/src/MessageFormatter.php� ��b� ����0vendor/guzzlehttp/guzzle/src/ClientInterface.php���b��_�:�,vendor/guzzlehttp/guzzle/src/UriTemplate.phpU��bU_kV�&vendor/guzzlehttp/guzzle/src/Utils.php^��b^��6��-vendor/guzzlehttp/guzzle/src/HandlerStack.php� ��b� X{'�1vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php(��b( �L�1vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.phpG��bG�@c(�8vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php���b���&�5vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php���b��H9�:vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.phpH��bH?I��+vendor/guzzlehttp/guzzle/src/Middleware.php���b�����/vendor/guzzlehttp/guzzle/src/RequestOptions.php���b��W{�2vendor/guzzlehttp/guzzle/src/functions_include.phpa��ba��j�%vendor/guzzlehttp/guzzle/src/Pool.php���b��C��0vendor/guzzlehttp/guzzle/src/RetryMiddleware.php���b��$ͤ.vendor/guzzlehttp/guzzle/src/TransferStats.php���b��/c��=vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php���b���u�3vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php7��b7�P��4vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.phpv��bv�9f��4vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.phpH2��bH2�vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php���b�����?vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php���b�QѴY�>vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php� ��b� ��Ze�<vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php���b��k�?vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php���b�A�4o�=vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php���b�P��9vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php���b�j 2�3vendor/phpunit/phpunit/src/Runner/Hook/TestHook.phpm��bmA��x�Bvendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php���b���o��?vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php���b����פ/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php\��b\���%�=vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php���b��4b�:vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php� ��b� ߤ��(vendor/phpunit/phpunit/src/Exception.phpx��bx�( l�3vendor/phpunit/phpunit/src/Framework/TestResult.php8I��b8I%��;vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php���b���p&�9vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php���b�լ��5vendor/phpunit/phpunit/src/Framework/Error/Notice.phpp��bp&2L��6vendor/phpunit/phpunit/src/Framework/Error/Warning.phpq��bqUCH�9vendor/phpunit/phpunit/src/Framework/Error/Deprecated.phpt��bt�s�4vendor/phpunit/phpunit/src/Framework/Error/Error.php]��b]\�#v�Gvendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php���b��eOq�=vendor/phpunit/phpunit/src/Framework/Constraint/Attribute.phpB��bB7J-n�;vendor/phpunit/phpunit/src/Framework/Constraint/IsFalse.php��b�[�Bvendor/phpunit/phpunit/src/Framework/Constraint/StringContains.phpA��bA���>vendor/phpunit/phpunit/src/Framework/Constraint/FileExists.php���b��ې�Dvendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php���b� ͐U�Kvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.phpx��bx��\�<vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php���b�.�2�>vendor/phpunit/phpunit/src/Framework/Constraint/IsReadable.php���b�����=vendor/phpunit/phpunit/src/Framework/Constraint/Exception.php���b��◤?vendor/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php���b���N̤Bvendor/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php���b��.��>vendor/phpunit/phpunit/src/Framework/Constraint/IsWritable.php���b����Ȥ;vendor/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php���b���� �:vendor/phpunit/phpunit/src/Framework/Constraint/IsTrue.php��b�$o�?vendor/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php���b�P3l`�9vendor/phpunit/phpunit/src/Framework/Constraint/Count.php���b�%g�>vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php���b�����>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php���b�m�b�?vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php���b�ze�8�Lvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContainsEqual.php���b�`]�Ԥ>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php���b��ޞ�:vendor/phpunit/phpunit/src/Framework/Constraint/IsJson.php ��b �&e�Avendor/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php���b��|�@vendor/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php���b�U�1�Kvendor/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php���b�̵�<�Pvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContainsIdentical.php���b���A�Svendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php���b����ˤ:vendor/phpunit/phpunit/src/Framework/Constraint/IsType.php~ ��b~ q�6�>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalNot.php<��b<�yid�:vendor/phpunit/phpunit/src/Framework/Constraint/IsNull.php��bS��ʤCvendor/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php���b��C�/�Evendor/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php���b���l�Uvendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.php���b��J���<vendor/phpunit/phpunit/src/Framework/Constraint/SameSize.php���b�� ��Fvendor/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php)��b)�Ɲ �?vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php���b���>vendor/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php,��b,)%�)�9vendor/phpunit/phpunit/src/Framework/Constraint/IsNan.php��bW�TŤEvendor/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php ��b �o}�Gvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContains.phpP��bP0����>vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php���b�?����=vendor/phpunit/phpunit/src/Framework/Constraint/Composite.php��b���¤<vendor/phpunit/phpunit/src/Framework/Constraint/LessThan.php���b����}�Dvendor/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.phpb��bbN��;vendor/phpunit/phpunit/src/Framework/Constraint/IsEqual.php���b�=/�}�?vendor/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php2��b2�?��Rvendor/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.php���b�m����<vendor/phpunit/phpunit/src/Framework/Constraint/IsFinite.php$��b$ȺX�=vendor/phpunit/phpunit/src/Framework/Constraint/LogicalOr.phpj��bj)橷�4vendor/phpunit/phpunit/src/Framework/TestBuilder.php���b��YD��=vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php��b����Avendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.phpB��bB+RY��Nvendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php���b�n����Kvendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php���b�(�ˊ�Evendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php���b�_����Cvendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.phpH ��bH }��@�Lvendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php���b�0�$�Hvendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php��b��ĤFvendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.phpv��bv>�xZ�Gvendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php���b��)�Kvendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php���b�ɲv$�Hvendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php���b��΂�Cvendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php��be�Gvendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php���b�� s%�Qvendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_void.tpl}��b}&����Kvendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tplM��bM�7i�Pvendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_void.tpl��b �ΤIvendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tplQ��bQ�<�ȤRvendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl���b� 4�R�Ivendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl;��b;O5�s�Hvendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl���b�����Ivendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl<��b<��i��Lvendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl���b��񻕤Jvendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl���b��wZ�>vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php$��b$�1���Bvendor/phpunit/phpunit/src/Framework/MockObject/Builder/Match_.php���b�����Mvendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php���b�ź[��Lvendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php���b�/����Kvendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php���b���*�Kvendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php���b��g�G�@vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php���b�v�l�Dvendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php���b� j���8vendor/phpunit/phpunit/src/Framework/MockObject/Stub.phpp��bp5��.�>vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php���b���;vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php:��b:��M�>vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.phpo��bo��_Ť=vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php ^��b ^�����>vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php���b���9�?vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php���b��W`p�>vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php� ��b� A ��Hvendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php\��b\�MäGvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php���b�pN 4�Cvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php$��b$� ���Bvendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php\��b\a�g��Ivendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php���b�����=vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php ��b tv�~�Gvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.phpU��bU��8��Hvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.phpG��bG�E��Cvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php���b��䫣�Gvendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php���b�Ǎ,b�Evendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php� ��b� I�)�<vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php���b��Rթ�Fvendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php���b��f�Gvendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php���b�C�E��Tvendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php���b�-W���lvendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php���b���h��Nvendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php���b��D�ͤ^vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php���b�p-�*�=vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php��b��h�;vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php ��b ��t�1vendor/phpunit/phpunit/src/Framework/TestCase.php����b��1J?�2vendor/phpunit/phpunit/src/Framework/TestSuite.phpZ-��bZ-�!s'�7vendor/phpunit/phpunit/src/Framework/SelfDescribing.php���b�ú�]�8vendor/phpunit/phpunit/src/Framework/WarningTestCase.phpU��bU$�Q�4vendor/phpunit/phpunit/src/Framework/TestFailure.php���b��=u �Jvendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.phpH��bH'I՘�8vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php���b�>�Dd�7vendor/phpunit/phpunit/src/Framework/IncompleteTest.phpe��be��K(�4vendor/phpunit/phpunit/src/Framework/SkippedTest.phpb��bb����>vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php��b���5vendor/phpunit/phpunit/src/Framework/TestListener.php��b��H��-vendor/phpunit/phpunit/src/Framework/Test.php���b����p�9vendor/phpunit/phpunit/src/Framework/Assert/Functions.phpY���bY���l�Kvendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php���b� m�f�Avendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php~��b~ �V�Cvendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php���b�,R76�Lvendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php���b� ���Rvendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php���b����ؤRvendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php���b�j OƤHvendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php���b�XeƲ�<vendor/phpunit/phpunit/src/Framework/Exception/Exception.php���b�ð���Ovendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php���b�Ś ��Hvendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php���b�N^��:vendor/phpunit/phpunit/src/Framework/Exception/Warning.php���b��7UޤSvendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php���b�[�Q�Gvendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php���b�����Ovendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php���b����T�Fvendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php���b�߀铤>vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php���b��)äKvendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php��b©TE�Avendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php���b�?V��Hvendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.phpz��bz���v�Mvendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php4��b4~KeC�/vendor/phpunit/phpunit/src/Framework/Assert.phpA���bA��nU�:vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php*��b*P�+�8vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php��b��V�:vendor/phpunit/phpunit/src/Util/ConfigurationGenerator.phpo��bo�l�ʤ-vendor/phpunit/phpunit/src/Util/Blacklist.phpY ��bY 15��9vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php���b�����9vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php� ��b� |c�֤=vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl&��b&T����?vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl) ��b) y�Ѥ>vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl� ��b� ����:vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php���b���Τ?vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php���b�'��"�1vendor/phpunit/phpunit/src/Util/Configuration.php�`��b�`*�F�-vendor/phpunit/phpunit/src/Util/Exception.php���b�m /r�;vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php���b�qշ�(vendor/phpunit/phpunit/src/Util/Json.php���b���je�*vendor/phpunit/phpunit/src/Util/Getopt.phpV ��bV S�.��0vendor/phpunit/phpunit/src/Util/ErrorHandler.php/ ��b/ .���.vendor/phpunit/phpunit/src/Util/Filesystem.php���b����+vendor/phpunit/phpunit/src/Util/Printer.php���b��F��0vendor/phpunit/phpunit/src/Util/Log/TeamCity.php���b�4�<��-vendor/phpunit/phpunit/src/Util/Log/JUnit.php� ��b� ]���*vendor/phpunit/phpunit/src/Util/Filter.php���b�M'��.vendor/phpunit/phpunit/src/Util/FileLoader.php3��b3R���'vendor/phpunit/phpunit/src/Util/Xml.php ��b /آd�)vendor/phpunit/phpunit/src/Util/Color.php^ ��b^ ��ʤ5vendor/phpunit/phpunit/src/Util/RegularExpression.phpP��bP_����7vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.phpI1��bI1���X�7vendor/phpunit/phpunit/src/Util/Annotation/Registry.phpG��bG���(vendor/phpunit/phpunit/src/Util/Type.php?��b?LM�$�(vendor/phpunit/phpunit/src/Util/Test.php�H��b�H {�.vendor/phpunit/phpunit/src/Util/Reflection.phpy��by1�H��/vendor/phpunit/phpunit/src/Util/GlobalState.php?��b?�����7vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php3��b3q����=vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php���b�=�B�:vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php���b�~ ߤ<vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php���b��Ha6�=vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.phpO��bO�)L|�=vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php&��b&���Ԥ=vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php���b��q�u�9vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php���b��h]S�:vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php���b��g8Ѥ0vendor/phpunit/phpunit/src/TextUI/TestRunner.php!���b!�D�}d�/vendor/phpunit/phpunit/src/TextUI/Exception.php���b�VR��3vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php�(��b�(���J�*vendor/phpunit/phpunit/src/TextUI/Help.php�%��b�%���-vendor/phpunit/phpunit/src/TextUI/Command.php�c��b�c1qd�2vendor/phpunit/php-token-stream/src/Token/Util.php���b�~ �"�Cvendor/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php���b������4vendor/phpunit/php-token-stream/src/Token/Stream.php�"��b�"�N��-vendor/phpunit/php-token-stream/src/Token.phpH��bHX�Q-�6vendor/phpunit/php-code-coverage/src/Driver/Xdebug.php���b��� n�6vendor/phpunit/php-code-coverage/src/Driver/Driver.phpW��bW��!��4vendor/phpunit/php-code-coverage/src/Driver/PCOV.php���b��1��6vendor/phpunit/php-code-coverage/src/Driver/PHPDBG.php���b�Q�B��-vendor/phpunit/php-code-coverage/src/Util.php���b��� ɤ5vendor/phpunit/php-code-coverage/src/CodeCoverage.php�E��b�EbHܤ7vendor/phpunit/php-code-coverage/src/Node/Directory.php:��b:��l̤6vendor/phpunit/php-code-coverage/src/Node/Iterator.phpK��bK�lZU�5vendor/phpunit/php-code-coverage/src/Node/Builder.php� ��b� ��c�:vendor/phpunit/php-code-coverage/src/Node/AbstractNode.phpU��bUh�֤2vendor/phpunit/php-code-coverage/src/Node/File.php�%��b�%���;�0vendor/phpunit/php-code-coverage/src/Version.phpu��bu�P¤/vendor/phpunit/php-code-coverage/src/Filter.php3 ��b3 ����4vendor/phpunit/php-code-coverage/src/Report/Text.php=��b=%'�(�3vendor/phpunit/php-code-coverage/src/Report/PHP.php���b���0t�:vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php���b�V Ё�=vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.php���b�v͆ �Dvendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php���b�Y~|'�:vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php� ��b� C�j�8vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php���b��6��:vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php���b��Wɚ�:vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.phpE��bEP�\>�<vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.phpZ��bZ![��8vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.phpq��bq��B�:vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php��bd�:�8vendor/phpunit/php-code-coverage/src/Report/Xml/File.php���b�?�8�9vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php(��b(M�� �;vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php=��b=��ER�=vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.phpv��bv���;vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php4��b4y/���Gvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php> ��b> ��XˤGvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php���b�z��Q�Qvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist ��b �f "�Vvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.distG��bG��l�Xvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item.html.dist���b���s:�[vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg���b���Z��Vvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg0��b0�QUU�Vvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist���b�G�M��Vvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.distt��bt���Xvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.cssn`��bn`��ӤSvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/octicons.cssX��bX'#��Pvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css���b���v�Tvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.cssX%��bX%�0,�Qvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/custom.css��b�[vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.distA��bAds�Svendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.jsQX��bQXQ��[�Vvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js����b��'�^�Rvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/nv.d3.min.js�R��b�RݤSvendor/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php���b������Vvendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php���b�Q�B�Cvendor/phpunit/php-code-coverage/src/Exception/RuntimeException.php���b�x�)ޤKvendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php���b���Mv�0vendor/theseer/tokenizer/src/TokenCollection.php���b�O���*vendor/theseer/tokenizer/src/Tokenizer.php9��b9>�_/�*vendor/theseer/tokenizer/src/Exception.phpf��bf����9vendor/theseer/tokenizer/src/TokenCollectionException.phpt��bt��u�.vendor/theseer/tokenizer/src/XMLSerializer.php���b����O�-vendor/theseer/tokenizer/src/NamespaceUri.php���b� �X�&vendor/theseer/tokenizer/src/Token.php���b���X��6vendor/theseer/tokenizer/src/NamespaceUriException.phpq��bq)0�U�vendor/hoa/event/Source.php\��b\����vendor/hoa/event/Listener.php���b�*�>��%vendor/hoa/event/Test/Unit/Source.php ��b VbJ�'vendor/hoa/event/Test/Unit/Listener.php���b��1��(vendor/hoa/event/Test/Unit/Exception.phpC��bC�g%L�%vendor/hoa/event/Test/Unit/Bucket.php���b��^��&vendor/hoa/event/Test/Unit/Listens.php���b�*�]o�)vendor/hoa/event/Test/Unit/Listenable.php,��b,�'�5�$vendor/hoa/event/Test/Unit/Event.phpH��bH{._֤vendor/hoa/event/Exception.php���b��x샤vendor/hoa/event/Bucket.php���b����v�vendor/hoa/event/Listens.php~��b~�w��vendor/hoa/event/Listenable.php���b���� �vendor/hoa/event/Event.php� ��b� �x`c�vendor/hoa/ustring/Search.php��b�OIV�&vendor/hoa/ustring/Test/Unit/Issue.php?��b?���h�'vendor/hoa/ustring/Test/Unit/Search.php��bԠ�ݤ(vendor/hoa/ustring/Test/Unit/Ustring.php�D��b�D�(q�� vendor/hoa/ustring/Exception.php���b��H���!vendor/hoa/ustring/Bin/Tocode.php���b�x�HӤ#vendor/hoa/ustring/Bin/Fromcode.php���b� s ��vendor/hoa/ustring/Ustring.php�.��b�. �uݤ vendor/hoa/protocol/Protocol.php� ��b� b�ʨ�*vendor/hoa/protocol/Test/Unit/Protocol.php{ ��b{ ݹw��)vendor/hoa/protocol/Test/Unit/Wrapper.php1A��b1A�C �+vendor/hoa/protocol/Test/Unit/Exception.phpI��bI�� A�.vendor/hoa/protocol/Test/Unit/Node/Library.php���b��T/5�+vendor/hoa/protocol/Test/Unit/Node/Node.php���b���SĤvendor/hoa/protocol/Wrapper.php���b�?� "�!vendor/hoa/protocol/Exception.php���b�E� �#vendor/hoa/protocol/Bin/Resolve.php���b�vh��$vendor/hoa/protocol/Node/Library.php���b��kO�!vendor/hoa/protocol/Node/Node.php���b�����:vendor/hoa/stream/Test/Integration/Filter/LateComputed.phpD��bD�B0�4vendor/hoa/stream/Test/Integration/Filter/Filter.phpg ��bg ���-vendor/hoa/stream/Test/Integration/Stream.phpe��be;\��)vendor/hoa/stream/Test/Unit/Exception.phpb��bb:���9vendor/hoa/stream/Test/Unit/Wrapper/IWrapper/IWrapper.php���b�)�W�7vendor/hoa/stream/Test/Unit/Wrapper/IWrapper/Stream.phpv��bv� ڛ�5vendor/hoa/stream/Test/Unit/Wrapper/IWrapper/File.phpp��bp�j�_�/vendor/hoa/stream/Test/Unit/Wrapper/Wrapper.php� ��b� �W�&�1vendor/hoa/stream/Test/Unit/Wrapper/Exception.phpg��bgO��0vendor/hoa/stream/Test/Unit/IStream/Statable.php��b=���1vendor/hoa/stream/Test/Unit/IStream/Touchable.php#��b#��IL�*vendor/hoa/stream/Test/Unit/IStream/In.php���b�-���2vendor/hoa/stream/Test/Unit/IStream/Structural.php���b�` ֤+vendor/hoa/stream/Test/Unit/IStream/Out.php���b�� ���1vendor/hoa/stream/Test/Unit/IStream/Pointable.php���b�Zg���.vendor/hoa/stream/Test/Unit/IStream/Stream.php[��b[[�¹�2vendor/hoa/stream/Test/Unit/IStream/Bufferable.php���b�7��פ0vendor/hoa/stream/Test/Unit/IStream/Lockable.php ��b :�6,�0vendor/hoa/stream/Test/Unit/IStream/Pathable.php���b��ε.�&vendor/hoa/stream/Test/Unit/Bucket.php� ��b� 隞o�'vendor/hoa/stream/Test/Unit/Context.php� ��b� ���0vendor/hoa/stream/Test/Unit/Filter/Exception.phpa��ba]BT��-vendor/hoa/stream/Test/Unit/Filter/Filter.php� ��b� ���]�,vendor/hoa/stream/Test/Unit/Filter/Basic.php��b���&vendor/hoa/stream/Test/Unit/Stream.php�8��b�8�>���)vendor/hoa/stream/Test/Unit/Composite.php���b��쑤�vendor/hoa/stream/Exception.php���b�'�����vendor/hoa/console/Cursor.php$#��b$#^G�� vendor/hoa/iterator/Multiple.phpF��bFg�2�)vendor/hoa/iterator/CallbackGenerator.php���b�� �Ԥ!vendor/hoa/iterator/Directory.php���b�̇vФ*vendor/hoa/iterator/Test/Unit/Multiple.php���b�$6=�3vendor/hoa/iterator/Test/Unit/CallbackGenerator.php���b���{��+vendor/hoa/iterator/Test/Unit/Directory.php���b���[��-vendor/hoa/iterator/Test/Unit/SplFileInfo.php. ��b. �y�,vendor/hoa/iterator/Test/Unit/FileSystem.php��bVc&�0vendor/hoa/iterator/Test/Unit/CallbackFilter.php? ��b? K���(vendor/hoa/iterator/Test/Unit/Append.php���b�ܙ�פ/vendor/hoa/iterator/Test/Unit/Demultiplexer.php���b���O�&vendor/hoa/iterator/Test/Unit/Mock.php<��b<Ŝ%X�%vendor/hoa/iterator/Test/Unit/Map.phpc��bc�X��'vendor/hoa/iterator/Test/Unit/Limit.php���b��һ��)vendor/hoa/iterator/Test/Unit/Counter.phpP��bPĊ�s�2vendor/hoa/iterator/Test/Unit/IteratorIterator.php���b�޹���(vendor/hoa/iterator/Test/Unit/Filter.php"��b"o�% �*vendor/hoa/iterator/Test/Unit/Repeater.php���b�Z�K�3vendor/hoa/iterator/Test/Unit/RegularExpression.php���b���Gä*vendor/hoa/iterator/Test/Unit/Infinite.php���b���;�*vendor/hoa/iterator/Test/Unit/NoRewind.phpS��bS��� �+vendor/hoa/iterator/Test/Unit/Lookahead.phpJ ��bJ �v ��,vendor/hoa/iterator/Test/Unit/Lookbehind.phpO ��bO ��퍤(vendor/hoa/iterator/Test/Unit/Buffer.php� ��b� ����#vendor/hoa/iterator/SplFileInfo.php���b�.��!vendor/hoa/iterator/Aggregate.php}��b}u��6� vendor/hoa/iterator/Iterator.php���b���`��"vendor/hoa/iterator/FileSystem.php~��b~^����&vendor/hoa/iterator/CallbackFilter.php���b���lf�!vendor/hoa/iterator/Exception.php���b�Isq�vendor/hoa/iterator/Append.phps��bs�?3��%vendor/hoa/iterator/Demultiplexer.php���b���v��vendor/hoa/iterator/Glob.phpo��bo��S{�vendor/hoa/iterator/Mock.phpp��bp�ZW{�vendor/hoa/iterator/Map.phpo��bo�]�դvendor/hoa/iterator/Outer.phpu��bu�/���vendor/hoa/iterator/Limit.phpq��bq����vendor/hoa/iterator/Counter.php4��b4����(vendor/hoa/iterator/IteratorIterator.php��bt���vendor/hoa/iterator/Filter.php|��b|�V�X�+vendor/hoa/iterator/Recursive/Directory.php���b�el¤+vendor/hoa/iterator/Recursive/Recursive.php���b�����*vendor/hoa/iterator/Recursive/Iterator.php���b���Ҥ0vendor/hoa/iterator/Recursive/CallbackFilter.php���b���Ń�&vendor/hoa/iterator/Recursive/Mock.php��b>��j�%vendor/hoa/iterator/Recursive/Map.php���b�*��\�(vendor/hoa/iterator/Recursive/Filter.php���b��o��3vendor/hoa/iterator/Recursive/RegularExpression.php/��b/ e3� vendor/hoa/iterator/Repeater.php`��b`��vendor/codeception/module-sequence/src/Codeception/Util/sq.php4��b4&���Fvendor/codeception/module-sequence/src/Codeception/Module/Sequence.php� ��b� ��'V�Hvendor/codeception/module-mongodb/src/Codeception/Lib/Driver/MongoDb.php���b��u�*�Dvendor/codeception/module-mongodb/src/Codeception/Module/MongoDb.php�:��b�:����Dvendor/codeception/module-asserts/src/Codeception/Module/Asserts.phpi��bi�a�Lvendor/codeception/module-asserts/src/Codeception/Module/AbstractAsserts.php���b��h��?vendor/codeception/phpunit-wrapper/src/ResultPrinter/Report.phpG��bG)=�Pvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/scenario.html.dist���b��S&��Qvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/scenarios.html.dist���b�/Z���Mvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/suite.html.dist3��b3�|���Pvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/substeps.html.dist#��b#͠w�Wvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/scenario_header.html.dist3��b3�ʤ�Lvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/step.html.distb��bb�dLvendor/codeception/phpunit-wrapper/src/ResultPrinter/template/fail.html.dist;��b;˟���=vendor/codeception/phpunit-wrapper/src/ResultPrinter/HTML.php�%��b�%D��a�;vendor/codeception/phpunit-wrapper/src/ResultPrinter/UI.php� ��b� U�g�3vendor/codeception/phpunit-wrapper/src/Listener.php��b7��>�<vendor/codeception/phpunit-wrapper/src/DispatcherWrapper.php���b���,�Bvendor/codeception/phpunit-wrapper/src/Constraint/WebDriverNot.php���b�� N��Bvendor/codeception/phpunit-wrapper/src/Constraint/JsonContains.php��bc�L�:vendor/codeception/phpunit-wrapper/src/Constraint/Page.php ��b e-��@vendor/codeception/phpunit-wrapper/src/Constraint/CrawlerNot.phpK��bK��z�>vendor/codeception/phpunit-wrapper/src/Constraint/JsonType.php��b����=vendor/codeception/phpunit-wrapper/src/Constraint/Crawler.php1��b1��,�?vendor/codeception/phpunit-wrapper/src/Constraint/WebDriver.php ��b ��ۄ�>vendor/codeception/phpunit-wrapper/src/NonFinal/TestRunner.php[���b[� �6�Fvendor/codeception/phpunit-wrapper/src/NonFinal/NameFilterIterator.php� ��b� ��:�9vendor/codeception/phpunit-wrapper/src/NonFinal/JUnit.php'.��b'.�91/�3vendor/codeception/phpunit-wrapper/src/TestCase.php���b�Y,Q��9vendor/codeception/phpunit-wrapper/src/ConsolePrinter.phpv��bv97MP�>vendor/codeception/phpunit-wrapper/src/phpunit7-interfaces.php���b��*���6vendor/codeception/phpunit-wrapper/src/Log/PhpUnit.php���b����.�4vendor/codeception/phpunit-wrapper/src/Log/JUnit.php���b��h��8vendor/codeception/phpunit-wrapper/src/ResultPrinter.php� ��b� Dž���;vendor/codeception/phpunit-wrapper/src/phpunit5-loggers.php�K��b�K����1vendor/codeception/phpunit-wrapper/src/Runner.phpY��bY(T3��5vendor/codeception/phpunit-wrapper/src/FilterTest.php=��b=f���/vendor/codeception/phpunit-wrapper/src/shim.php���b�VaY��/vendor/codeception/phpunit-wrapper/src/Init.php��b���̤;vendor/codeception/phpunit-wrapper/src/Overrides/Filter.phpN ��bN X6� �/vendor/codeception/phpunit-wrapper/RoboFile.php���b�xw��Vvendor/codeception/module-webdriver/src/Codeception/Lib/Interfaces/SessionSnapshot.php���b�_��y�Vvendor/codeception/module-webdriver/src/Codeception/Lib/Interfaces/ScreenshotSaver.phph��bh�h��Hvendor/codeception/module-webdriver/src/Codeception/Module/WebDriver.php����b���$/�Uvendor/codeception/module-webdriver/src/Codeception/Exception/ConnectionException.php`��b`LA�@vendor/codeception/module-rest/src/Codeception/Util/JsonType.php�"��b�"e�N�Avendor/codeception/module-rest/src/Codeception/Util/JsonArray.php:��b:.����>vendor/codeception/module-rest/src/Codeception/Step/AsJson.php���b��Ԭw�>vendor/codeception/module-rest/src/Codeception/Module/REST.php���b��f��Hvendor/codeception/module-queue/src/Codeception/Lib/Interfaces/Queue.php^��b^^�=�Cvendor/codeception/module-queue/src/Codeception/Lib/Driver/Iron.php� ��b� T� c�Hvendor/codeception/module-queue/src/Codeception/Lib/Driver/AmazonSQS.php[��b[��>d�Jvendor/codeception/module-queue/src/Codeception/Lib/Driver/Pheanstalk4.php���b��L�x�Hvendor/codeception/module-queue/src/Codeception/Lib/Driver/Beanstalk.php:��b:���Ѥ@vendor/codeception/module-queue/src/Codeception/Module/Queue.php�,��b�,Ń���@vendor/codeception/module-redis/src/Codeception/Module/Redis.php�R��b�R=��1vendor/codeception/stub/src/Test/Feature/Stub.php�(��b�(w$�֤$vendor/codeception/stub/src/Stub.phpPW��bPWkjL�-vendor/codeception/stub/src/Stub/Expected.php���b�!��l�3vendor/codeception/stub/src/Stub/ConsecutiveMap.phpU��bU��桤2vendor/codeception/stub/src/Stub/StubMarshaler.php��bO�o�$vendor/codeception/stub/RoboFile.php4��b4(����<vendor/codeception/module-ftp/src/Codeception/Module/FTP.phpM^��bM^]� �Fvendor/codeception/module-memcache/src/Codeception/Module/Memcache.php���b�� ��<vendor/codeception/module-cli/src/Codeception/Module/Cli.php ��b b��>vendor/codeception/module-amqp/src/Codeception/Module/AMQP.php�/��b�/��Up�Mvendor/codeception/module-phpbrowser/src/Codeception/Lib/Connector/Guzzle.phpk.��bk.����Jvendor/codeception/module-phpbrowser/src/Codeception/Module/PhpBrowser.php���b�5/u:�Fvendor/codeception/lib-asserts/src/Codeception/Util/Shared/Asserts.php� ��b� �+�ĤOvendor/codeception/lib-asserts/src/Codeception/Util/Shared/InheritedAsserts.php���b���ڙ�Bvendor/codeception/module-db/src/Codeception/Lib/Interfaces/Db.php< ��b< Δ/פ>vendor/codeception/module-db/src/Codeception/Lib/Driver/Db.php�"��b�"��ҳ�Avendor/codeception/module-db/src/Codeception/Lib/Driver/MySql.php���b�_�d(�?vendor/codeception/module-db/src/Codeception/Lib/Driver/Oci.phpA��bA�s�M�Bvendor/codeception/module-db/src/Codeception/Lib/Driver/SqlSrv.phpi ��bi �G8�Fvendor/codeception/module-db/src/Codeception/Lib/Driver/PostgreSql.phph��bh99��Bvendor/codeception/module-db/src/Codeception/Lib/Driver/Sqlite.php� ��b� �@ v�@vendor/codeception/module-db/src/Codeception/Lib/DbPopulator.php��b��jC�:vendor/codeception/module-db/src/Codeception/Module/Db.php����b����{1�/vendor/codeception/codeception/ext/Recorder.php@T��b@T�^;�0vendor/codeception/codeception/ext/RunBefore.phpG��bG0�?��5vendor/codeception/codeception/ext/SimpleReporter.php���b��mA[�2vendor/codeception/codeception/ext/DotReporter.php ��b |�hq�0vendor/codeception/codeception/ext/RunFailed.php� ��b� K��-vendor/codeception/codeception/ext/Logger.php���b�t��9�1vendor/codeception/codeception/ext/RunProcess.php���b�'*��Evendor/codeception/codeception/src/Codeception/Lib/Interfaces/API.phpb��bbD�^ǤRvendor/codeception/codeception/src/Codeception/Lib/Interfaces/DoctrineProvider.phpu��bu��'�Pvendor/codeception/codeception/src/Codeception/Lib/Interfaces/ElementLocator.php���b���N�Hvendor/codeception/codeception/src/Codeception/Lib/Interfaces/Remote.php���b�R��\�Qvendor/codeception/codeception/src/Codeception/Lib/Interfaces/DependsOnModule.php_��b_�Ī��Uvendor/codeception/codeception/src/Codeception/Lib/Interfaces/ConflictsWithModule.php���b��hГ�Lvendor/codeception/codeception/src/Codeception/Lib/Interfaces/DataMapper.phpf��bf?j�Nvendor/codeception/codeception/src/Codeception/Lib/Interfaces/PartedModule.php��b��T�Qvendor/codeception/codeception/src/Codeception/Lib/Interfaces/PageSourceSaver.php_��b_蹉�Qvendor/codeception/codeception/src/Codeception/Lib/Interfaces/RequiresPackage.php���b��Q�Nvendor/codeception/codeception/src/Codeception/Lib/Interfaces/MultiSession.php��b)�wb�Evendor/codeception/codeception/src/Codeception/Lib/Interfaces/Web.phpRo��bRo �_�Nvendor/codeception/codeception/src/Codeception/Lib/Interfaces/ActiveRecord.phpA��bA�NŤEvendor/codeception/codeception/src/Codeception/Lib/Interfaces/ORM.php?��b?��ΤFvendor/codeception/codeception/src/Codeception/Lib/ModuleContainer.php�C��b�C~���9vendor/codeception/codeception/src/Codeception/Lib/Di.phpA��bA@q�=vendor/codeception/codeception/src/Codeception/Lib/Friend.php���b�[����Hvendor/codeception/codeception/src/Codeception/Lib/Generator/Actions.php6$��b6$�� ��Hvendor/codeception/codeception/src/Codeception/Lib/Generator/Feature.php���b�L<�Fvendor/codeception/codeception/src/Codeception/Lib/Generator/Actor.phpA ��bA ��g-�Kvendor/codeception/codeception/src/Codeception/Lib/Generator/StepObject.php���b�`��Pvendor/codeception/codeception/src/Codeception/Lib/Generator/GherkinSnippets.php���b��;�Qvendor/codeception/codeception/src/Codeception/Lib/Generator/Shared/Classname.php��b/��!�Kvendor/codeception/codeception/src/Codeception/Lib/Generator/PageObject.php���b������Gvendor/codeception/codeception/src/Codeception/Lib/Generator/Helper.php���b��yi6�Evendor/codeception/codeception/src/Codeception/Lib/Generator/Cept.php���b��uA�Evendor/codeception/codeception/src/Codeception/Lib/Generator/Test.phpJ��bJ�A:��Evendor/codeception/codeception/src/Codeception/Lib/Generator/Cest.php��b���9�Fvendor/codeception/codeception/src/Codeception/Lib/Generator/Group.phpB��bB�+�b�Ivendor/codeception/codeception/src/Codeception/Lib/Generator/Snapshot.php��b�� ۤKvendor/codeception/codeception/src/Codeception/Lib/Actor/Shared/Comment.php���b��`��Ivendor/codeception/codeception/src/Codeception/Lib/Actor/Shared/Retry.php���b��^�Jvendor/codeception/codeception/src/Codeception/Lib/Actor/Shared/Friend.php���b��tG��Ivendor/codeception/codeception/src/Codeception/Lib/Actor/Shared/Pause.php���b�^=*P�Hvendor/codeception/codeception/src/Codeception/Lib/Console/Colorizer.phpQ��bQ�z� �Jvendor/codeception/codeception/src/Codeception/Lib/Console/DiffFactory.phpB��bBj���Mvendor/codeception/codeception/src/Codeception/Lib/Console/MessageFactory.php���b�߻W��Jvendor/codeception/codeception/src/Codeception/Lib/Console/ReplHistory.php���b���a �Fvendor/codeception/codeception/src/Codeception/Lib/Console/Message.php& ��b& )�X�Evendor/codeception/codeception/src/Codeception/Lib/Console/Output.php� ��b� c�J¤Cvendor/codeception/codeception/src/Codeception/Lib/ParamsLoader.php&��b&�"�X�Cvendor/codeception/codeception/src/Codeception/Lib/GroupManager.phpf��bf ���`vendor/codeception/codeception/src/Codeception/Lib/Connector/Shared/PhpSuperGlobalsConverter.php���b���.�Cvendor/codeception/codeception/src/Codeception/Lib/Notification.php���b���<�=vendor/codeception/codeception/src/Codeception/Lib/Parser.phpH��bH����Ivendor/codeception/codeception/src/Codeception/Event/PrintResultEvent.php���b�Ok0äJvendor/codeception/codeception/src/Codeception/Event/DispatcherWrapper.php���b���r�Bvendor/codeception/codeception/src/Codeception/Event/StepEvent.phpb��bb6T��Bvendor/codeception/codeception/src/Codeception/Event/FailEvent.php��b�7�w�Bvendor/codeception/codeception/src/Codeception/Event/TestEvent.php~��b~�� �Cvendor/codeception/codeception/src/Codeception/Event/SuiteEvent.php���b� o��8vendor/codeception/codeception/src/Codeception/Actor.php���b�g �i�Lvendor/codeception/codeception/src/Codeception/Test/Interfaces/Dependent.phpo��bo�N�x�Nvendor/codeception/codeception/src/Codeception/Test/Interfaces/Descriptive.php���b�֙�Hvendor/codeception/codeception/src/Codeception/Test/Interfaces/Plain.phpn��bn�E���Kvendor/codeception/codeception/src/Codeception/Test/Interfaces/Reported.php���b��G"+�Qvendor/codeception/codeception/src/Codeception/Test/Interfaces/StrictCoverage.php���b����Qvendor/codeception/codeception/src/Codeception/Test/Interfaces/ScenarioDriven.phpD��bD&<�@vendor/codeception/codeception/src/Codeception/Test/Metadata.php���b�*� ��>vendor/codeception/codeception/src/Codeception/Test/Loader.php^��b^����Bvendor/codeception/codeception/src/Codeception/Test/Descriptor.php2��b2��r�Nvendor/codeception/codeception/src/Codeception/Test/Loader/LoaderInterface.php���b��F��Fvendor/codeception/codeception/src/Codeception/Test/Loader/Gherkin.php��bh�W��Cvendor/codeception/codeception/src/Codeception/Test/Loader/Cept.php��b�Nގ�Cvendor/codeception/codeception/src/Codeception/Test/Loader/Unit.php� ��b� ��v�Cvendor/codeception/codeception/src/Codeception/Test/Loader/Cest.php���b�h""n�?vendor/codeception/codeception/src/Codeception/Test/Gherkin.php��b�்�<vendor/codeception/codeception/src/Codeception/Test/Cept.php2��b2}a�i�Nvendor/codeception/codeception/src/Codeception/Test/Feature/ScenarioLoader.phpC��bC�N<��Pvendor/codeception/codeception/src/Codeception/Test/Feature/AssertionCounter.php���b�/� y�Qvendor/codeception/codeception/src/Codeception/Test/Feature/MetadataCollector.php��b�Y��Kvendor/codeception/codeception/src/Codeception/Test/Feature/ErrorLogger.php���b� �ҥ�Lvendor/codeception/codeception/src/Codeception/Test/Feature/CodeCoverage.php��bW�e�Wvendor/codeception/codeception/src/Codeception/Test/Feature/IgnoreIfMetadataBlocked.php���b� �B�<vendor/codeception/codeception/src/Codeception/Test/Test.php���b�!���<vendor/codeception/codeception/src/Codeception/Test/Unit.php���b�ͿZ=�<vendor/codeception/codeception/src/Codeception/Test/Cest.php���b��=��9vendor/codeception/codeception/src/Codeception/Events.php���b����S�>vendor/codeception/codeception/src/Codeception/GroupObject.php���b�Dj�f�;vendor/codeception/codeception/src/Codeception/Codecept.php< ��b< ^. �@vendor/codeception/codeception/src/Codeception/Configuration.php�b��b�b�3��Ovendor/codeception/codeception/src/Codeception/Util/ArrayContainsComparator.php���b�Ig��Dvendor/codeception/codeception/src/Codeception/Util/PathResolver.php���b��e]\�@vendor/codeception/codeception/src/Codeception/Util/Fixtures.php���b�`դ=vendor/codeception/codeception/src/Codeception/Util/Debug.php���b��ۼr�Bvendor/codeception/codeception/src/Codeception/Util/FileSystem.phpW ��bW -2p�@vendor/codeception/codeception/src/Codeception/Util/Autoload.php���b�����;vendor/codeception/codeception/src/Codeception/Util/Uri.php,��b,<���Dvendor/codeception/codeception/src/Codeception/Util/XmlStructure.phpZ ��bZ KP�T�Ivendor/codeception/codeception/src/Codeception/Util/Shared/Namespaces.php���b�1u�@vendor/codeception/codeception/src/Codeception/Util/Template.php5��b5qn��=vendor/codeception/codeception/src/Codeception/Util/Maybe.phpL��bL2w��<vendor/codeception/codeception/src/Codeception/Util/Soap.phpn��bn�4uK�<vendor/codeception/codeception/src/Codeception/Util/Stub.php��b�x55�Fvendor/codeception/codeception/src/Codeception/Util/ActionSequence.php� ��b� 2����Hvendor/codeception/codeception/src/Codeception/Util/ReflectionHelper.php���b��ѕ��;vendor/codeception/codeception/src/Codeception/Util/Xml.phpz��bz~HE��Rvendor/codeception/codeception/src/Codeception/Util/ReflectionPropertyAccessor.php� ��b� ��ը�Bvendor/codeception/codeception/src/Codeception/Util/Annotation.php=��b=� ��?vendor/codeception/codeception/src/Codeception/Util/Locator.php3(��b3(�۪�Bvendor/codeception/codeception/src/Codeception/Util/XmlBuilder.phpE��bEO��Ȥ?vendor/codeception/codeception/src/Codeception/Step/Comment.php ��b �m��=vendor/codeception/codeception/src/Codeception/Step/Retry.phpM ��bM n����>vendor/codeception/codeception/src/Codeception/Step/Action.phpX��bX��yM�<vendor/codeception/codeception/src/Codeception/Step/Skip.php|��b|k@�=vendor/codeception/codeception/src/Codeception/Step/TryTo.php���b�Dcv��Avendor/codeception/codeception/src/Codeception/Step/Assertion.phpy��by֝�?�@vendor/codeception/codeception/src/Codeception/Step/Executor.php���b��8 O�Bvendor/codeception/codeception/src/Codeception/Step/Incomplete.php���b��}Ȯ�Evendor/codeception/codeception/src/Codeception/Step/GeneratedStep.php���b��@���Qvendor/codeception/codeception/src/Codeception/Step/Argument/PasswordArgument.php���b�ѡ�ǤPvendor/codeception/codeception/src/Codeception/Step/Argument/FormattedOutput.php���b�5lf�Avendor/codeception/codeception/src/Codeception/Step/Condition.phpy��by��k�Lvendor/codeception/codeception/src/Codeception/Step/ConditionalAssertion.php���b�~���<vendor/codeception/codeception/src/Codeception/Step/Meta.php��bP��f�>vendor/codeception/codeception/src/Codeception/Application.php��bƈdL�@vendor/codeception/codeception/src/Codeception/TestInterface.php���b�����8vendor/codeception/codeception/src/Codeception/Suite.php���b�-�p�Kvendor/codeception/codeception/src/Codeception/Coverage/SuiteSubscriber.php ��b l҃��Bvendor/codeception/codeception/src/Codeception/Coverage/Filter.php��bͱl��Rvendor/codeception/codeception/src/Codeception/Coverage/PhpCodeCoverageFactory.php���b�U�C_�Svendor/codeception/codeception/src/Codeception/Coverage/Subscriber/RemoteServer.php� ��b� �y���Lvendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Local.phpr��br�e��Nvendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Printer.php5��b5�o^!�Rvendor/codeception/codeception/src/Codeception/Coverage/Subscriber/LocalServer.php&��b&5�8��;vendor/codeception/codeception/src/Codeception/Scenario.php#��b#�?��<vendor/codeception/codeception/src/Codeception/Extension.php� ��b� .���Gvendor/codeception/codeception/src/Codeception/Command/GenerateCept.php���b��zfɤBvendor/codeception/codeception/src/Codeception/Command/Console.php���b���p�Mvendor/codeception/codeception/src/Codeception/Command/GeneratePageObject.php���b���.��Jvendor/codeception/codeception/src/Codeception/Command/GherkinSnippets.php� ��b� �T֤@vendor/codeception/codeception/src/Codeception/Command/Clean.php0��b0��)ŤDvendor/codeception/codeception/src/Codeception/Command/Bootstrap.php���b��A��Avendor/codeception/codeception/src/Codeception/Command/DryRun.php�!��b�!�a=�Mvendor/codeception/codeception/src/Codeception/Command/CompletionFallback.php���b�� �S�>vendor/codeception/codeception/src/Codeception/Command/Run.php�s��b�s�$�Hvendor/codeception/codeception/src/Codeception/Command/Shared/Config.php���b�hBDT�Lvendor/codeception/codeception/src/Codeception/Command/Shared/FileSystem.php���b��Ȁ��Gvendor/codeception/codeception/src/Codeception/Command/Shared/Style.php���b�eAO�Mvendor/codeception/codeception/src/Codeception/Command/GenerateStepObject.php= ��b= �[��Evendor/codeception/codeception/src/Codeception/Command/Completion.php� ��b� &�\��Gvendor/codeception/codeception/src/Codeception/Command/GherkinSteps.phpZ��bZ����Gvendor/codeception/codeception/src/Codeception/Command/GenerateCest.php���b��0f�Kvendor/codeception/codeception/src/Codeception/Command/GenerateSnapshot.php���b������Ivendor/codeception/codeception/src/Codeception/Command/GenerateHelper.php���b���X��Jvendor/codeception/codeception/src/Codeception/Command/GenerateFeature.php���b��б��@vendor/codeception/codeception/src/Codeception/Command/Build.php� ��b� ��ΤGvendor/codeception/codeception/src/Codeception/Command/GenerateTest.php���b���e��Nvendor/codeception/codeception/src/Codeception/Command/GenerateEnvironment.php=��b=��U�Ivendor/codeception/codeception/src/Codeception/Command/ConfigValidate.php���b��R��Evendor/codeception/codeception/src/Codeception/Command/SelfUpdate.php& ��b& w�{��Hvendor/codeception/codeception/src/Codeception/Command/GenerateGroup.php���b��4gE�?vendor/codeception/codeception/src/Codeception/Command/Init.php���b�g~ �Lvendor/codeception/codeception/src/Codeception/Command/GenerateScenarios.php��b�4r��Hvendor/codeception/codeception/src/Codeception/Command/GenerateSuite.php���b�&x��9vendor/codeception/codeception/src/Codeception/Module.phpg"��bg"��vϤ7vendor/codeception/codeception/src/Codeception/Step.phpP*��bP*qf�Τ?vendor/codeception/codeception/src/Codeception/SuiteManager.php���b�S;w"�Fvendor/codeception/codeception/src/Codeception/Template/Acceptance.phpW��bW+\�ԤEvendor/codeception/codeception/src/Codeception/Template/Bootstrap.phpC��bC�լ��Dvendor/codeception/codeception/src/Codeception/Template/Upgrade4.phpI ��bI �p��?vendor/codeception/codeception/src/Codeception/Template/Api.php� ��b� �]82�@vendor/codeception/codeception/src/Codeception/Template/Unit.php? ��b? �C�;vendor/codeception/codeception/src/Codeception/Snapshot.phpM��bMT�~��Rvendor/codeception/codeception/src/Codeception/Exception/ModuleConfigException.php���b�4����Kvendor/codeception/codeception/src/Codeception/Exception/ParseException.phpT��bTg��¤Lvendor/codeception/codeception/src/Codeception/Exception/ElementNotFound.php���b��g&ݤLvendor/codeception/codeception/src/Codeception/Exception/RemoteException.php���b�I~��Qvendor/codeception/codeception/src/Codeception/Exception/TestRuntimeException.phpa��ba�ݜ��Lvendor/codeception/codeception/src/Codeception/Exception/ContentNotFound.phpr��br�.秤Svendor/codeception/codeception/src/Codeception/Exception/ConfigurationException.php\��b\�L���Ovendor/codeception/codeception/src/Codeception/Exception/TestParseException.php���b�Sű�Svendor/codeception/codeception/src/Codeception/Exception/ModuleRequireException.php���b���1��Lvendor/codeception/codeception/src/Codeception/Exception/ModuleException.php���b�J����Vvendor/codeception/codeception/src/Codeception/Exception/MalformedLocatorException.php��b-m��Wvendor/codeception/codeception/src/Codeception/Exception/ConditionalAssertionFailed.php}��b}��v�Tvendor/codeception/codeception/src/Codeception/Exception/ModuleConflictException.php��b�7���Ovendor/codeception/codeception/src/Codeception/Exception/ExtensionException.php���b��\+V�Ovendor/codeception/codeception/src/Codeception/Exception/InjectionException.phpX��bXB�h�?vendor/codeception/codeception/src/Codeception/InitTemplate.php�(��b�(�V$a�Ivendor/codeception/codeception/src/Codeception/Subscriber/AutoRebuild.php���b��z�c�Mvendor/codeception/codeception/src/Codeception/Subscriber/ExtensionLoader.php���b�P��Evendor/codeception/codeception/src/Codeception/Subscriber/Console.phpfW��bfW�P�¤Jvendor/codeception/codeception/src/Codeception/Subscriber/Dependencies.php���b��uL&�Ivendor/codeception/codeception/src/Codeception/Subscriber/PrepareTest.phpM��bM�xrc�Gvendor/codeception/codeception/src/Codeception/Subscriber/Bootstrap.php���b��Z�Qvendor/codeception/codeception/src/Codeception/Subscriber/Shared/StaticEvents.php���b���S�Mvendor/codeception/codeception/src/Codeception/Subscriber/BeforeAfterTest.php ��b ��b��Jvendor/codeception/codeception/src/Codeception/Subscriber/ErrorHandler.php��b�R���Qvendor/codeception/codeception/src/Codeception/Subscriber/GracefulTermination.php��b�2V�Dvendor/codeception/codeception/src/Codeception/Subscriber/Module.phpl ��bl �p��Fvendor/codeception/codeception/src/Codeception/Subscriber/FailFast.php���b���t8�Ivendor/codeception/codeception/src/Codeception/CustomCommandInterface.php���b�gvendor/codeception/module-soap/src/Codeception/Module/SOAP.php�8��b�86�o�Hvendor/codeception/lib-innerbrowser/src/Codeception/Lib/InnerBrowser.php���b���H�Evendor/codeception/lib-innerbrowser/src/Codeception/Lib/Framework.php���b��F�Evendor/codeception/lib-innerbrowser/src/Codeception/Util/HttpCode.php���b��nr)�Vvendor/codeception/lib-innerbrowser/src/Codeception/Exception/ExternalUrlException.php[��b[�@�Ť6vendor/phpdocumentor/reflection-docblock/src/Utils.phpO��bOsx@��9vendor/phpdocumentor/reflection-docblock/src/DocBlock.php ��b ��(ƤIvendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php`��b`WAWˤDvendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php� ��b� ����Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php���b�)I�Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php� ��b� v\1 �Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php� ��b� -���Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php]��b]-��O�Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php���b���,�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php���b��c&�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php,��b,���F�Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.phpg ��bg V�ߤDvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php��b�j=�Hvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php���b��DU�Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.phpW��bWm���Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.phpZ ��bZ .��l�Jvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.phpX��bXН4��Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.phph ��bh ��D+�Svendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php���b��|�~�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.phpc��bc>�U�Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.phpt ��bt �{i��Bvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php���b�(�@v�Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php���b�����Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.phpW��bW렴X�Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php-��b-�G�R�Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php���b�͒��Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php6��b6 }�äGvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php] ��b] ]6��Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php2��b2��I>�Nvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.phpm��bm���Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php\��b\k1�b�Rvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php���b�6�t��Kvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.phpf ��bf �Q���Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php� ��b� �Am�Wvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.phpq��bq��?9�]vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.phpr��br�e�-�=vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php\��b\���Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpa��ba�#��Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php ��b ~��Hvendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php���b�*�Ǹ�@vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php���b���$�5vendor/phpdocumentor/type-resolver/src/PseudoType.php���b�۔�Ӥ7vendor/phpdocumentor/type-resolver/src/TypeResolver.php�6��b�6k>���8vendor/phpdocumentor/type-resolver/src/FqsenResolver.php���b������Fvendor/phpdocumentor/type-resolver/src/PseudoTypes/NegativeInteger.php���b�K}�?�Hvendor/phpdocumentor/type-resolver/src/PseudoTypes/HtmlEscapedString.php���b��c��Evendor/phpdocumentor/type-resolver/src/PseudoTypes/CallableString.php���b���#��Fvendor/phpdocumentor/type-resolver/src/PseudoTypes/LowercaseString.php���b��[��<vendor/phpdocumentor/type-resolver/src/PseudoTypes/List_.php���b�T �P�Cvendor/phpdocumentor/type-resolver/src/PseudoTypes/IntegerRange.php���b�����Nvendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyLowercaseString.php���b��t<�Evendor/phpdocumentor/type-resolver/src/PseudoTypes/NonEmptyString.php���b�d�0;�Dvendor/phpdocumentor/type-resolver/src/PseudoTypes/NumericString.php���b��<��?vendor/phpdocumentor/type-resolver/src/PseudoTypes/Numeric_.php���b���>��Fvendor/phpdocumentor/type-resolver/src/PseudoTypes/PositiveInteger.php���b���M8�Dvendor/phpdocumentor/type-resolver/src/PseudoTypes/LiteralString.php���b�EɃ��=vendor/phpdocumentor/type-resolver/src/PseudoTypes/False_.php��b̅�ˤBvendor/phpdocumentor/type-resolver/src/PseudoTypes/TraitString.php���b�5c$��<vendor/phpdocumentor/type-resolver/src/PseudoTypes/True_.php��b�4 �/vendor/phpdocumentor/type-resolver/src/Type.php���b�QQ���:vendor/phpdocumentor/type-resolver/src/Types/Callable_.php���b�Nsɤ@vendor/phpdocumentor/type-resolver/src/Types/InterfaceString.php��bN����6vendor/phpdocumentor/type-resolver/src/Types/Self_.php���b�d�#�;vendor/phpdocumentor/type-resolver/src/Types/Expression.php���b�9��7vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php���b��ٶ��:vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php��b�����=vendor/phpdocumentor/type-resolver/src/Types/Intersection.php ��b ��C��9vendor/phpdocumentor/type-resolver/src/Types/Nullable.php���b�4�Jo�6vendor/phpdocumentor/type-resolver/src/Types/Null_.php���b��yTZ�7vendor/phpdocumentor/type-resolver/src/Types/Float_.php���b��}s��<vendor/phpdocumentor/type-resolver/src/Types/ClassString.php���b�E�p�?vendor/phpdocumentor/type-resolver/src/Types/AggregatedType.php���b�9��B�6vendor/phpdocumentor/type-resolver/src/Types/Void_.php���b����\�:vendor/phpdocumentor/type-resolver/src/Types/Resource_.php���b�v�!��8vendor/phpdocumentor/type-resolver/src/Types/Boolean.php���b��L���?vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php��bl $�5vendor/phpdocumentor/type-resolver/src/Types/This.php���b��r �8vendor/phpdocumentor/type-resolver/src/Types/Context.phpV��bVAݭ�7vendor/phpdocumentor/type-resolver/src/Types/Array_.php���b�!S�8vendor/phpdocumentor/type-resolver/src/Types/Parent_.php���b�cMxR�9vendor/phpdocumentor/type-resolver/src/Types/ArrayKey.php���b�j([�=vendor/phpdocumentor/type-resolver/src/Types/AbstractList.php���b�ꭄ�8vendor/phpdocumentor/type-resolver/src/Types/String_.php���b��-�Y�8vendor/phpdocumentor/type-resolver/src/Types/Static_.php���b������8vendor/phpdocumentor/type-resolver/src/Types/Integer.php���b��A��8vendor/phpdocumentor/type-resolver/src/Types/Object_.php*��b*hi�4�9vendor/phpdocumentor/type-resolver/src/Types/Compound.php��bj5�0�;vendor/phpdocumentor/type-resolver/src/Types/Collection.php���b�1���7vendor/phpdocumentor/type-resolver/src/Types/Scalar.php���b�t����7vendor/phpdocumentor/type-resolver/src/Types/Never_.php���b�=���4vendor/phpdocumentor/reflection-common/src/Fqsen.php���b����v�7vendor/phpdocumentor/reflection-common/src/Location.php���b�e�V��=vendor/phpdocumentor/reflection-common/src/ProjectFactory.php���b�޳� �6vendor/phpdocumentor/reflection-common/src/Element.php���b��9J�3vendor/phpdocumentor/reflection-common/src/File.php���b� �)�6vendor/phpdocumentor/reflection-common/src/Project.php���b�ޗ�:vendor/padraic/humbug_get_contents/src/FileGetContents.php��bvl�j�4vendor/padraic/humbug_get_contents/src/functions.php���b�K�g@�3vendor/padraic/humbug_get_contents/src/function.php���b����n�<vendor/behat/gherkin/src/Behat/Gherkin/Cache/MemoryCache.php.��b.&�cm�?vendor/behat/gherkin/src/Behat/Gherkin/Cache/CacheInterface.php ��b E���:vendor/behat/gherkin/src/Behat/Gherkin/Cache/FileCache.phpG��bG��¤Avendor/behat/gherkin/src/Behat/Gherkin/Loader/LoaderInterface.php���b�EZb�@vendor/behat/gherkin/src/Behat/Gherkin/Loader/YamlFileLoader.php���b��S o�=vendor/behat/gherkin/src/Behat/Gherkin/Loader/ArrayLoader.php]��b]�i�ȤCvendor/behat/gherkin/src/Behat/Gherkin/Loader/GherkinFileLoader.php���b����ۤDvendor/behat/gherkin/src/Behat/Gherkin/Loader/AbstractFileLoader.php��bR�Ivendor/behat/gherkin/src/Behat/Gherkin/Loader/CucumberNDJsonAstLoader.phpW��bW�����Evendor/behat/gherkin/src/Behat/Gherkin/Loader/FileLoaderInterface.php���b��=��Avendor/behat/gherkin/src/Behat/Gherkin/Loader/DirectoryLoader.php���b�ܯ�G�@vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleTableNode.php���b��3�8vendor/behat/gherkin/src/Behat/Gherkin/Node/StepNode.php���b�I�i�Avendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioInterface.php���b�A�9p�;vendor/behat/gherkin/src/Behat/Gherkin/Node/FeatureNode.php���b�Ἰu�<vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioNode.php���b�� B�<vendor/behat/gherkin/src/Behat/Gherkin/Node/PyStringNode.php?��b?��O�Fvendor/behat/gherkin/src/Behat/Gherkin/Node/StepContainerInterface.php���b��' ~�=vendor/behat/gherkin/src/Behat/Gherkin/Node/NodeInterface.php���b�q�!z�>vendor/behat/gherkin/src/Behat/Gherkin/Node/BackgroundNode.php���b����1�Cvendor/behat/gherkin/src/Behat/Gherkin/Node/TaggedNodeInterface.php���b�%�,�Avendor/behat/gherkin/src/Behat/Gherkin/Node/ArgumentInterface.phpi��bi���\�Evendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioLikeInterface.php���b���x�Dvendor/behat/gherkin/src/Behat/Gherkin/Node/KeywordNodeInterface.php���b��n�9vendor/behat/gherkin/src/Behat/Gherkin/Node/TableNode.php9��b9�$A�;vendor/behat/gherkin/src/Behat/Gherkin/Node/OutlineNode.phpe��be��?��;vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleNode.php ��b c�|��2vendor/behat/gherkin/src/Behat/Gherkin/Gherkin.php ��b �yB�<vendor/behat/gherkin/src/Behat/Gherkin/Filter/RoleFilter.phpY��bY:��{�Avendor/behat/gherkin/src/Behat/Gherkin/Filter/NarrativeFilter.php���b�����>vendor/behat/gherkin/src/Behat/Gherkin/Filter/SimpleFilter.php���b��<���Avendor/behat/gherkin/src/Behat/Gherkin/Filter/LineRangeFilter.php9 ��b9 ���ä;vendor/behat/gherkin/src/Behat/Gherkin/Filter/TagFilter.php� ��b� (��<vendor/behat/gherkin/src/Behat/Gherkin/Filter/NameFilter.php���b�S-���Hvendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilterInterface.php$��b$Q�=Ϥ=vendor/behat/gherkin/src/Behat/Gherkin/Filter/PathsFilter.php���b�{�6ݤ<vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineFilter.php���b����s�Hvendor/behat/gherkin/src/Behat/Gherkin/Filter/FeatureFilterInterface.php���b������Avendor/behat/gherkin/src/Behat/Gherkin/Filter/FilterInterface.php���b��s�H�?vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilter.phpe��be� ���Bvendor/behat/gherkin/src/Behat/Gherkin/Exception/NodeException.php���b�t����>vendor/behat/gherkin/src/Behat/Gherkin/Exception/Exception.phpK��bK���Dvendor/behat/gherkin/src/Behat/Gherkin/Exception/ParserException.php���b��d�K�Cvendor/behat/gherkin/src/Behat/Gherkin/Exception/LexerException.php���b��U�Cvendor/behat/gherkin/src/Behat/Gherkin/Exception/CacheException.php���b�J񼩤0vendor/behat/gherkin/src/Behat/Gherkin/Lexer.php�%��b�%�� �1vendor/behat/gherkin/src/Behat/Gherkin/Parser.php�3��b�3�$�Dvendor/behat/gherkin/src/Behat/Gherkin/Keywords/CucumberKeywords.php���b������Avendor/behat/gherkin/src/Behat/Gherkin/Keywords/ArrayKeywords.php���b�e���Gvendor/behat/gherkin/src/Behat/Gherkin/Keywords/CachedArrayKeywords.php���b��K�P�Evendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsInterface.phpZ��bZ[��D�Bvendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsDumper.php���b�r-�M� vendor/behat/gherkin/libpath.php��btrnǤvendor/behat/gherkin/i18n.php����b����9/�*vendor/sebastian/exporter/src/Exporter.phpZ��bZm6���:vendor/sebastian/global-state/src/exceptions/Exception.phpi��bi�7�X�Avendor/sebastian/global-state/src/exceptions/RuntimeException.php���b�XB�/vendor/sebastian/global-state/src/Blacklist.php ��b 㳆�2vendor/sebastian/global-state/src/CodeExporter.php��b;�"ɤ.vendor/sebastian/global-state/src/Restorer.phpn ��bn ��t.�.vendor/sebastian/global-state/src/Snapshot.phpX��bXs��פ&vendor/sebastian/type/src/VoidType.phpB��bBuTM*�&vendor/sebastian/type/src/NullType.phpB��bBl�.��*vendor/sebastian/type/src/CallableType.phpM ��bM s Z��/vendor/sebastian/type/src/GenericObjectType.phpS��bS`�@��*vendor/sebastian/type/src/IterableType.php���b�'q�Ӥ1vendor/sebastian/type/src/exception/Exception.phpb��bb+���8vendor/sebastian/type/src/exception/RuntimeException.php���b�h���)vendor/sebastian/type/src/UnknownType.php,��b,�`��"vendor/sebastian/type/src/Type.phpB��bB���5�(vendor/sebastian/type/src/SimpleType.php��b��0�&vendor/sebastian/type/src/TypeName.php���b�ߩ k�(vendor/sebastian/type/src/ObjectType.php���b�R�X�:vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php%��b%cM�?vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php���b��2S�=vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php���b�\�j��Cvendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.phpR��bR*��?vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php6��b6�u�@�$vendor/sebastian/diff/src/Differ.php���b����I�Ovendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php3��b3�Eɑ�#vendor/sebastian/diff/src/Chunk.php���b���li�Mvendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php��b����@vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php���b�� )�"vendor/sebastian/diff/src/Line.php���b�F�.G�"vendor/sebastian/diff/src/Diff.php.��b.�|�1vendor/sebastian/diff/src/Exception/Exception.phpc��bc�(ip�>vendor/sebastian/diff/src/Exception/ConfigurationException.php���b����F�@vendor/sebastian/diff/src/Exception/InvalidArgumentException.php���b�b���$vendor/sebastian/diff/src/Parser.php&��b&^oA�(vendor/sebastian/version/src/Version.phpu��bu�*>�4vendor/sebastian/recursion-context/src/Exception.phpX��bXN��3�2vendor/sebastian/recursion-context/src/Context.phpR��bR^k�_�Cvendor/sebastian/recursion-context/src/InvalidArgumentException.php���b�>_�F�?vendor/sebastian/resource-operations/src/ResourceOperations.php����b����}��7vendor/sebastian/resource-operations/build/generate.php���b�d���3vendor/sebastian/object-reflector/src/Exception.phpo��bo����9vendor/sebastian/object-reflector/src/ObjectReflector.phpC��bC���"�Bvendor/sebastian/object-reflector/src/InvalidArgumentException.php���b�]ˤ4vendor/sebastian/object-enumerator/src/Exception.phpV��bVc�5vendor/sebastian/object-enumerator/src/Enumerator.phpi��bi�禷�Cvendor/sebastian/object-enumerator/src/InvalidArgumentException.php���b������,vendor/sebastian/environment/src/Console.php� ��b� >0�Y�,vendor/sebastian/environment/src/Runtime.phpZ ��bZ ��q�4vendor/sebastian/environment/src/OperatingSystem.php���b���-�+vendor/sebastian/comparator/src/Factory.php���b��w�3vendor/sebastian/comparator/src/ArrayComparator.phpi��bir��.vendor/sebastian/comparator/src/Comparator.php��bRl���6vendor/sebastian/comparator/src/DateTimeComparator.php��b� ��5vendor/sebastian/comparator/src/NumericComparator.phpW��bW�ߢ�2vendor/sebastian/comparator/src/TypeComparator.php��b:v�G�5vendor/sebastian/comparator/src/ComparisonFailure.php��b�S �8vendor/sebastian/comparator/src/MockObjectComparator.phpE��bE`rќ�4vendor/sebastian/comparator/src/ScalarComparator.php���b�!�<�5vendor/sebastian/comparator/src/DOMNodeComparator.php��b B�9�7vendor/sebastian/comparator/src/ExceptionComparator.php���b���0ڤ4vendor/sebastian/comparator/src/DoubleComparator.php)��b)���4vendor/sebastian/comparator/src/ObjectComparator.phpl��bl �tS�>vendor/sebastian/comparator/src/SplObjectStorageComparator.phpm��bm5����6vendor/sebastian/comparator/src/ResourceComparator.php���b�T2���8vendor/sebastian/code-unit-reverse-lookup/src/Wizard.phpZ��bZ����Gvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exchange/AMQPExchangeType.php���b��o�5vendor/php-amqplib/php-amqplib/PhpAmqpLib/Package.phpg��bg �Kv�Dvendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/SocketConstants.php���b�VĄߤ?vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/BigInteger.php\��b\�����?vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/MiscHelper.php4��b4��y�@vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/DebugHelper.php3��b3r��¤Jvendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap080.php� ��b� P6ФEvendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait091.php���b�U�G�Evendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait080.php ��b ��lw�Ivendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol080.php�M��b�M�=�^�Jvendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap091.php���b�v�qF�Ivendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol091.php�5��b�5�]?=�;vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Assert.php7��b7�q�Avendor/php-amqplib/php-amqplib/PhpAmqpLib/Message/AMQPMessage.php���b����&�Kvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AbstractConnection.php�G��b�G%ƌ&�Mvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPStreamConnection.php���b�9��~�`vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/Heartbeat/AbstractSignalHeartbeatSender.phpS��bS�f� �Uvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/Heartbeat/SIGHeartbeatSender.php���b��du��Wvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/Heartbeat/PCNTLHeartbeatSender.php���b��7��Mvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSocketConnection.php���b���aԤNvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazySSLConnection.php[��b[����Qvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazySocketConnection.phpd��bd�QpŤNvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPConnectionFactory.php ��b ��ȤJvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSSLConnection.php��bD]�Kvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazyConnection.php[��b[�Y�e�Mvendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPConnectionConfig.phpP��bP����Avendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AbstractClient.phpi��bi�s�<vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPArray.phpl��bl��Uۤ?vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants080.phpq ��bq ��ԛ�=vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPWriter.php���b��ŤIvendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPAbstractCollection.phpV!��bV!��f��<vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPTable.php���b�J�R�>vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPDecimal.phpq��bq��~Z�<vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants.php���b�L��y�?vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants091.php� ��b� �F=¤@vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/AbstractIO.php� ��b� ˜�~�>vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/SocketIO.php���b�IX�Q�>vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/StreamIO.php���b��G��=vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPReader.phpl!��bl!��)/�Tvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPHeartbeatMissedException.phpu��bu渖�Uvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionClosedException.phpp��bp�h �Svendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNotImplementedException.phpk��bkd4а�Kvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNoDataException.phpf��bf? ,ͤUvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPEmptyDeliveryTagException.phpm��bm֔��Vvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionBlockedException.php=��b=��_{�Gvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOException.phpw��bw�f5�Mvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPDataReadException.phpe��bew068�Rvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelClosedException.phpj��bj읦�Tvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolChannelException.phpm��bm�N�!�Pvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPBasicCancelException.php_��b_<���Qvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidFrameException.phpi��biM��Kvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOWaitException.phpc��bc��M��Jvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPLogicException.php���b���J_�Pvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfBoundsException.php���b�����Ovendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfRangeException.php���b�|#�ǤKvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPSocketException.phpc��bc�b�ҤTvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidArgumentException.php���b��,DΤNvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPExceptionInterface.phpM��bM�"WܤLvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPTimeoutException.php��b��Mf�Mvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolException.php ��b q����Lvendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPRuntimeException.php���b����A�Evendor/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AbstractChannel.php�$��b�$R�^��Avendor/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AMQPChannel.php�Z��b�Zm!� 22)); if (isset($query)) { $path.= '?' . $query; } elseif (preg_match('/(\?|\?#)$/', $orig)) { $path.= '?'; } if (isset($fragment)) { $path.= '#' . $fragment; } elseif ($orig[strlen($orig) - 1] == '#') { $path.= '#'; } if (!isset($host)) { return false; } if (isset($this->context)) { $context = stream_context_get_params($this->context); if (isset($context['notification'])) { $this->notification = $context['notification']; } } if ($host[0] == '$') { $host = substr($host, 1); global ${$host}; if (($$host instanceof SFTP) === false) { return false; } $this->sftp = $$host; } else { if (isset($this->context)) { $context = stream_context_get_options($this->context); } if (isset($context[$scheme]['session'])) { $sftp = $context[$scheme]['session']; } if (isset($context[$scheme]['sftp'])) { $sftp = $context[$scheme]['sftp']; } if (isset($sftp) && $sftp instanceof SFTP) { $this->sftp = $sftp; return $path; } if (isset($context[$scheme]['username'])) { $user = $context[$scheme]['username']; } if (isset($context[$scheme]['password'])) { $pass = $context[$scheme]['password']; } if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof RSA) { $pass = $context[$scheme]['privkey']; } if (!isset($user) || !isset($pass)) { return false; } if (isset(self::$instances[$host][$port][$user][(string) $pass])) { $this->sftp = self::$instances[$host][$port][$user][(string) $pass]; } else { $this->sftp = new SFTP($host, $port); $this->sftp->disableStatCache(); if (isset($this->notification) && is_callable($this->notification)) { call_user_func($this->notification, STREAM_NOTIFY_CONNECT, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0); call_user_func($this->notification, STREAM_NOTIFY_AUTH_REQUIRED, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0); if (!$this->sftp->login($user, $pass)) { call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_ERR, 'Login Failure', NET_SSH2_MSG_USERAUTH_FAILURE, 0, 0); return false; } call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_INFO, 'Login Success', NET_SSH2_MSG_USERAUTH_SUCCESS, 0, 0); } else { if (!$this->sftp->login($user, $pass)) { return false; } } self::$instances[$host][$port][$user][(string) $pass] = $this->sftp; } } return $path; } function _stream_open($path, $mode, $options, &$opened_path) { $path = $this->_parse_path($path); if ($path === false) { return false; } $this->path = $path; $this->size = $this->sftp->size($path); $this->mode = preg_replace('#[bt]$#', '', $mode); $this->eof = false; if ($this->size === false) { if ($this->mode[0] == 'r') { return false; } else { $this->sftp->touch($path); $this->size = 0; } } else { switch ($this->mode[0]) { case 'x': return false; case 'w': $this->sftp->truncate($path, 0); $this->size = 0; } } $this->pos = $this->mode[0] != 'a' ? 0 : $this->size; return true; } function _stream_read($count) { switch ($this->mode) { case 'w': case 'a': case 'x': case 'c': return false; } $result = $this->sftp->get($this->path, false, $this->pos, $count); if (isset($this->notification) && is_callable($this->notification)) { if ($result === false) { call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0); return 0; } call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($result), $this->size); } if (empty($result)) { $this->eof = true; return false; } $this->pos+= strlen($result); return $result; } function _stream_write($data) { switch ($this->mode) { case 'r': return false; } $result = $this->sftp->put($this->path, $data, SFTP::SOURCE_STRING, $this->pos); if (isset($this->notification) && is_callable($this->notification)) { if (!$result) { call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0); return 0; } call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($data), strlen($data)); } if ($result === false) { return false; } $this->pos+= strlen($data); if ($this->pos > $this->size) { $this->size = $this->pos; } $this->eof = false; return strlen($data); } function _stream_tell() { return $this->pos; } function _stream_eof() { return $this->eof; } function _stream_seek($offset, $whence) { switch ($whence) { case SEEK_SET: if ($offset < 0) { return false; } break; case SEEK_CUR: $offset+= $this->pos; break; case SEEK_END: $offset+= $this->size; } $this->pos = $offset; $this->eof = false; return true; } function _stream_metadata($path, $option, $var) { $path = $this->_parse_path($path); if ($path === false) { return false; } switch ($option) { case 1: $time = isset($var[0]) ? $var[0] : null; $atime = isset($var[1]) ? $var[1] : null; return $this->sftp->touch($path, $time, $atime); case 2: case 3: return false; case 4: return $this->sftp->chown($path, $var); case 5: return $this->sftp->chgrp($path, $var); case 6: return $this->sftp->chmod($path, $var) !== false; } } function _stream_cast($cast_as) { return $this->sftp->fsock; } function _stream_lock($operation) { return false; } function _rename($path_from, $path_to) { $path1 = parse_url($path_from); $path2 = parse_url($path_to); unset($path1['path'], $path2['path']); if ($path1 != $path2) { return false; } $path_from = $this->_parse_path($path_from); $path_to = parse_url($path_to); if ($path_from === false) { return false; } $path_to = $path_to['path']; if (!$this->sftp->rename($path_from, $path_to)) { if ($this->sftp->stat($path_to)) { return $this->sftp->delete($path_to, true) && $this->sftp->rename($path_from, $path_to); } return false; } return true; } function _dir_opendir($path, $options) { $path = $this->_parse_path($path); if ($path === false) { return false; } $this->pos = 0; $this->entries = $this->sftp->nlist($path); return $this->entries !== false; } function _dir_readdir() { if (isset($this->entries[$this->pos])) { return $this->entries[$this->pos++]; } return false; } function _dir_rewinddir() { $this->pos = 0; return true; } function _dir_closedir() { return true; } function _mkdir($path, $mode, $options) { $path = $this->_parse_path($path); if ($path === false) { return false; } return $this->sftp->mkdir($path, $mode, $options & STREAM_MKDIR_RECURSIVE); } function _rmdir($path, $options) { $path = $this->_parse_path($path); if ($path === false) { return false; } return $this->sftp->rmdir($path); } function _stream_flush() { return true; } function _stream_stat() { $results = $this->sftp->stat($this->path); if ($results === false) { return false; } return $results; } function _unlink($path) { $path = $this->_parse_path($path); if ($path === false) { return false; } return $this->sftp->delete($path, false); } function _url_stat($path, $flags) { $path = $this->_parse_path($path); if ($path === false) { return false; } $results = $flags & STREAM_URL_STAT_LINK ? $this->sftp->lstat($path) : $this->sftp->stat($path); if ($results === false) { return false; } return $results; } function _stream_truncate($new_size) { if (!$this->sftp->truncate($this->path, $new_size)) { return false; } $this->eof = false; $this->size = $new_size; return true; } function _stream_set_option($option, $arg1, $arg2) { return false; } function _stream_close() { } function __call($name, $arguments) { if (defined('NET_SFTP_STREAM_LOGGING')) { echo $name . '('; $last = count($arguments) - 1; foreach ($arguments as $i => $argument) { var_export($argument); if ($i != $last) { echo ','; } } echo ")\r\n"; } $name = '_' . $name; if (!method_exists($this, $name)) { return false; } return call_user_func_array(array($this, $name), $arguments); } } message_numbers = array( 1 => 'NET_SSH2_MSG_DISCONNECT', 2 => 'NET_SSH2_MSG_IGNORE', 3 => 'NET_SSH2_MSG_UNIMPLEMENTED', 4 => 'NET_SSH2_MSG_DEBUG', 5 => 'NET_SSH2_MSG_SERVICE_REQUEST', 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT', 20 => 'NET_SSH2_MSG_KEXINIT', 21 => 'NET_SSH2_MSG_NEWKEYS', 30 => 'NET_SSH2_MSG_KEXDH_INIT', 31 => 'NET_SSH2_MSG_KEXDH_REPLY', 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST', 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE', 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS', 53 => 'NET_SSH2_MSG_USERAUTH_BANNER', 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST', 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS', 82 => 'NET_SSH2_MSG_REQUEST_FAILURE', 90 => 'NET_SSH2_MSG_CHANNEL_OPEN', 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION', 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE', 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST', 94 => 'NET_SSH2_MSG_CHANNEL_DATA', 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA', 96 => 'NET_SSH2_MSG_CHANNEL_EOF', 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE', 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST', 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS', 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE' ); $this->disconnect_reasons = array( 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR', 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED', 4 => 'NET_SSH2_DISCONNECT_RESERVED', 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR', 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR', 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE', 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED', 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE', 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST', 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION', 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS', 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER', 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME' ); $this->channel_open_failure_reasons = array( 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED' ); $this->terminal_modes = array( 0 => 'NET_SSH2_TTY_OP_END' ); $this->channel_extended_data_type_codes = array( 1 => 'NET_SSH2_EXTENDED_DATA_STDERR' ); $this->_define_array( $this->message_numbers, $this->disconnect_reasons, $this->channel_open_failure_reasons, $this->terminal_modes, $this->channel_extended_data_type_codes, array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'), array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'), array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'), array(30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD', 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP', 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT', 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY', 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'), array(30 => 'NET_SSH2_MSG_KEX_ECDH_INIT', 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY') ); if (is_resource($host)) { $this->fsock = $host; return; } if (is_string($host)) { $this->host = $host; $this->port = $port; $this->timeout = $timeout; } } function setCryptoEngine($engine) { $this->crypto_engine = $engine; } function sendIdentificationStringFirst() { $this->send_id_string_first = true; } function sendIdentificationStringLast() { $this->send_id_string_first = false; } function sendKEXINITFirst() { $this->send_kex_first = true; } function sendKEXINITLast() { $this->send_kex_first = false; } function _connect() { if ($this->bitmap & self::MASK_CONSTRUCTOR) { return false; } $this->bitmap |= self::MASK_CONSTRUCTOR; $this->curTimeout = $this->timeout; $this->last_packet = microtime(true); if (!is_resource($this->fsock)) { $start = microtime(true); $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout); if (!$this->fsock) { $host = $this->host . ':' . $this->port; user_error(rtrim("Cannot connect to $host. Error $errno. $errstr")); return false; } $elapsed = microtime(true) - $start; if ($this->curTimeout) { $this->curTimeout-= $elapsed; if ($this->curTimeout < 0) { $this->is_timeout = true; return false; } } } $this->identifier = $this->_generate_identifier(); if ($this->send_id_string_first) { fputs($this->fsock, $this->identifier . "\r\n"); } $data = ''; while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) { $line = ''; while (true) { if ($this->curTimeout) { if ($this->curTimeout < 0) { $this->is_timeout = true; return false; } $read = array($this->fsock); $write = $except = null; $start = microtime(true); $sec = (int) floor($this->curTimeout); $usec = (int) (1000000 * ($this->curTimeout - $sec)); if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { $this->is_timeout = true; return false; } $elapsed = microtime(true) - $start; $this->curTimeout-= $elapsed; } $temp = stream_get_line($this->fsock, 255, "\n"); if (strlen($temp) == 255) { continue; } if ($temp === false) { return false; } $line.= "$temp\n"; break; } $data.= $line; } if (feof($this->fsock)) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } $extra = $matches[1]; if (defined('NET_SSH2_LOGGING')) { $this->_append_log('<-', $matches[0]); $this->_append_log('->', $this->identifier . "\r\n"); } $this->server_identifier = trim($temp, "\r\n"); if (strlen($extra)) { $this->errors[] = $data; } if (version_compare($matches[3], '1.99', '<')) { user_error("Cannot connect to SSH $matches[3] servers"); return false; } if (!$this->send_id_string_first) { fputs($this->fsock, $this->identifier . "\r\n"); } if (!$this->send_kex_first) { $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { user_error('Expected SSH_MSG_KEXINIT'); return false; } if (!$this->_key_exchange($response)) { return false; } } if ($this->send_kex_first && !$this->_key_exchange()) { return false; } $this->bitmap|= self::MASK_CONNECTED; return true; } function _generate_identifier() { $identifier = 'SSH-2.0-phpseclib_2.0'; $ext = array(); if (function_exists('sodium_crypto_box_publickey_from_secretkey')) { $ext[] = 'libsodium'; } if (extension_loaded('openssl')) { $ext[] = 'openssl'; } elseif (extension_loaded('mcrypt')) { $ext[] = 'mcrypt'; } if (extension_loaded('gmp')) { $ext[] = 'gmp'; } elseif (extension_loaded('bcmath')) { $ext[] = 'bcmath'; } if (!empty($ext)) { $identifier .= ' (' . implode(', ', $ext) . ')'; } return $identifier; } function _key_exchange($kexinit_payload_server = false) { $preferred = $this->preferred; $send_kex = true; $kex_algorithms = isset($preferred['kex']) ? $preferred['kex'] : $this->getSupportedKEXAlgorithms(); $server_host_key_algorithms = isset($preferred['hostkey']) ? $preferred['hostkey'] : $this->getSupportedHostKeyAlgorithms(); $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ? $preferred['server_to_client']['crypt'] : $this->getSupportedEncryptionAlgorithms(); $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ? $preferred['client_to_server']['crypt'] : $this->getSupportedEncryptionAlgorithms(); $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ? $preferred['server_to_client']['mac'] : $this->getSupportedMACAlgorithms(); $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ? $preferred['client_to_server']['mac'] : $this->getSupportedMACAlgorithms(); $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ? $preferred['server_to_client']['comp'] : $this->getSupportedCompressionAlgorithms(); $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ? $preferred['client_to_server']['comp'] : $this->getSupportedCompressionAlgorithms(); switch (true) { case $this->server_identifier == 'SSH-2.0-SSHD': case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK': if (!isset($preferred['server_to_client']['mac'])) { $s2c_mac_algorithms = array_values(array_diff( $s2c_mac_algorithms, array('hmac-sha1-96', 'hmac-md5-96') )); } if (!isset($preferred['client_to_server']['mac'])) { $c2s_mac_algorithms = array_values(array_diff( $c2s_mac_algorithms, array('hmac-sha1-96', 'hmac-md5-96') )); } } $str_kex_algorithms = implode(',', $kex_algorithms); $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); $encryption_algorithms_server_to_client = implode(',', $s2c_encryption_algorithms); $encryption_algorithms_client_to_server = implode(',', $c2s_encryption_algorithms); $mac_algorithms_server_to_client = implode(',', $s2c_mac_algorithms); $mac_algorithms_client_to_server = implode(',', $c2s_mac_algorithms); $compression_algorithms_server_to_client = implode(',', $s2c_compression_algorithms); $compression_algorithms_client_to_server = implode(',', $c2s_compression_algorithms); $client_cookie = Random::string(16); $kexinit_payload_client = pack( 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', NET_SSH2_MSG_KEXINIT, $client_cookie, strlen($str_kex_algorithms), $str_kex_algorithms, strlen($str_server_host_key_algorithms), $str_server_host_key_algorithms, strlen($encryption_algorithms_client_to_server), $encryption_algorithms_client_to_server, strlen($encryption_algorithms_server_to_client), $encryption_algorithms_server_to_client, strlen($mac_algorithms_client_to_server), $mac_algorithms_client_to_server, strlen($mac_algorithms_server_to_client), $mac_algorithms_server_to_client, strlen($compression_algorithms_client_to_server), $compression_algorithms_client_to_server, strlen($compression_algorithms_server_to_client), $compression_algorithms_server_to_client, 0, '', 0, '', 0, 0 ); if ($kexinit_payload_server === false) { if (!$this->_send_binary_packet($kexinit_payload_client)) { return false; } $kexinit_payload_server = $this->_get_binary_packet(); if ($kexinit_payload_server === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) { user_error('Expected SSH_MSG_KEXINIT'); return false; } $send_kex = false; } $response = $kexinit_payload_server; $this->_string_shift($response, 1); $server_cookie = $this->_string_shift($response, 16); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); if (!strlen($response)) { return false; } extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1))); $first_kex_packet_follows = $first_kex_packet_follows != 0; if ($send_kex && !$this->_send_binary_packet($kexinit_payload_client)) { return false; } $decrypt = $this->_array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client); $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt); if ($decryptKeyLength === null) { user_error('No compatible server to client encryption algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $encrypt = $this->_array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server); $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt); if ($encryptKeyLength === null) { user_error('No compatible client to server encryption algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $this->kex_algorithm = $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms); if ($kex_algorithm === false) { user_error('No compatible key exchange algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms); if ($server_host_key_algorithm === false) { user_error('No compatible server host key algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $mac_algorithm_in = $this->_array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); if ($mac_algorithm_in === false) { user_error('No compatible server to client message authentication algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $compression_map = array( 'none' => self::NET_SSH2_COMPRESSION_NONE, 'zlib' => self::NET_SSH2_COMPRESSION_ZLIB, 'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH ); $compression_algorithm_out = $this->_array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server); if ($compression_algorithm_out === false) { user_error('No compatible client to server compression algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $this->compress = $compression_map[$compression_algorithm_out]; $compression_algorithm_in = $this->_array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client); if ($compression_algorithm_in === false) { user_error('No compatible server to client compression algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $this->decompress = $compression_map[$compression_algorithm_in]; $exchange_hash_rfc4419 = ''; if ($kex_algorithm === 'curve25519-sha256@libssh.org') { $x = Random::string(32); $eBytes = sodium_crypto_box_publickey_from_secretkey($x); $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY'; $kexHash = new Hash('sha256'); } else { if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) { $dh_group_sizes_packed = pack( 'NNN', $this->kex_dh_group_size_min, $this->kex_dh_group_size_preferred, $this->kex_dh_group_size_max ); $packet = pack( 'Ca*', NET_SSH2_MSG_KEXDH_GEX_REQUEST, $dh_group_sizes_packed ); if (!$this->_send_binary_packet($packet)) { return false; } $this->_updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'); $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) { user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP'); return false; } $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP'); if (strlen($response) < 4) { return false; } extract(unpack('NprimeLength', $this->_string_shift($response, 4))); $primeBytes = $this->_string_shift($response, $primeLength); $prime = new BigInteger($primeBytes, -256); if (strlen($response) < 4) { return false; } extract(unpack('NgLength', $this->_string_shift($response, 4))); $gBytes = $this->_string_shift($response, $gLength); $g = new BigInteger($gBytes, -256); $exchange_hash_rfc4419 = pack( 'a*Na*Na*', $dh_group_sizes_packed, $primeLength, $primeBytes, $gLength, $gBytes ); $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY'; } else { switch ($kex_algorithm) { case 'diffie-hellman-group1-sha1': $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; break; case 'diffie-hellman-group14-sha1': $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'; break; } $g = new BigInteger(2); $prime = new BigInteger($prime, 16); $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT'; $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY'; } switch ($kex_algorithm) { case 'diffie-hellman-group-exchange-sha256': $kexHash = new Hash('sha256'); break; default: $kexHash = new Hash('sha1'); } $one = new BigInteger(1); $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength)); $max = $one->bitwise_leftShift(16 * $keyLength); $max = $max->subtract($one); $x = $one->random($one, $max); $e = $g->modPow($x, $prime); $eBytes = $e->toBytes(true); } $data = pack('CNa*', constant($clientKexInitMessage), strlen($eBytes), $eBytes); if (!$this->_send_binary_packet($data)) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } switch ($clientKexInitMessage) { case 'NET_SSH2_MSG_KEX_ECDH_INIT': $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT'); break; case 'NET_SSH2_MSG_KEXDH_GEX_INIT': $this->_updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT'); } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != constant($serverKexReplyMessage)) { user_error("Expected $serverKexReplyMessage"); return false; } switch ($serverKexReplyMessage) { case 'NET_SSH2_MSG_KEX_ECDH_REPLY': $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY'); break; case 'NET_SSH2_MSG_KEXDH_GEX_REPLY': $this->_updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY'); } if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $fBytes = $this->_string_shift($response, $temp['length']); if (strlen($response) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->signature = $this->_string_shift($response, $temp['length']); if (strlen($this->signature) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($this->signature, 4)); $this->signature_format = $this->_string_shift($this->signature, $temp['length']); if ($kex_algorithm === 'curve25519-sha256@libssh.org') { if (strlen($fBytes) !== 32) { user_error('Received curve25519 public key of invalid length.'); return false; } $key = new BigInteger(sodium_crypto_scalarmult($x, $fBytes), 256); if (extension_loaded('sodium') || extension_loaded('libsodium')) { sodium_memzero($x); } } else { $f = new BigInteger($fBytes, -256); $key = $f->modPow($x, $prime); } $keyBytes = $key->toBytes(true); $this->exchange_hash = pack( 'Na*Na*Na*Na*Na*a*Na*Na*Na*', strlen($this->identifier), $this->identifier, strlen($this->server_identifier), $this->server_identifier, strlen($kexinit_payload_client), $kexinit_payload_client, strlen($kexinit_payload_server), $kexinit_payload_server, strlen($this->server_public_host_key), $this->server_public_host_key, $exchange_hash_rfc4419, strlen($eBytes), $eBytes, strlen($fBytes), $fBytes, strlen($keyBytes), $keyBytes ); $this->exchange_hash = $kexHash->hash($this->exchange_hash); if ($this->session_id === false) { $this->session_id = $this->exchange_hash; } switch ($server_host_key_algorithm) { case 'ssh-dss': $expected_key_format = 'ssh-dss'; break; default: $expected_key_format = 'ssh-rsa'; } if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) { switch (true) { case $this->signature_format == $server_host_key_algorithm: case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512': case $this->signature_format != 'ssh-rsa': user_error('Server Host Key Algorithm Mismatch'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } } $packet = pack( 'C', NET_SSH2_MSG_NEWKEYS ); if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != NET_SSH2_MSG_NEWKEYS) { user_error('Expected SSH_MSG_NEWKEYS'); return false; } $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt); if ($this->encrypt) { if ($this->crypto_engine) { $this->encrypt->setPreferredEngine($this->crypto_engine); } if ($this->encrypt->block_size) { $this->encrypt_block_size = $this->encrypt->block_size; } $this->encrypt->enableContinuousBuffer(); $this->encrypt->disablePadding(); if ($this->encrypt->getBlockLength()) { $this->encrypt_block_size = $this->encrypt->getBlockLength() >> 3; } $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id); while ($this->encrypt_block_size > strlen($iv)) { $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); } $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size)); $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id); while ($encryptKeyLength > strlen($key)) { $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); $this->encrypt->name = $decrypt; } $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt); if ($this->decrypt) { if ($this->crypto_engine) { $this->decrypt->setPreferredEngine($this->crypto_engine); } if ($this->decrypt->block_size) { $this->decrypt_block_size = $this->decrypt->block_size; } $this->decrypt->enableContinuousBuffer(); $this->decrypt->disablePadding(); if ($this->decrypt->getBlockLength()) { $this->decrypt_block_size = $this->decrypt->getBlockLength() >> 3; } $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id); while ($this->decrypt_block_size > strlen($iv)) { $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv); } $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size)); $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id); while ($decryptKeyLength > strlen($key)) { $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); $this->decrypt->name = $decrypt; } if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') { $this->encrypt->encrypt(str_repeat("\0", 1536)); } if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') { $this->decrypt->decrypt(str_repeat("\0", 1536)); } $mac_algorithm_out = $this->_array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server); if ($mac_algorithm_out === false) { user_error('No compatible client to server message authentication algorithms found'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $createKeyLength = 0; switch ($mac_algorithm_out) { case 'hmac-sha2-256': $this->hmac_create = new Hash('sha256'); $createKeyLength = 32; break; case 'hmac-sha1': $this->hmac_create = new Hash('sha1'); $createKeyLength = 20; break; case 'hmac-sha1-96': $this->hmac_create = new Hash('sha1-96'); $createKeyLength = 20; break; case 'hmac-md5': $this->hmac_create = new Hash('md5'); $createKeyLength = 16; break; case 'hmac-md5-96': $this->hmac_create = new Hash('md5-96'); $createKeyLength = 16; } $this->hmac_create->name = $mac_algorithm_out; $checkKeyLength = 0; $this->hmac_size = 0; switch ($mac_algorithm_in) { case 'hmac-sha2-256': $this->hmac_check = new Hash('sha256'); $checkKeyLength = 32; $this->hmac_size = 32; break; case 'hmac-sha1': $this->hmac_check = new Hash('sha1'); $checkKeyLength = 20; $this->hmac_size = 20; break; case 'hmac-sha1-96': $this->hmac_check = new Hash('sha1-96'); $checkKeyLength = 20; $this->hmac_size = 12; break; case 'hmac-md5': $this->hmac_check = new Hash('md5'); $checkKeyLength = 16; $this->hmac_size = 16; break; case 'hmac-md5-96': $this->hmac_check = new Hash('md5-96'); $checkKeyLength = 16; $this->hmac_size = 12; } $this->hmac_check->name = $mac_algorithm_in; $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); while ($createKeyLength > strlen($key)) { $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id); while ($checkKeyLength > strlen($key)) { $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key); } $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); $this->regenerate_compression_context = $this->regenerate_decompression_context = true; return true; } function _encryption_algorithm_to_key_size($algorithm) { if ($this->bad_key_size_fix && $this->_bad_algorithm_candidate($algorithm)) { return 16; } switch ($algorithm) { case 'none': return 0; case 'aes128-cbc': case 'aes128-ctr': case 'arcfour': case 'arcfour128': case 'blowfish-cbc': case 'blowfish-ctr': case 'twofish128-cbc': case 'twofish128-ctr': return 16; case '3des-cbc': case '3des-ctr': case 'aes192-cbc': case 'aes192-ctr': case 'twofish192-cbc': case 'twofish192-ctr': return 24; case 'aes256-cbc': case 'aes256-ctr': case 'arcfour256': case 'twofish-cbc': case 'twofish256-cbc': case 'twofish256-ctr': return 32; } return null; } function _encryption_algorithm_to_crypt_instance($algorithm) { switch ($algorithm) { case '3des-cbc': return new TripleDES(); case '3des-ctr': return new TripleDES(Base::MODE_CTR); case 'aes256-cbc': case 'aes192-cbc': case 'aes128-cbc': return new Rijndael(); case 'aes256-ctr': case 'aes192-ctr': case 'aes128-ctr': return new Rijndael(Base::MODE_CTR); case 'blowfish-cbc': return new Blowfish(); case 'blowfish-ctr': return new Blowfish(Base::MODE_CTR); case 'twofish128-cbc': case 'twofish192-cbc': case 'twofish256-cbc': case 'twofish-cbc': return new Twofish(); case 'twofish128-ctr': case 'twofish192-ctr': case 'twofish256-ctr': return new Twofish(Base::MODE_CTR); case 'arcfour': case 'arcfour128': case 'arcfour256': return new RC4(); } return null; } function _bad_algorithm_candidate($algorithm) { switch ($algorithm) { case 'arcfour256': case 'aes192-ctr': case 'aes256-ctr': return true; } return false; } function login($username) { $args = func_get_args(); $this->auth[] = $args; if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) { if ($this->_login($username)) { return true; } if (count($args) == 1) { return false; } } return call_user_func_array(array(&$this, '_login'), $args); } function _login($username) { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { if (!$this->_connect()) { return false; } } $args = array_slice(func_get_args(), 1); if (empty($args)) { return $this->_login_helper($username); } while (count($args)) { if (!$this->auth_methods_to_continue || !$this->smartMFA) { $newargs = $args; $args = array(); } else { $newargs = array(); foreach ($this->auth_methods_to_continue as $method) { switch ($method) { case 'publickey': foreach ($args as $key => $arg) { if (is_object($arg)) { $newargs[] = $arg; unset($args[$key]); break; } } break; case 'keyboard-interactive': $hasArray = $hasString = false; foreach ($args as $arg) { if ($hasArray || is_array($arg)) { $hasArray = true; break; } if ($hasString || is_string($arg)) { $hasString = true; break; } } if ($hasArray && $hasString) { foreach ($args as $key => $arg) { if (is_array($arg)) { $newargs[] = $arg; break 2; } } } case 'password': foreach ($args as $key => $arg) { $newargs[] = $arg; unset($args[$key]); break; } } } } if (!count($newargs)) { return false; } foreach ($newargs as $arg) { if ($this->_login_helper($username, $arg)) { return true; } } } return false; } function _login_helper($username, $password = null) { if (!($this->bitmap & self::MASK_CONNECTED)) { return false; } if (!($this->bitmap & self::MASK_LOGIN_REQ)) { $packet = pack( 'CNa*', NET_SSH2_MSG_SERVICE_REQUEST, strlen('ssh-userauth'), 'ssh-userauth' ); if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { if ($this->retry_connect) { $this->retry_connect = false; if (!$this->_connect()) { return false; } return $this->_login_helper($username, $password); } $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { user_error('Expected SSH_MSG_SERVICE_ACCEPT'); return false; } $this->bitmap |= self::MASK_LOGIN_REQ; } if (strlen($this->last_interactive_response)) { return !is_string($password) && !is_array($password) ? false : $this->_keyboard_interactive_process($password); } if ($password instanceof RSA) { return $this->_privatekey_login($username, $password); } elseif ($password instanceof Agent) { return $this->_ssh_agent_login($username, $password); } if (is_array($password)) { if ($this->_keyboard_interactive_login($username, $password)) { $this->bitmap |= self::MASK_LOGIN; return true; } return false; } if (!isset($password)) { $packet = pack( 'CNa*Na*Na*', NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', strlen('none'), 'none' ); if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; case NET_SSH2_MSG_USERAUTH_FAILURE: extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4))); $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen)); default: return false; } } $packet = pack( 'CNa*Na*Na*CNa*', NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', strlen('password'), 'password', 0, strlen($password), $password ); if (!defined('NET_SSH2_LOGGING')) { $logged = null; } else { $logged = pack( 'CNa*Na*Na*CNa*', NET_SSH2_MSG_USERAUTH_REQUEST, strlen('username'), 'username', strlen('ssh-connection'), 'ssh-connection', strlen('password'), 'password', 0, strlen('password'), 'password' ); } if (!$this->_send_binary_packet($packet, $logged)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $this->_string_shift($response, $length); return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_FAILURE: if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $auth_methods = explode(',', $this->_string_shift($response, $length)); $this->auth_methods_to_continue = $auth_methods; if (!strlen($response)) { return false; } extract(unpack('Cpartial_success', $this->_string_shift($response, 1))); $partial_success = $partial_success != 0; if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) { if ($this->_keyboard_interactive_login($username, $password)) { $this->bitmap |= self::MASK_LOGIN; return true; } return false; } return false; case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; } return false; } function _keyboard_interactive_login($username, $password) { $packet = pack( 'CNa*Na*Na*Na*Na*', NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', strlen('keyboard-interactive'), 'keyboard-interactive', 0, '', 0, '' ); if (!$this->_send_binary_packet($packet)) { return false; } return $this->_keyboard_interactive_process($password); } function _keyboard_interactive_process() { $responses = func_get_args(); if (strlen($this->last_interactive_response)) { $response = $this->last_interactive_response; } else { $orig = $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_INFO_REQUEST: if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->_string_shift($response, $length); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->_string_shift($response, $length); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->_string_shift($response, $length); if (strlen($response) < 4) { return false; } extract(unpack('Nnum_prompts', $this->_string_shift($response, 4))); for ($i = 0; $i < count($responses); $i++) { if (is_array($responses[$i])) { foreach ($responses[$i] as $key => $value) { $this->keyboard_requests_responses[$key] = $value; } unset($responses[$i]); } } $responses = array_values($responses); if (isset($this->keyboard_requests_responses)) { for ($i = 0; $i < $num_prompts; $i++) { if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $prompt = $this->_string_shift($response, $length); foreach ($this->keyboard_requests_responses as $key => $value) { if (substr($prompt, 0, strlen($key)) == $key) { $responses[] = $value; break; } } } } if (strlen($this->last_interactive_response)) { $this->last_interactive_response = ''; } else { $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST'); } if (!count($responses) && $num_prompts) { $this->last_interactive_response = $orig; return false; } $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses)); for ($i = 0; $i < count($responses); $i++) { $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]); $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer'); } if (!$this->_send_binary_packet($packet, $logged)) { return false; } $this->_updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'); return $this->_keyboard_interactive_process(); case NET_SSH2_MSG_USERAUTH_SUCCESS: return true; case NET_SSH2_MSG_USERAUTH_FAILURE: extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4))); $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen)); return false; } return false; } function _ssh_agent_login($username, $agent) { $this->agent = $agent; $keys = $agent->requestIdentities(); foreach ($keys as $key) { if ($this->_privatekey_login($username, $key)) { return true; } } return false; } function _privatekey_login($username, $privatekey) { $publickey = $privatekey->getPublicKey(RSA::PUBLIC_FORMAT_RAW); if ($publickey === false) { return false; } $publickey = array( 'e' => $publickey['e']->toBytes(true), 'n' => $publickey['n']->toBytes(true) ); $publickey = pack( 'Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey['e']), $publickey['e'], strlen($publickey['n']), $publickey['n'] ); switch ($this->signature_format) { case 'rsa-sha2-512': $hash = 'sha512'; $signatureType = 'rsa-sha2-512'; break; case 'rsa-sha2-256': $hash = 'sha256'; $signatureType = 'rsa-sha2-256'; break; default: $hash = 'sha1'; $signatureType = 'ssh-rsa'; } $part1 = pack( 'CNa*Na*Na*', NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', strlen('publickey'), 'publickey' ); $part2 = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($publickey), $publickey); $packet = $part1 . chr(0) . $part2; if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: if (strlen($response) < 4) { return false; } extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4))); $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen)); $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE'; return false; case NET_SSH2_MSG_USERAUTH_PK_OK: $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK'); break; case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; default: user_error('Unexpected response to publickey authentication pt 1'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } $packet = $part1 . chr(1) . $part2; $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1); $privatekey->setHash($hash); $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet)); $signature = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($signature), $signature); $packet.= pack('Na*', strlen($signature), $signature); if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_binary_packet(); if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4))); $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen)); return false; case NET_SSH2_MSG_USERAUTH_SUCCESS: $this->bitmap |= self::MASK_LOGIN; return true; } user_error('Unexpected response to publickey authentication pt 2'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } function setTimeout($timeout) { $this->timeout = $this->curTimeout = $timeout; } function setKeepAlive($interval) { $this->keepAlive = $interval; } function getStdError() { return $this->stdErrorLog; } function exec($command, $callback = null) { $this->curTimeout = $this->timeout; $this->is_timeout = false; $this->stdErrorLog = ''; if (!$this->isAuthenticated()) { return false; } if ($this->in_request_pty_exec) { user_error('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); return false; } $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size; $packet_size = 0x4000; $packet = pack( 'CNa*N3', NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', self::CHANNEL_EXEC, $this->window_size_server_to_client[self::CHANNEL_EXEC], $packet_size ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN; $response = $this->_get_channel_packet(self::CHANNEL_EXEC); if ($response === false) { return false; } if ($this->request_pty === true) { $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); $packet = pack( 'CNNa*CNa*N5a*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_EXEC], strlen('pty-req'), 'pty-req', 1, strlen('vt100'), 'vt100', $this->windowColumns, $this->windowRows, 0, 0, strlen($terminal_modes), $terminal_modes ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; if (!$this->_get_channel_packet(self::CHANNEL_EXEC)) { user_error('Unable to request pseudo-terminal'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } $this->in_request_pty_exec = true; } $packet = pack( 'CNNa*CNa*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_EXEC], strlen('exec'), 'exec', 1, strlen($command), $command ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; $response = $this->_get_channel_packet(self::CHANNEL_EXEC); if ($response === false) { return false; } $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA; if ($callback === false || $this->in_request_pty_exec) { return true; } $output = ''; while (true) { $temp = $this->_get_channel_packet(self::CHANNEL_EXEC); switch (true) { case $temp === true: return is_callable($callback) ? true : $output; case $temp === false: return false; default: if (is_callable($callback)) { if (call_user_func($callback, $temp) === true) { $this->_close_channel(self::CHANNEL_EXEC); return true; } } else { $output.= $temp; } } } } function _initShell() { if ($this->in_request_pty_exec === true) { return true; } $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; $packet_size = 0x4000; $packet = pack( 'CNa*N3', NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', self::CHANNEL_SHELL, $this->window_size_server_to_client[self::CHANNEL_SHELL], $packet_size ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; $response = $this->_get_channel_packet(self::CHANNEL_SHELL); if ($response === false) { return false; } $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); $packet = pack( 'CNNa*CNa*N5a*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SHELL], strlen('pty-req'), 'pty-req', 1, strlen('vt100'), 'vt100', $this->windowColumns, $this->windowRows, 0, 0, strlen($terminal_modes), $terminal_modes ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST; if (!$this->_get_channel_packet(self::CHANNEL_SHELL)) { user_error('Unable to request pseudo-terminal'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } $packet = pack( 'CNNa*C', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SHELL], strlen('shell'), 'shell', 1 ); if (!$this->_send_binary_packet($packet)) { return false; } $response = $this->_get_channel_packet(self::CHANNEL_SHELL); if ($response === false) { return false; } $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA; $this->bitmap |= self::MASK_SHELL; return true; } function _get_interactive_channel() { switch (true) { case $this->in_subsystem: return self::CHANNEL_SUBSYSTEM; case $this->in_request_pty_exec: return self::CHANNEL_EXEC; default: return self::CHANNEL_SHELL; } } function _get_open_channel() { $channel = self::CHANNEL_EXEC; do { if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) { return $channel; } } while ($channel++ < self::CHANNEL_SUBSYSTEM); return false; } function read($expect = '', $mode = self::READ_SIMPLE) { $this->curTimeout = $this->timeout; $this->is_timeout = false; if (!$this->isAuthenticated()) { user_error('Operation disallowed prior to login()'); return false; } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { user_error('Unable to initiate an interactive shell session'); return false; } $channel = $this->_get_interactive_channel(); if ($mode == self::READ_NEXT) { return $this->_get_channel_packet($channel); } $match = $expect; while (true) { if ($mode == self::READ_REGEX) { preg_match($expect, substr($this->interactiveBuffer, -1024), $matches); $match = isset($matches[0]) ? $matches[0] : ''; } $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false; if ($pos !== false) { return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); } $response = $this->_get_channel_packet($channel); if (is_bool($response)) { $this->in_request_pty_exec = false; return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false; } $this->interactiveBuffer.= $response; } } function write($cmd) { if (!$this->isAuthenticated()) { user_error('Operation disallowed prior to login()'); return false; } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { user_error('Unable to initiate an interactive shell session'); return false; } return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd); } function startSubsystem($subsystem) { $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; $packet = pack( 'CNa*N3', NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', self::CHANNEL_SUBSYSTEM, $this->window_size, 0x4000 ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN; $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM); if ($response === false) { return false; } $packet = pack( 'CNNa*CNa*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL_SUBSYSTEM], strlen('subsystem'), 'subsystem', 1, strlen($subsystem), $subsystem ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST; $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM); if ($response === false) { return false; } $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA; $this->bitmap |= self::MASK_SHELL; $this->in_subsystem = true; return true; } function stopSubsystem() { $this->in_subsystem = false; $this->_close_channel(self::CHANNEL_SUBSYSTEM); return true; } function reset() { $this->_close_channel($this->_get_interactive_channel()); } function isTimeout() { return $this->is_timeout; } function disconnect() { $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) { fclose($this->realtime_log_file); } } function __destruct() { $this->disconnect(); } function isConnected() { return (bool) ($this->bitmap & self::MASK_CONNECTED); } function isAuthenticated() { return (bool) ($this->bitmap & self::MASK_LOGIN); } function ping() { if (!$this->isAuthenticated()) { if (!empty($this->auth)) { return $this->_reconnect(); } return false; } $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; $packet_size = 0x4000; $packet = pack( 'CNa*N3', NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', self::CHANNEL_KEEP_ALIVE, $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], $packet_size ); if (!@$this->_send_binary_packet($packet)) { return $this->_reconnect(); } $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; $response = @$this->_get_channel_packet(self::CHANNEL_KEEP_ALIVE); if ($response !== false) { $this->_close_channel(self::CHANNEL_KEEP_ALIVE); return true; } return $this->_reconnect(); } function _reconnect() { $this->_reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); $this->retry_connect = true; if (!$this->_connect()) { return false; } foreach ($this->auth as $auth) { $result = call_user_func_array(array(&$this, 'login'), $auth); } return $result; } function _reset_connection($reason) { $this->_disconnect($reason); $this->decrypt = $this->encrypt = false; $this->decrypt_block_size = $this->encrypt_block_size = 8; $this->hmac_check = $this->hmac_create = false; $this->hmac_size = false; $this->session_id = false; $this->retry_connect = true; $this->get_seq_no = $this->send_seq_no = 0; } function _get_binary_packet($skip_channel_filter = false) { if ($skip_channel_filter) { $read = array($this->fsock); $write = $except = null; if (!$this->curTimeout) { if ($this->keepAlive <= 0) { @stream_select($read, $write, $except, null); } else { if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); return $this->_get_binary_packet(true); } } } else { if ($this->curTimeout < 0) { $this->is_timeout = true; return true; } $read = array($this->fsock); $write = $except = null; $start = microtime(true); if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) { if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); $elapsed = microtime(true) - $start; $this->curTimeout-= $elapsed; return $this->_get_binary_packet(true); } $elapsed = microtime(true) - $start; $this->curTimeout-= $elapsed; } $sec = (int)floor($this->curTimeout); $usec = (int)(1000000 * ($this->curTimeout - $sec)); if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { $this->is_timeout = true; return true; } $elapsed = microtime(true) - $start; $this->curTimeout-= $elapsed; } } if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; user_error('Connection closed (by server) prematurely ' . $elapsed . 's'); return false; } $start = microtime(true); $raw = stream_get_contents($this->fsock, $this->decrypt_block_size); if (!strlen($raw)) { user_error('No data received from server'); return false; } if ($this->decrypt !== false) { $raw = $this->decrypt->decrypt($raw); } if ($raw === false) { user_error('Unable to decrypt content'); return false; } if (strlen($raw) < 5) { return false; } extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5))); $remaining_length = $packet_length + 4 - $this->decrypt_block_size; if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt->name) && !($this->bitmap & SSH2::MASK_LOGIN)) { $this->bad_key_size_fix = true; $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); return false; } user_error('Invalid size'); return false; } $buffer = ''; while ($remaining_length > 0) { $temp = stream_get_contents($this->fsock, $remaining_length); if ($temp === false || feof($this->fsock)) { $this->bitmap = 0; user_error('Error reading from socket'); return false; } $buffer.= $temp; $remaining_length-= strlen($temp); } $stop = microtime(true); if (strlen($buffer)) { $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer; } $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1); $padding = $this->_string_shift($raw, $padding_length); if ($this->hmac_check !== false) { $hmac = stream_get_contents($this->fsock, $this->hmac_size); if ($hmac === false || strlen($hmac) != $this->hmac_size) { $this->bitmap = 0; user_error('Error reading socket'); return false; } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { user_error('Invalid HMAC'); return false; } } switch ($this->decompress) { case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: if (!$this->isAuthenticated()) { break; } case self::NET_SSH2_COMPRESSION_ZLIB: if ($this->regenerate_decompression_context) { $this->regenerate_decompression_context = false; $cmf = ord($payload[0]); $cm = $cmf & 0x0F; if ($cm != 8) { user_error("Only CM = 8 ('deflate') is supported ($cm)"); } $cinfo = ($cmf & 0xF0) >> 4; if ($cinfo > 7) { user_error("CINFO above 7 is not allowed ($cinfo)"); } $windowSize = 1 << ($cinfo + 8); $flg = ord($payload[1]); if ((($cmf << 8) | $flg) % 31) { user_error('fcheck failed'); } $fdict = boolval($flg & 0x20); $flevel = ($flg & 0xC0) >> 6; $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, array('window' => $cinfo + 8)); $payload = substr($payload, 2); } if ($this->decompress_context) { $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH); } } $this->get_seq_no++; if (defined('NET_SSH2_LOGGING')) { $current = microtime(true); $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')'; $message_number = '<- ' . $message_number . ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; $this->_append_log($message_number, $payload); $this->last_packet = $current; } return $this->_filter($payload, $skip_channel_filter); } function _filter($payload, $skip_channel_filter) { switch (ord($payload[0])) { case NET_SSH2_MSG_DISCONNECT: $this->_string_shift($payload, 1); if (strlen($payload) < 8) { return false; } extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length); $this->bitmap = 0; return false; case NET_SSH2_MSG_IGNORE: $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_DEBUG: $this->_string_shift($payload, 2); if (strlen($payload) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); $this->errors[] = 'SSH_MSG_DEBUG: ' . $this->_string_shift($payload, $length); $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_UNIMPLEMENTED: return false; case NET_SSH2_MSG_KEXINIT: if ($this->session_id !== false) { $this->send_kex_first = false; if (!$this->_key_exchange($payload)) { $this->bitmap = 0; return false; } $payload = $this->_get_binary_packet($skip_channel_filter); } } if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { $this->_string_shift($payload, 1); if (strlen($payload) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); $this->banner_message = $this->_string_shift($payload, $length); $payload = $this->_get_binary_packet(); } if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { if (is_bool($payload)) { return $payload; } switch (ord($payload[0])) { case NET_SSH2_MSG_CHANNEL_REQUEST: if (strlen($payload) == 31) { extract(unpack('cpacket_type/Nchannel/Nlength', $payload)); if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) { if (ord(substr($payload, 9 + $length))) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel])); } $payload = $this->_get_binary_packet($skip_channel_filter); } } break; case NET_SSH2_MSG_CHANNEL_DATA: case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: case NET_SSH2_MSG_CHANNEL_CLOSE: case NET_SSH2_MSG_CHANNEL_EOF: if (!$skip_channel_filter && !empty($this->server_channels)) { $this->binary_packet_buffer = $payload; $this->_get_channel_packet(true); $payload = $this->_get_binary_packet(); } break; case NET_SSH2_MSG_GLOBAL_REQUEST: if (strlen($payload) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . $this->_string_shift($payload, $length); if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) { return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_OPEN: $this->_string_shift($payload, 1); if (strlen($payload) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); $data = $this->_string_shift($payload, $length); if (strlen($payload) < 4) { return false; } extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); switch ($data) { case 'auth-agent': case 'auth-agent@openssh.com': if (isset($this->agent)) { $new_channel = self::CHANNEL_AGENT_FORWARD; if (strlen($payload) < 8) { return false; } extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4))); extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4))); $this->packet_size_client_to_server[$new_channel] = $remote_window_size; $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size; $this->window_size_client_to_server[$new_channel] = $this->window_size; $packet_size = 0x4000; $packet = pack( 'CN4', NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, $server_channel, $new_channel, $packet_size, $packet_size ); $this->server_channels[$new_channel] = $server_channel; $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION; if (!$this->_send_binary_packet($packet)) { return false; } } break; default: $packet = pack( 'CN3a*Na*', NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, '' ); if (!$this->_send_binary_packet($packet)) { return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } } $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: $this->_string_shift($payload, 1); if (strlen($payload) < 8) { return false; } extract(unpack('Nchannel', $this->_string_shift($payload, 4))); extract(unpack('Nwindow_size', $this->_string_shift($payload, 4))); $this->window_size_client_to_server[$channel]+= $window_size; $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet($skip_channel_filter); } } return $payload; } function enableQuietMode() { $this->quiet_mode = true; } function disableQuietMode() { $this->quiet_mode = false; } function isQuietModeEnabled() { return $this->quiet_mode; } function enablePTY() { $this->request_pty = true; } function disablePTY() { if ($this->in_request_pty_exec) { $this->_close_channel(self::CHANNEL_EXEC); $this->in_request_pty_exec = false; } $this->request_pty = false; } function isPTYEnabled() { return $this->request_pty; } function _get_channel_packet($client_channel, $skip_extended = false) { if (!empty($this->channel_buffers[$client_channel])) { switch ($this->channel_status[$client_channel]) { case NET_SSH2_MSG_CHANNEL_REQUEST: foreach ($this->channel_buffers[$client_channel] as $i => $packet) { switch (ord($packet[0])) { case NET_SSH2_MSG_CHANNEL_SUCCESS: case NET_SSH2_MSG_CHANNEL_FAILURE: unset($this->channel_buffers[$client_channel][$i]); return substr($packet, 1); } } break; default: return substr(array_shift($this->channel_buffers[$client_channel]), 1); } } while (true) { if ($this->binary_packet_buffer !== false) { $response = $this->binary_packet_buffer; $this->binary_packet_buffer = false; } else { $response = $this->_get_binary_packet(true); if ($response === true && $this->is_timeout) { if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { $this->_close_channel($client_channel); } return true; } if ($response === false) { $this->bitmap = 0; user_error('Connection closed by server'); return false; } } if ($client_channel == -1 && $response === true) { return true; } if (!strlen($response)) { return false; } extract(unpack('Ctype', $this->_string_shift($response, 1))); if (strlen($response) < 4) { return false; } if ($type == NET_SSH2_MSG_CHANNEL_OPEN) { extract(unpack('Nlength', $this->_string_shift($response, 4))); } else { extract(unpack('Nchannel', $this->_string_shift($response, 4))); } if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) { $this->window_size_server_to_client[$channel]-= strlen($response); if ($this->window_size_server_to_client[$channel] < 0) { $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize); if (!$this->_send_binary_packet($packet)) { return false; } $this->window_size_server_to_client[$channel]+= $this->window_resize; } switch ($type) { case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: if (strlen($response) < 8) { return false; } extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8))); $data = $this->_string_shift($response, $length); $this->stdErrorLog.= $data; if ($skip_extended || $this->quiet_mode) { continue 2; } if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) { return $data; } $this->channel_buffers[$channel][] = chr($type) . $data; continue 2; case NET_SSH2_MSG_CHANNEL_REQUEST: if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) { continue 2; } if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $value = $this->_string_shift($response, $length); switch ($value) { case 'exit-signal': $this->_string_shift($response, 1); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); $this->_string_shift($response, 1); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); if ($length) { $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); } $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; continue 3; case 'exit-status': if (strlen($response) < 5) { return false; } extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5))); $this->exit_status = $exit_status; continue 3; default: continue 3; } } switch ($this->channel_status[$channel]) { case NET_SSH2_MSG_CHANNEL_OPEN: switch ($type) { case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: if (strlen($response) < 4) { return false; } extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); $this->server_channels[$channel] = $server_channel; if (strlen($response) < 4) { return false; } extract(unpack('Nwindow_size', $this->_string_shift($response, 4))); if ($window_size < 0) { $window_size&= 0x7FFFFFFF; $window_size+= 0x80000000; } $this->window_size_client_to_server[$channel] = $window_size; if (strlen($response) < 4) { return false; } $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); $this->_on_channel_open(); return $result; case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: user_error('Unable to open channel'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); default: if ($client_channel == $channel) { user_error('Unexpected response to open request'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } return $this->_get_channel_packet($client_channel, $skip_extended); } break; case NET_SSH2_MSG_CHANNEL_REQUEST: switch ($type) { case NET_SSH2_MSG_CHANNEL_SUCCESS: return true; case NET_SSH2_MSG_CHANNEL_FAILURE: return false; case NET_SSH2_MSG_CHANNEL_DATA: if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $data = $this->_string_shift($response, $length); $this->channel_buffers[$channel][] = chr($type) . $data; return $this->_get_channel_packet($client_channel, $skip_extended); default: user_error('Unable to fulfill channel request'); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } case NET_SSH2_MSG_CHANNEL_CLOSE: return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); } } switch ($type) { case NET_SSH2_MSG_CHANNEL_DATA: if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $data = $this->_string_shift($response, $length); if ($channel == self::CHANNEL_AGENT_FORWARD) { $agent_response = $this->agent->_forward_data($data); if (!is_bool($agent_response)) { $this->_send_channel_packet($channel, $agent_response); } break; } if ($client_channel == $channel) { return $data; } $this->channel_buffers[$channel][] = chr($type) . $data; break; case NET_SSH2_MSG_CHANNEL_CLOSE: $this->curTimeout = 5; if ($this->bitmap & self::MASK_SHELL) { $this->bitmap&= ~self::MASK_SHELL; } if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); } $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; if ($client_channel == $channel) { return true; } case NET_SSH2_MSG_CHANNEL_EOF: break; default: user_error("Error reading channel data ($type)"); return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } } } function _send_binary_packet($data, $logged = null) { if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; user_error('Connection closed prematurely'); return false; } if (!isset($logged)) { $logged = $data; } switch ($this->compress) { case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH: if (!$this->isAuthenticated()) { break; } case self::NET_SSH2_COMPRESSION_ZLIB: if (!$this->regenerate_compression_context) { $header = ''; } else { $this->regenerate_compression_context = false; $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, array('window' => 15)); $header = "\x78\x9C"; } if ($this->compress_context) { $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH); } } $packet_length = strlen($data) + 9; $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; $padding_length = $packet_length - strlen($data) - 5; $padding = Random::string($padding_length); $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : ''; $this->send_seq_no++; if ($this->encrypt !== false) { $packet = $this->encrypt->encrypt($packet); } $packet.= $hmac; $start = microtime(true); $result = strlen($packet) == @fputs($this->fsock, $packet); $stop = microtime(true); if (defined('NET_SSH2_LOGGING')) { $current = microtime(true); $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')'; $message_number = '-> ' . $message_number . ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)'; $this->_append_log($message_number, $logged); $this->last_packet = $current; } return $result; } function _append_log($message_number, $message) { if (strlen($message_number) > 2) { $this->_string_shift($message); } switch (NET_SSH2_LOGGING) { case self::LOG_SIMPLE: $this->message_number_log[] = $message_number; break; case self::LOG_COMPLEX: $this->message_number_log[] = $message_number; $this->log_size+= strlen($message); $this->message_log[] = $message; while ($this->log_size > self::LOG_MAX_SIZE) { $this->log_size-= strlen(array_shift($this->message_log)); array_shift($this->message_number_log); } break; case self::LOG_REALTIME: switch (PHP_SAPI) { case 'cli': $start = $stop = "\r\n"; break; default: $start = '
';
$stop = '
'; } echo $start . $this->_format_log(array($message), array($message_number)) . $stop; @flush(); @ob_flush(); break; case self::LOG_REALTIME_FILE: if (!isset($this->realtime_log_file)) { $filename = self::LOG_REALTIME_FILENAME; $fp = fopen($filename, 'w'); $this->realtime_log_file = $fp; } if (!is_resource($this->realtime_log_file)) { break; } $entry = $this->_format_log(array($message), array($message_number)); if ($this->realtime_log_wrap) { $temp = "<<< START >>>\r\n"; $entry.= $temp; fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); } $this->realtime_log_size+= strlen($entry); if ($this->realtime_log_size > self::LOG_MAX_SIZE) { fseek($this->realtime_log_file, 0); $this->realtime_log_size = strlen($entry); $this->realtime_log_wrap = true; } fputs($this->realtime_log_file, $entry); } } function _send_channel_packet($client_channel, $data) { while (strlen($data)) { if (!$this->window_size_client_to_server[$client_channel]) { $this->bitmap^= self::MASK_WINDOW_ADJUST; $this->_get_channel_packet(-1); $this->bitmap^= self::MASK_WINDOW_ADJUST; } $max_size = min( $this->packet_size_client_to_server[$client_channel], $this->window_size_client_to_server[$client_channel] ); $temp = $this->_string_shift($data, $max_size); $packet = pack( 'CN2a*', NET_SSH2_MSG_CHANNEL_DATA, $this->server_channels[$client_channel], strlen($temp), $temp ); $this->window_size_client_to_server[$client_channel]-= strlen($temp); if (!$this->_send_binary_packet($packet)) { return false; } } return true; } function _close_channel($client_channel, $want_reply = false) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); if (!$want_reply) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); } $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; $this->curTimeout = 5; while (!is_bool($this->_get_channel_packet($client_channel))) { } if ($this->is_timeout) { $this->disconnect(); } if ($want_reply) { $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); } if ($this->bitmap & self::MASK_SHELL) { $this->bitmap&= ~self::MASK_SHELL; } } function _disconnect($reason) { if ($this->bitmap & self::MASK_CONNECTED) { $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, ''); $this->_send_binary_packet($data); } $this->bitmap = 0; if (is_resource($this->fsock) && get_resource_type($this->fsock) == 'stream') { fclose($this->fsock); } return false; } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } function _define_array() { $args = func_get_args(); foreach ($args as $arg) { foreach ($arg as $key => $value) { if (!defined($value)) { define($value, $key); } else { break 2; } } } } function getLog() { if (!defined('NET_SSH2_LOGGING')) { return false; } switch (NET_SSH2_LOGGING) { case self::LOG_SIMPLE: return $this->message_number_log; case self::LOG_COMPLEX: $log = $this->_format_log($this->message_log, $this->message_number_log); return PHP_SAPI == 'cli' ? $log : '
' . $log . '
'; default: return false; } } function _format_log($message_log, $message_number_log) { $output = ''; for ($i = 0; $i < count($message_log); $i++) { $output.= $message_number_log[$i] . "\r\n"; $current_log = $message_log[$i]; $j = 0; do { if (strlen($current_log)) { $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; } $fragment = $this->_string_shift($current_log, $this->log_short_width); $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary)); $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; $j++; } while (strlen($current_log)); $output.= "\r\n"; } return $output; } function _format_log_helper($matches) { return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); } function _on_channel_open() { if (isset($this->agent)) { $this->agent->_on_channel_open($this); } } function _array_intersect_first($array1, $array2) { foreach ($array1 as $value) { if (in_array($value, $array2)) { return $value; } } return false; } function getErrors() { return $this->errors; } function getLastError() { $count = count($this->errors); if ($count > 0) { return $this->errors[$count - 1]; } } function getServerIdentification() { $this->_connect(); return $this->server_identifier; } function getKexAlgorithms() { $this->_connect(); return $this->kex_algorithms; } function getServerHostKeyAlgorithms() { $this->_connect(); return $this->server_host_key_algorithms; } function getEncryptionAlgorithmsClient2Server() { $this->_connect(); return $this->encryption_algorithms_client_to_server; } function getEncryptionAlgorithmsServer2Client() { $this->_connect(); return $this->encryption_algorithms_server_to_client; } function getMACAlgorithmsClient2Server() { $this->_connect(); return $this->mac_algorithms_client_to_server; } function getMACAlgorithmsServer2Client() { $this->_connect(); return $this->mac_algorithms_server_to_client; } function getCompressionAlgorithmsClient2Server() { $this->_connect(); return $this->compression_algorithms_client_to_server; } function getCompressionAlgorithmsServer2Client() { $this->_connect(); return $this->compression_algorithms_server_to_client; } function getLanguagesServer2Client() { $this->_connect(); return $this->languages_server_to_client; } function getLanguagesClient2Server() { $this->_connect(); return $this->languages_client_to_server; } function getServerAlgorithms() { $this->_connect(); return array( 'kex' => $this->kex_algorithms, 'hostkey' => $this->server_host_key_algorithms, 'client_to_server' => array( 'crypt' => $this->encryption_algorithms_client_to_server, 'mac' => $this->mac_algorithms_client_to_server, 'comp' => $this->compression_algorithms_client_to_server, 'lang' => $this->languages_client_to_server ), 'server_to_client' => array( 'crypt' => $this->encryption_algorithms_server_to_client, 'mac' => $this->mac_algorithms_server_to_client, 'comp' => $this->compression_algorithms_server_to_client, 'lang' => $this->languages_server_to_client ) ); } function getSupportedKEXAlgorithms() { $kex_algorithms = array( 'curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1', ); if (!function_exists('sodium_crypto_box_publickey_from_secretkey')) { $kex_algorithms = array_diff( $kex_algorithms, array('curve25519-sha256@libssh.org') ); } return $kex_algorithms; } function getSupportedHostKeyAlgorithms() { return array( 'rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa', 'ssh-dss' ); } function getSupportedEncryptionAlgorithms() { $algos = array( 'arcfour256', 'arcfour128', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc', 'blowfish-ctr', 'blowfish-cbc', '3des-ctr', '3des-cbc', ); if ($this->crypto_engine) { $engines = array($this->crypto_engine); } else { $engines = array( Base::ENGINE_OPENSSL, Base::ENGINE_MCRYPT, Base::ENGINE_INTERNAL ); } $ciphers = array(); foreach ($engines as $engine) { foreach ($algos as $algo) { $obj = $this->_encryption_algorithm_to_crypt_instance($algo); if ($obj instanceof Rijndael) { $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo)); } switch ($algo) { case 'arcfour128': case 'arcfour256': if ($engine != Base::ENGINE_INTERNAL) { continue 2; } } if ($obj->isValidEngine($engine)) { $algos = array_diff($algos, array($algo)); $ciphers[] = $algo; } } } return $ciphers; } function getSupportedMACAlgorithms() { return array( 'hmac-sha2-256', 'hmac-sha1-96', 'hmac-sha1', 'hmac-md5-96', 'hmac-md5', ); } function getSupportedCompressionAlgorithms() { $algos = array('none'); if (function_exists('deflate_init')) { $algos[] = 'zlib@openssh.com'; $algos[] = 'zlib'; } return $algos; } function getAlgorithmsNegotiated() { $this->_connect(); $compression_map = array( self::NET_SSH2_COMPRESSION_NONE => 'none', self::NET_SSH2_COMPRESSION_ZLIB => 'zlib', self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com' ); return array( 'kex' => $this->kex_algorithm, 'hostkey' => $this->signature_format, 'client_to_server' => array( 'crypt' => $this->encrypt->name, 'mac' => $this->hmac_create->name, 'comp' => $compression_map[$this->compress], ), 'server_to_client' => array( 'crypt' => $this->decrypt->name, 'mac' => $this->hmac_check->name, 'comp' => $compression_map[$this->decompress], ) ); } function setPreferredAlgorithms($methods) { $preferred = $methods; if (isset($preferred['kex'])) { $preferred['kex'] = array_intersect( $preferred['kex'], $this->getSupportedKEXAlgorithms() ); } if (isset($preferred['hostkey'])) { $preferred['hostkey'] = array_intersect( $preferred['hostkey'], $this->getSupportedHostKeyAlgorithms() ); } $keys = array('client_to_server', 'server_to_client'); foreach ($keys as $key) { if (isset($preferred[$key])) { $a = &$preferred[$key]; if (isset($a['crypt'])) { $a['crypt'] = array_intersect( $a['crypt'], $this->getSupportedEncryptionAlgorithms() ); } if (isset($a['comp'])) { $a['comp'] = array_intersect( $a['comp'], $this->getSupportedCompressionAlgorithms() ); } if (isset($a['mac'])) { $a['mac'] = array_intersect( $a['mac'], $this->getSupportedMACAlgorithms() ); } } } $keys = array( 'kex', 'hostkey', 'client_to_server/crypt', 'client_to_server/comp', 'client_to_server/mac', 'server_to_client/crypt', 'server_to_client/comp', 'server_to_client/mac', ); foreach ($keys as $key) { $p = $preferred; $m = $methods; $subkeys = explode('/', $key); foreach ($subkeys as $subkey) { if (!isset($p[$subkey])) { continue 2; } $p = $p[$subkey]; $m = $m[$subkey]; } if (count($p) != count($m)) { $diff = array_diff($m, $p); $msg = count($diff) == 1 ? ' is not a supported algorithm' : ' are not supported algorithms'; user_error(implode(', ', $diff) . $msg); return false; } } $this->preferred = $preferred; } function getBannerMessage() { return $this->banner_message; } function getServerPublicHostKey() { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { if (!$this->_connect()) { return false; } } $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; if (strlen($server_public_host_key) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4))); $this->_string_shift($server_public_host_key, $length); if ($this->signature_validated) { return $this->bitmap ? $this->signature_format . ' ' . base64_encode($this->server_public_host_key) : false; } $this->signature_validated = true; switch ($this->signature_format) { case 'ssh-dss': $zero = new BigInteger(); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($signature, 4)); if ($temp['length'] != 40) { user_error('Invalid signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $r = new BigInteger($this->_string_shift($signature, 20), 256); $s = new BigInteger($this->_string_shift($signature, 20), 256); switch (true) { case $r->equals($zero): case $r->compare($q) >= 0: case $s->equals($zero): case $s->compare($q) >= 0: user_error('Invalid signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $w = $s->modInverse($q); $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16)); list(, $u1) = $u1->divide($q); $u2 = $w->multiply($r); list(, $u2) = $u2->divide($q); $g = $g->modPow($u1, $p); $y = $y->modPow($u2, $p); $v = $g->multiply($y); list(, $v) = $v->divide($p); list(, $v) = $v->divide($q); if (!$v->equals($r)) { user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; case 'ssh-rsa': case 'rsa-sha2-256': case 'rsa-sha2-512': if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $rawN = $this->_string_shift($server_public_host_key, $temp['length']); $n = new BigInteger($rawN, -256); $nLength = strlen(ltrim($rawN, "\0")); if (strlen($signature) < 4) { return false; } $temp = unpack('Nlength', $this->_string_shift($signature, 4)); $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256); if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { user_error('Invalid signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $s = $s->modPow($e, $n); $s = $s->toBytes(); switch ($this->signature_format) { case 'rsa-sha2-512': $hash = 'sha512'; break; case 'rsa-sha2-256': $hash = 'sha256'; break; default: $hash = 'sha1'; } $hashObj = new Hash($hash); switch ($this->signature_format) { case 'rsa-sha2-512': $h = pack('N5a*', 0x00305130, 0x0D060960, 0x86480165, 0x03040203, 0x05000440, $hashObj->hash($this->exchange_hash)); break; case 'rsa-sha2-256': $h = pack('N5a*', 0x00303130, 0x0D060960, 0x86480165, 0x03040201, 0x05000420, $hashObj->hash($this->exchange_hash)); break; default: $hash = 'sha1'; $h = pack('N4a*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, $hashObj->hash($this->exchange_hash)); } $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h; if ($s != $h) { user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; default: user_error('Unsupported signature format'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } return $this->signature_format . ' ' . base64_encode($this->server_public_host_key); } function getExitStatus() { if (is_null($this->exit_status)) { return false; } return $this->exit_status; } function getWindowColumns() { return $this->windowColumns; } function getWindowRows() { return $this->windowRows; } function setWindowColumns($value) { $this->windowColumns = $value; } function setWindowRows($value) { $this->windowRows = $value; } function setWindowSize($columns = 80, $rows = 24) { $this->windowColumns = $columns; $this->windowRows = $rows; } function _updateLogHistory($old, $new) { if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) { $this->message_number_log[count($this->message_number_log) - 1] = str_replace( $old, $new, $this->message_number_log[count($this->message_number_log) - 1] ); } } function getAuthMethodsToContinue() { return $this->auth_methods_to_continue; } function enableSmartMFA() { $this->smartMFA = true; } function disableSmartMFA() { $this->smartMFA = false; } } max_sftp_packet = 1 << 15; $this->packet_types = array( 1 => 'NET_SFTP_INIT', 2 => 'NET_SFTP_VERSION', 3 => 'NET_SFTP_OPEN', 4 => 'NET_SFTP_CLOSE', 5 => 'NET_SFTP_READ', 6 => 'NET_SFTP_WRITE', 7 => 'NET_SFTP_LSTAT', 9 => 'NET_SFTP_SETSTAT', 10 => 'NET_SFTP_FSETSTAT', 11 => 'NET_SFTP_OPENDIR', 12 => 'NET_SFTP_READDIR', 13 => 'NET_SFTP_REMOVE', 14 => 'NET_SFTP_MKDIR', 15 => 'NET_SFTP_RMDIR', 16 => 'NET_SFTP_REALPATH', 17 => 'NET_SFTP_STAT', 18 => 'NET_SFTP_RENAME', 19 => 'NET_SFTP_READLINK', 20 => 'NET_SFTP_SYMLINK', 21 => 'NET_SFTP_LINK', 101=> 'NET_SFTP_STATUS', 102=> 'NET_SFTP_HANDLE', 103=> 'NET_SFTP_DATA', 104=> 'NET_SFTP_NAME', 105=> 'NET_SFTP_ATTRS', 200=> 'NET_SFTP_EXTENDED' ); $this->status_codes = array( 0 => 'NET_SFTP_STATUS_OK', 1 => 'NET_SFTP_STATUS_EOF', 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE', 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED', 4 => 'NET_SFTP_STATUS_FAILURE', 5 => 'NET_SFTP_STATUS_BAD_MESSAGE', 6 => 'NET_SFTP_STATUS_NO_CONNECTION', 7 => 'NET_SFTP_STATUS_CONNECTION_LOST', 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED', 9 => 'NET_SFTP_STATUS_INVALID_HANDLE', 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH', 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS', 12 => 'NET_SFTP_STATUS_WRITE_PROTECT', 13 => 'NET_SFTP_STATUS_NO_MEDIA', 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM', 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED', 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL', 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT', 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY', 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY', 20 => 'NET_SFTP_STATUS_INVALID_FILENAME', 21 => 'NET_SFTP_STATUS_LINK_LOOP', 22 => 'NET_SFTP_STATUS_CANNOT_DELETE', 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER', 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY', 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT', 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED', 27 => 'NET_SFTP_STATUS_DELETE_PENDING', 28 => 'NET_SFTP_STATUS_FILE_CORRUPT', 29 => 'NET_SFTP_STATUS_OWNER_INVALID', 30 => 'NET_SFTP_STATUS_GROUP_INVALID', 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK' ); $this->attributes = array( 0x00000001 => 'NET_SFTP_ATTR_SIZE', 0x00000002 => 'NET_SFTP_ATTR_UIDGID', 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS', 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME', 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME', 0x00000040 => 'NET_SFTP_ATTR_ACL', 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES', 0x00000200 => 'NET_SFTP_ATTR_BITS', 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT', 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE', 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT', 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME', 0x00008000 => 'NET_SFTP_ATTR_CTIME', (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED' ); $this->open_flags = array( 0x00000001 => 'NET_SFTP_OPEN_READ', 0x00000002 => 'NET_SFTP_OPEN_WRITE', 0x00000004 => 'NET_SFTP_OPEN_APPEND', 0x00000008 => 'NET_SFTP_OPEN_CREATE', 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE', 0x00000020 => 'NET_SFTP_OPEN_EXCL', 0x00000040 => 'NET_SFTP_OPEN_TEXT' ); $this->open_flags5 = array( 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW', 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE', 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING', 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE', 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING', 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC', 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE', 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ', 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE', 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE', 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY', 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW', 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE', 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO', 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP', 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM', 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER', ); $this->file_types = array( 1 => 'NET_SFTP_TYPE_REGULAR', 2 => 'NET_SFTP_TYPE_DIRECTORY', 3 => 'NET_SFTP_TYPE_SYMLINK', 4 => 'NET_SFTP_TYPE_SPECIAL', 5 => 'NET_SFTP_TYPE_UNKNOWN', 6 => 'NET_SFTP_TYPE_SOCKET', 7 => 'NET_SFTP_TYPE_CHAR_DEVICE', 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE', 9 => 'NET_SFTP_TYPE_FIFO' ); $this->_define_array( $this->packet_types, $this->status_codes, $this->attributes, $this->open_flags, $this->open_flags5, $this->file_types ); if (!defined('NET_SFTP_QUEUE_SIZE')) { define('NET_SFTP_QUEUE_SIZE', 32); } if (!defined('NET_SFTP_UPLOAD_QUEUE_SIZE')) { define('NET_SFTP_UPLOAD_QUEUE_SIZE', 1024); } } function _precheck() { if (!($this->bitmap & SSH2::MASK_LOGIN)) { return false; } if ($this->pwd === false) { return $this->_init_sftp_connection(); } return true; } function _partial_init_sftp_connection() { $this->window_size_server_to_client[self::CHANNEL] = $this->window_size; $packet = pack( 'CNa*N3', NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', self::CHANNEL, $this->window_size, 0x4000 ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN; $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { return false; } elseif ($response === true && $this->isTimeout()) { return false; } $packet = pack( 'CNNa*CNa*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp' ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" . "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" . "exec sftp-server"; $packet = pack( 'CNNa*CNa*', NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[self::CHANNEL], strlen('exec'), 'exec', 1, strlen($command), $command ); if (!$this->_send_binary_packet($packet)) { return false; } $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { return false; } } elseif ($response === true && $this->isTimeout()) { return false; } $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA; if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_VERSION) { user_error('Expected SSH_FXP_VERSION'); return false; } $this->use_request_id = true; if (strlen($response) < 4) { return false; } extract(unpack('Nversion', $this->_string_shift($response, 4))); $this->defaultVersion = $version; while (!empty($response)) { if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $key = $this->_string_shift($response, $length); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $value = $this->_string_shift($response, $length); $this->extensions[$key] = $value; } $this->partial_init = true; return true; } function _init_sftp_connection() { if (!$this->partial_init && !$this->_partial_init_sftp_connection()) { return false; } $this->version = $this->defaultVersion; if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) { $versions = explode(',', $this->extensions['versions']); $supported = array(6, 5, 4); if ($this->preferredVersion) { $supported = array_diff($supported, array($this->preferredVersion)); array_unshift($supported, $this->preferredVersion); } foreach ($supported as $ver) { if (in_array($ver, $versions)) { if ($ver === $this->version) { break; } $this->version = (int) $ver; $packet = pack('Na*Na*', strlen('version-select'), 'version-select', strlen($ver), $ver); if (!$this->_send_sftp_packet(NET_SFTP_EXTENDED, $packet)) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } break; } } } if ($this->version < 2 || $this->version > 6) { return false; } $this->pwd = $this->_realpath('.'); $this->_update_stat_cache($this->pwd, array()); return true; } function disableStatCache() { $this->use_stat_cache = false; } function enableStatCache() { $this->use_stat_cache = true; } function clearStatCache() { $this->stat_cache = array(); } function enablePathCanonicalization() { $this->canonicalize_paths = true; } function disablePathCanonicalization() { $this->canonicalize_paths = false; } function enableArbitraryLengthPackets() { $this->allow_arbitrary_length_packets = true; } function disableArbitraryLengthPackets() { $this->allow_arbitrary_length_packets = false; } function pwd() { if (!$this->_precheck()) { return false; } return $this->pwd; } function _logError($response, $status = -1) { if ($status == -1) { if (strlen($response) < 4) { return; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); } $error = $this->status_codes[$status]; if ($this->version > 2 || strlen($response) < 4) { extract(unpack('Nlength', $this->_string_shift($response, 4))); $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length); } else { $this->sftp_errors[] = $error; } } function realpath($path) { if (!$this->_precheck()) { return false; } return $this->_realpath($path); } function _realpath($path) { if (!$this->canonicalize_paths) { return $path; } if ($this->pwd === false) { if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: $this->_string_shift($response, 4); if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); return $this->_string_shift($response, $length); case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); return false; } } if (!strlen($path) || $path[0] != '/') { $path = $this->pwd . '/' . $path; } $path = explode('/', $path); $new = array(); foreach ($path as $dir) { if (!strlen($dir)) { continue; } switch ($dir) { case '..': array_pop($new); case '.': break; default: $new[] = $dir; } } return '/' . implode('/', $new); } function chdir($dir) { if (!$this->_precheck()) { return false; } if ($dir === '') { $dir = './'; } elseif ($dir[strlen($dir) - 1] != '/') { $dir.= '/'; } $dir = $this->_realpath($dir); if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) { $this->pwd = $dir; return true; } if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } if (!$this->_close_handle($handle)) { return false; } $this->_update_stat_cache($dir, array()); $this->pwd = $dir; return true; } function nlist($dir = '.', $recursive = false) { return $this->_nlist_helper($dir, $recursive, ''); } function _nlist_helper($dir, $recursive, $relativeDir) { $files = $this->_list($dir, false); if (!$recursive || $files === false) { return $files; } $result = array(); foreach ($files as $value) { if ($value == '.' || $value == '..') { if ($relativeDir == '') { $result[] = $value; } continue; } if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) { $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/'); $temp = is_array($temp) ? $temp : array(); $result = array_merge($result, $temp); } else { $result[] = $relativeDir . $value; } } return $result; } function rawlist($dir = '.', $recursive = false) { $files = $this->_list($dir, true); if (!$recursive || $files === false) { return $files; } static $depth = 0; foreach ($files as $key => $value) { if ($depth != 0 && $key == '..') { unset($files[$key]); continue; } $is_directory = false; if ($key != '.' && $key != '..') { if ($this->use_stat_cache) { $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key))); } else { $stat = $this->lstat($dir . '/' . $key); $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY; } } if ($is_directory) { $depth++; $files[$key] = $this->rawlist($dir . '/' . $key, true); $depth--; } else { $files[$key] = (object) $value; } } return $files; } function _list($dir, $raw = true) { if (!$this->_precheck()) { return false; } $dir = $this->_realpath($dir . '/'); if ($dir === false) { return false; } if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } $this->_update_stat_cache($dir, array()); $contents = array(); while (true) { if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: if (strlen($response) < 4) { return false; } extract(unpack('Ncount', $this->_string_shift($response, 4))); for ($i = 0; $i < $count; $i++) { if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $shortname = $this->_string_shift($response, $length); if ($this->version < 4) { if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $longname = $this->_string_shift($response, $length); } $attributes = $this->_parseAttributes($response); if (!isset($attributes['type']) && $this->version < 4) { $fileType = $this->_parseLongname($longname); if ($fileType) { $attributes['type'] = $fileType; } } $contents[$shortname] = $attributes + array('filename' => $shortname); if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) { $this->_update_stat_cache($dir . '/' . $shortname, array()); } else { if ($shortname == '..') { $temp = $this->_realpath($dir . '/..') . '/.'; } else { $temp = $dir . '/' . $shortname; } $this->_update_stat_cache($temp, (object) array('lstat' => $attributes)); } } break; case NET_SFTP_STATUS: if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_EOF) { $this->_logError($response, $status); return false; } break 2; default: user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); return false; } } if (!$this->_close_handle($handle)) { return false; } if (count($this->sortOptions)) { uasort($contents, array(&$this, '_comparator')); } return $raw ? $contents : array_map('strval', array_keys($contents)); } function _comparator($a, $b) { switch (true) { case $a['filename'] === '.' || $b['filename'] === '.': if ($a['filename'] === $b['filename']) { return 0; } return $a['filename'] === '.' ? -1 : 1; case $a['filename'] === '..' || $b['filename'] === '..': if ($a['filename'] === $b['filename']) { return 0; } return $a['filename'] === '..' ? -1 : 1; case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY: if (!isset($b['type'])) { return 1; } if ($b['type'] !== $a['type']) { return -1; } break; case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY: return 1; } foreach ($this->sortOptions as $sort => $order) { if (!isset($a[$sort]) || !isset($b[$sort])) { if (isset($a[$sort])) { return -1; } if (isset($b[$sort])) { return 1; } return 0; } switch ($sort) { case 'filename': $result = strcasecmp($a['filename'], $b['filename']); if ($result) { return $order === SORT_DESC ? -$result : $result; } break; case 'permissions': case 'mode': $a[$sort]&= 07777; $b[$sort]&= 07777; default: if ($a[$sort] === $b[$sort]) { break; } return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort]; } } } function setListOrder() { $this->sortOptions = array(); $args = func_get_args(); if (empty($args)) { return; } $len = count($args) & 0x7FFFFFFE; for ($i = 0; $i < $len; $i+=2) { $this->sortOptions[$args[$i]] = $args[$i + 1]; } if (!count($this->sortOptions)) { $this->sortOptions = array('bogus' => true); } } function size($filename) { $result = $this->stat($filename); if ($result === false) { return false; } return isset($result['size']) ? $result['size'] : -1; } function _update_stat_cache($path, $value) { if ($this->use_stat_cache === false) { return; } $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path)); $temp = &$this->stat_cache; $max = count($dirs) - 1; foreach ($dirs as $i => $dir) { if (is_object($temp)) { $temp = array(); } if (!isset($temp[$dir])) { $temp[$dir] = array(); } if ($i === $max) { if (is_object($temp[$dir]) && is_object($value)) { if (!isset($value->stat) && isset($temp[$dir]->stat)) { $value->stat = $temp[$dir]->stat; } if (!isset($value->lstat) && isset($temp[$dir]->lstat)) { $value->lstat = $temp[$dir]->lstat; } } $temp[$dir] = $value; break; } $temp = &$temp[$dir]; } } function _remove_from_stat_cache($path) { $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path)); $temp = &$this->stat_cache; $max = count($dirs) - 1; foreach ($dirs as $i => $dir) { if (!is_array($temp)) { return false; } if ($i === $max) { unset($temp[$dir]); return true; } if (!isset($temp[$dir])) { return false; } $temp = &$temp[$dir]; } } function _query_stat_cache($path) { $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path)); $temp = &$this->stat_cache; foreach ($dirs as $dir) { if (!is_array($temp)) { return null; } if (!isset($temp[$dir])) { return null; } $temp = &$temp[$dir]; } return $temp; } function stat($filename) { if (!$this->_precheck()) { return false; } $filename = $this->_realpath($filename); if ($filename === false) { return false; } if ($this->use_stat_cache) { $result = $this->_query_stat_cache($filename); if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) { return $result['.']->stat; } if (is_object($result) && isset($result->stat)) { return $result->stat; } } $stat = $this->_stat($filename, NET_SFTP_STAT); if ($stat === false) { $this->_remove_from_stat_cache($filename); return false; } if (isset($stat['type'])) { if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { $filename.= '/.'; } $this->_update_stat_cache($filename, (object) array('stat' => $stat)); return $stat; } $pwd = $this->pwd; $stat['type'] = $this->chdir($filename) ? NET_SFTP_TYPE_DIRECTORY : NET_SFTP_TYPE_REGULAR; $this->pwd = $pwd; if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { $filename.= '/.'; } $this->_update_stat_cache($filename, (object) array('stat' => $stat)); return $stat; } function lstat($filename) { if (!$this->_precheck()) { return false; } $filename = $this->_realpath($filename); if ($filename === false) { return false; } if ($this->use_stat_cache) { $result = $this->_query_stat_cache($filename); if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) { return $result['.']->lstat; } if (is_object($result) && isset($result->lstat)) { return $result->lstat; } } $lstat = $this->_stat($filename, NET_SFTP_LSTAT); if ($lstat === false) { $this->_remove_from_stat_cache($filename); return false; } if (isset($lstat['type'])) { if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) { $filename.= '/.'; } $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); return $lstat; } $stat = $this->_stat($filename, NET_SFTP_STAT); if ($lstat != $stat) { $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK)); $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); return $stat; } $pwd = $this->pwd; $lstat['type'] = $this->chdir($filename) ? NET_SFTP_TYPE_DIRECTORY : NET_SFTP_TYPE_REGULAR; $this->pwd = $pwd; if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) { $filename.= '/.'; } $this->_update_stat_cache($filename, (object) array('lstat' => $lstat)); return $lstat; } function _stat($filename, $type) { $packet = pack('Na*', strlen($filename), $filename); if (!$this->_send_sftp_packet($type, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_ATTRS: return $this->_parseAttributes($response); case NET_SFTP_STATUS: $this->_logError($response); return false; } user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); return false; } function truncate($filename, $new_size) { $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); return $this->_setstat($filename, $attr, false); } function touch($filename, $time = null, $atime = null) { if (!$this->_precheck()) { return false; } $filename = $this->_realpath($filename); if ($filename === false) { return false; } if (!isset($time)) { $time = time(); } if (!isset($atime)) { $atime = $time; } if ($this->version < 4) { $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time); } else { $attr = pack( 'N5', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime / 4294967296, $atime, $time / 4294967296, $time ); } $packet = pack('Na*', strlen($filename), $filename); $packet.= $this->version >= 5 ? pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) : pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL); $packet.= $attr; if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: return $this->_close_handle(substr($response, 4)); case NET_SFTP_STATUS: $this->_logError($response); break; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } return $this->_setstat($filename, $attr, false); } function chown($filename, $uid, $recursive = false) { $attr = $this->version < 4 ? pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) : pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, strlen($uid), $uid, 0, ''); return $this->_setstat($filename, $attr, $recursive); } function chgrp($filename, $gid, $recursive = false) { $attr = $this->version < 4 ? pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid) : pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, 0, '', strlen($gid), $gid); return $this->_setstat($filename, $attr, $recursive); } function chmod($mode, $filename, $recursive = false) { if (is_string($mode) && is_int($filename)) { $temp = $mode; $mode = $filename; $filename = $temp; } $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777); if (!$this->_setstat($filename, $attr, $recursive)) { return false; } if ($recursive) { return true; } $filename = $this->realpath($filename); $packet = pack('Na*', strlen($filename), $filename); if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_ATTRS: $attrs = $this->_parseAttributes($response); return $attrs['permissions']; case NET_SFTP_STATUS: $this->_logError($response); return false; } user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); return false; } function _setstat($filename, $attr, $recursive) { if (!$this->_precheck()) { return false; } $filename = $this->_realpath($filename); if ($filename === false) { return false; } $this->_remove_from_stat_cache($filename); if ($recursive) { $i = 0; $result = $this->_setstat_recursive($filename, $attr, $i); $this->_read_put_responses($i); return $result; } $packet = $this->version >= 4 ? pack('Na*a*Ca*', strlen($filename), $filename, substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) : pack('Na*a*', strlen($filename), $filename, $attr); if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } return true; } function _setstat_recursive($path, $attr, &$i) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; $entries = $this->_list($path, true); if ($entries === false) { return $this->_setstat($path, $attr, false); } if (empty($entries)) { return false; } unset($entries['.'], $entries['..']); foreach ($entries as $filename => $props) { if (!isset($props['type'])) { return false; } $temp = $path . '/' . $filename; if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { if (!$this->_setstat_recursive($temp, $attr, $i)) { return false; } } else { $packet = $this->version >= 4 ? pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) : pack('Na*a*', strlen($temp), $temp, $attr); if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; } } } $packet = $this->version >= 4 ? pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) : pack('Na*a*', strlen($temp), $temp, $attr); if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; } return true; } function readlink($link) { if (!$this->_precheck()) { return false; } $link = $this->_realpath($link); if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: break; case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Ncount', $this->_string_shift($response, 4))); if (!$count) { return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); return $this->_string_shift($response, $length); } function symlink($target, $link) { if (!$this->_precheck()) { return false; } $link = $this->_realpath($link); if ($this->version == 6) { $type = NET_SFTP_LINK; $packet = pack('Na*Na*C', strlen($link), $link, strlen($target), $target, 1); } else { $type = NET_SFTP_SYMLINK; $packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ? pack('Na*Na*', strlen($target), $target, strlen($link), $link) : pack('Na*Na*', strlen($link), $link, strlen($target), $target); } if (!$this->_send_sftp_packet($type, $packet)) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } return true; } function mkdir($dir, $mode = -1, $recursive = false) { if (!$this->_precheck()) { return false; } $dir = $this->_realpath($dir); if ($recursive) { $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir)); if (empty($dirs[0])) { array_shift($dirs); $dirs[0] = '/' . $dirs[0]; } for ($i = 0; $i < count($dirs); $i++) { $temp = array_slice($dirs, 0, $i + 1); $temp = implode('/', $temp); $result = $this->_mkdir_helper($temp, $mode); } return $result; } return $this->_mkdir_helper($dir, $mode); } function _mkdir_helper($dir, $mode) { if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, "\0\0\0\0"))) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } if ($mode !== -1) { $this->chmod($mode, $dir); } return true; } function rmdir($dir) { if (!$this->_precheck()) { return false; } $dir = $this->_realpath($dir); if ($dir === false) { return false; } if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } $this->_remove_from_stat_cache($dir); return true; } function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null) { if (!$this->_precheck()) { return false; } $remote_file = $this->_realpath($remote_file); if ($remote_file === false) { return false; } $this->_remove_from_stat_cache($remote_file); if ($this->version >= 5) { $flags = NET_SFTP_OPEN_OPEN_OR_CREATE; } else { $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; } if ($start >= 0) { $offset = $start; } elseif ($mode & self::RESUME) { $size = $this->size($remote_file); $offset = $size !== false ? $size : 0; } else { $offset = 0; if ($this->version >= 5) { $flags = NET_SFTP_OPEN_CREATE_TRUNCATE; } else { $flags|= NET_SFTP_OPEN_TRUNCATE; } } $packet = pack('Na*', strlen($remote_file), $remote_file); $packet.= $this->version >= 5 ? pack('N3', 0, $flags, 0) : pack('N2', $flags, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } $dataCallback = false; switch (true) { case $mode & self::SOURCE_CALLBACK: if (!is_callable($data)) { user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); } $dataCallback = $data; break; case is_resource($data): $mode = $mode & ~self::SOURCE_LOCAL_FILE; $info = stream_get_meta_data($data); if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') { $fp = fopen('php://memory', 'w+'); stream_copy_to_stream($data, $fp); rewind($fp); } else { $fp = $data; } break; case $mode & self::SOURCE_LOCAL_FILE: if (!is_file($data)) { user_error("$data is not a valid file"); return false; } $fp = @fopen($data, 'rb'); if (!$fp) { return false; } } if (isset($fp)) { $stat = fstat($fp); $size = !empty($stat) ? $stat['size'] : 0; if ($local_start >= 0) { fseek($fp, $local_start); $size-= $local_start; } } elseif ($dataCallback) { $size = 0; } else { $size = strlen($data); } $sent = 0; $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size; $sftp_packet_size = $this->max_sftp_packet; $sftp_packet_size-= strlen($handle) + 25; $i = $j = 0; while ($dataCallback || ($size === 0 || $sent < $size)) { if ($dataCallback) { $temp = call_user_func($dataCallback, $sftp_packet_size); if (is_null($temp)) { break; } } else { $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size); if ($temp === false || $temp === '') { break; } } $subtemp = $offset + $sent; $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp); if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) { if ($mode & self::SOURCE_LOCAL_FILE) { fclose($fp); } return false; } $sent+= strlen($temp); if (is_callable($progressCallback)) { call_user_func($progressCallback, $sent); } $i++; $j++; if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) { if (!$this->_read_put_responses($i)) { $i = 0; break; } $i = 0; } } $result = $this->_close_handle($handle); if (!$this->_read_put_responses($i)) { if ($mode & self::SOURCE_LOCAL_FILE) { fclose($fp); } $this->_close_handle($handle); return false; } if ($mode & SFTP::SOURCE_LOCAL_FILE) { if (isset($fp) && is_resource($fp)) { fclose($fp); } if ($this->preserveTime) { $stat = stat($data); if ($this->version < 4) { $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['mtime']); } else { $attr = pack( 'N5', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'] / 4294967296, $stat['atime'], $stat['mtime'] / 4294967296, $stat['mtime'] ); } if (!$this->_setstat($remote_file, $attr, false)) { user_error('Error setting file time'); } } } return $result; } function _read_put_responses($i) { while ($i--) { $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); break; } } return $i < 0; } function _close_handle($handle) { if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } return true; } function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null) { if (!$this->_precheck()) { return false; } $remote_file = $this->_realpath($remote_file); if ($remote_file === false) { return false; } $packet = pack('Na*', strlen($remote_file), $remote_file); $packet.= $this->version >= 5 ? pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) : pack('N2', NET_SFTP_OPEN_READ, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: $handle = substr($response, 4); break; case NET_SFTP_STATUS: $this->_logError($response); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } if (is_resource($local_file)) { $fp = $local_file; $stat = fstat($fp); $res_offset = $stat['size']; } else { $res_offset = 0; if ($local_file !== false && !is_callable($local_file)) { $fp = fopen($local_file, 'wb'); if (!$fp) { return false; } } else { $content = ''; } } $fclose_check = $local_file !== false && !is_callable($local_file) && !is_resource($local_file); $start = $offset; $read = 0; while (true) { $i = 0; while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) { $tempoffset = $start + $read; $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet; $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size); if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) { if ($fclose_check) { fclose($fp); } return false; } $packet = null; $read+= $packet_size; $i++; } if (!$i) { break; } $packets_sent = $i - 1; $clear_responses = false; while ($i > 0) { $i--; if ($clear_responses) { $this->_get_sftp_packet($packets_sent - $i); continue; } else { $response = $this->_get_sftp_packet($packets_sent - $i); } switch ($this->packet_type) { case NET_SFTP_DATA: $temp = substr($response, 4); $offset+= strlen($temp); if ($local_file === false) { $content.= $temp; } elseif (is_callable($local_file)) { $local_file($temp); } else { fputs($fp, $temp); } if (is_callable($progressCallback)) { call_user_func($progressCallback, $offset); } $temp = null; break; case NET_SFTP_STATUS: $this->_logError($response); $clear_responses = true; break; default: if ($fclose_check) { fclose($fp); } if ($this->channel_close) { $this->partial_init = false; $this->_init_sftp_connection(); return false; } else { user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS'); } } $response = null; } if ($clear_responses) { break; } } if ($length > 0 && $length <= $offset - $start) { if ($local_file === false) { $content = substr($content, 0, $length); } else { ftruncate($fp, $length + $res_offset); } } if ($fclose_check) { fclose($fp); if ($this->preserveTime) { $stat = $this->stat($remote_file); touch($local_file, $stat['mtime'], $stat['atime']); } } if (!$this->_close_handle($handle)) { return false; } return isset($content) ? $content : true; } function delete($path, $recursive = true) { if (!$this->_precheck()) { return false; } if (is_object($path)) { $path = (string) $path; } if (!is_string($path) || $path == '') { return false; } $path = $this->_realpath($path); if ($path === false) { return false; } if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); if (!$recursive) { return false; } $i = 0; $result = $this->_delete_recursive($path, $i); $this->_read_put_responses($i); return $result; } $this->_remove_from_stat_cache($path); return true; } function _delete_recursive($path, &$i) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; $entries = $this->_list($path, true); if (empty($entries)) { return false; } unset($entries['.'], $entries['..']); foreach ($entries as $filename => $props) { if (!isset($props['type'])) { return false; } $temp = $path . '/' . $filename; if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { if (!$this->_delete_recursive($temp, $i)) { return false; } } else { if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) { return false; } $this->_remove_from_stat_cache($temp); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; } } } if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) { return false; } $this->_remove_from_stat_cache($path); $i++; if ($i >= NET_SFTP_QUEUE_SIZE) { if (!$this->_read_put_responses($i)) { return false; } $i = 0; } return true; } function file_exists($path) { if ($this->use_stat_cache) { if (!$this->_precheck()) { return false; } $path = $this->_realpath($path); $result = $this->_query_stat_cache($path); if (isset($result)) { return $result !== false; } } return $this->stat($path) !== false; } function is_dir($path) { $result = $this->_get_stat_cache_prop($path, 'type'); if ($result === false) { return false; } return $result === NET_SFTP_TYPE_DIRECTORY; } function is_file($path) { $result = $this->_get_stat_cache_prop($path, 'type'); if ($result === false) { return false; } return $result === NET_SFTP_TYPE_REGULAR; } function is_link($path) { $result = $this->_get_lstat_cache_prop($path, 'type'); if ($result === false) { return false; } return $result === NET_SFTP_TYPE_SYMLINK; } function is_readable($path) { if (!$this->_precheck()) { return false; } $path = $this->_realpath($path); $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: return true; case NET_SFTP_STATUS: return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } } function is_writable($path) { if (!$this->_precheck()) { return false; } $path = $this->_realpath($path); $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } $response = $this->_get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_HANDLE: return true; case NET_SFTP_STATUS: return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); return false; } } function is_writeable($path) { return $this->is_writable($path); } function fileatime($path) { return $this->_get_stat_cache_prop($path, 'atime'); } function filemtime($path) { return $this->_get_stat_cache_prop($path, 'mtime'); } function fileperms($path) { return $this->_get_stat_cache_prop($path, 'permissions'); } function fileowner($path) { return $this->_get_stat_cache_prop($path, 'uid'); } function filegroup($path) { return $this->_get_stat_cache_prop($path, 'gid'); } function filesize($path) { return $this->_get_stat_cache_prop($path, 'size'); } function filetype($path) { $type = $this->_get_stat_cache_prop($path, 'type'); if ($type === false) { return false; } switch ($type) { case NET_SFTP_TYPE_BLOCK_DEVICE: return 'block'; case NET_SFTP_TYPE_CHAR_DEVICE: return 'char'; case NET_SFTP_TYPE_DIRECTORY: return 'dir'; case NET_SFTP_TYPE_FIFO: return 'fifo'; case NET_SFTP_TYPE_REGULAR: return 'file'; case NET_SFTP_TYPE_SYMLINK: return 'link'; default: return false; } } function _get_stat_cache_prop($path, $prop) { return $this->_get_xstat_cache_prop($path, $prop, 'stat'); } function _get_lstat_cache_prop($path, $prop) { return $this->_get_xstat_cache_prop($path, $prop, 'lstat'); } function _get_xstat_cache_prop($path, $prop, $type) { if (!$this->_precheck()) { return false; } if ($this->use_stat_cache) { $path = $this->_realpath($path); $result = $this->_query_stat_cache($path); if (is_object($result) && isset($result->$type)) { return $result->{$type}[$prop]; } } $result = $this->$type($path); if ($result === false || !isset($result[$prop])) { return false; } return $result[$prop]; } function rename($oldname, $newname) { if (!$this->_precheck()) { return false; } $oldname = $this->_realpath($oldname); $newname = $this->_realpath($newname); if ($oldname === false || $newname === false) { return false; } $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname); if ($this->version >= 5) { $packet.= "\0\0\0\0"; } if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) { return false; } $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS'); return false; } if (strlen($response) < 4) { return false; } extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->_logError($response, $status); return false; } $this->_remove_from_stat_cache($oldname); $this->_remove_from_stat_cache($newname); return true; } function _parseTime($key, $flags, &$response) { if (strlen($response) < 8) { user_error('Malformed file attributes'); return array(); } $attr = array(); $attr[$key] = hexdec(bin2hex($this->_string_shift($response, 8))); if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) { $attr+= extract(unpack('N' . $key . '_nseconds', $this->_string_shift($response, 4))); } return $attr; } function _parseAttributes(&$response) { if ($this->version >= 4) { $length = 5; $format = 'Nflags/Ctype'; } else { $length = 4; $format = 'Nflags'; } $attr = array(); if (strlen($response) < $length) { user_error('Malformed file attributes'); return array(); } extract(unpack($format, $this->_string_shift($response, $length))); if (isset($type)) { $attr['type'] = $type; } foreach ($this->attributes as $key => $value) { switch ($flags & $key) { case NET_SFTP_ATTR_UIDGID: if ($this->version > 3) { continue 2; } break; case NET_SFTP_ATTR_CREATETIME: case NET_SFTP_ATTR_MODIFYTIME: case NET_SFTP_ATTR_ACL: case NET_SFTP_ATTR_OWNERGROUP: case NET_SFTP_ATTR_SUBSECOND_TIMES: if ($this->version < 4) { continue 2; } break; case NET_SFTP_ATTR_BITS: if ($this->version < 5) { continue 2; } break; case NET_SFTP_ATTR_ALLOCATION_SIZE: case NET_SFTP_ATTR_TEXT_HINT: case NET_SFTP_ATTR_MIME_TYPE: case NET_SFTP_ATTR_LINK_COUNT: case NET_SFTP_ATTR_UNTRANSLATED_NAME: case NET_SFTP_ATTR_CTIME: if ($this->version < 6) { continue 2; } } switch ($flags & $key) { case NET_SFTP_ATTR_SIZE: $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8))); break; case NET_SFTP_ATTR_UIDGID: if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_PERMISSIONS: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Npermissions', $this->_string_shift($response, 4)); $attr+= array('mode' => $attr['permissions']); $fileType = $this->_parseMode($attr['permissions']); if ($fileType !== false) { $attr+= array('type' => $fileType); } break; case NET_SFTP_ATTR_ACCESSTIME: if ($this->version >= 4) { $attr+= $this->_parseTime('atime', $flags, $response); break; } if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_CREATETIME: $attr+= $this->_parseTime('createtime', $flags, $response); break; case NET_SFTP_ATTR_MODIFYTIME: $attr+= $this->_parseTime('mtime', $flags, $response); break; case NET_SFTP_ATTR_ACL: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Ncount', $this->_string_shift($response, 4))); for ($i = 0; $i < $count; $i++) { if (strlen($response) < 16) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Ntype/Nflag/Nmask/Nlength', $this->_string_shift($response, 16))); if (strlen($response) < $length) { user_error('Malformed file attributes'); return $attr; } $this->_string_shift($response, $length); } break; case NET_SFTP_ATTR_OWNERGROUP: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); if (strlen($response) < $length) { user_error('Malformed file attributes'); return $attr; } $attr['owner'] = $this->_string_shift($response, $length); if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); if (strlen($response) < $length) { user_error('Malformed file attributes'); return $attr; } $attr['group'] = $this->_string_shift($response, $length); break; case NET_SFTP_ATTR_SUBSECOND_TIMES: break; case NET_SFTP_ATTR_BITS: if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nattrib-bits/Nattrib-bits-valid', $this->_string_shift($response, 8))); break; case NET_SFTP_ATTR_ALLOCATION_SIZE: $attr['allocation-size'] = hexdec(bin2hex($this->_string_shift($response, 8))); break; case NET_SFTP_ATTR_TEXT_HINT: extract(unpack('Ctext-hint', $this->_string_shift($response))); break; case NET_SFTP_ATTR_MIME_TYPE: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); if (strlen($response) < $length) { user_error('Malformed file attributes'); return $attr; } $attr['mime-type'] = $this->_string_shift($response, $length); break; case NET_SFTP_ATTR_LINK_COUNT: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Nlink-count', $this->_string_shift($response, 4)); break; case NET_SFTP_ATTR_UNTRANSLATED_NAME: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); if (strlen($response) < $length) { user_error('Malformed file attributes'); return $attr; } $attr['untranslated-name'] = $this->_string_shift($response, $length); break; case NET_SFTP_ATTR_CTIME: $attr+= $this->_parseTime('ctime', $flags, $response); break; case NET_SFTP_ATTR_EXTENDED: if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Ncount', $this->_string_shift($response, 4))); for ($i = 0; $i < $count; $i++) { if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $key = $this->_string_shift($response, $length); if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } extract(unpack('Nlength', $this->_string_shift($response, 4))); $attr[$key] = $this->_string_shift($response, $length); } } } return $attr; } function _parseMode($mode) { switch ($mode & 0170000) { case 0000000: return false; case 0040000: return NET_SFTP_TYPE_DIRECTORY; case 0100000: return NET_SFTP_TYPE_REGULAR; case 0120000: return NET_SFTP_TYPE_SYMLINK; case 0010000: return NET_SFTP_TYPE_FIFO; case 0020000: return NET_SFTP_TYPE_CHAR_DEVICE; case 0060000: return NET_SFTP_TYPE_BLOCK_DEVICE; case 0140000: return NET_SFTP_TYPE_SOCKET; case 0160000: return NET_SFTP_TYPE_SPECIAL; default: return NET_SFTP_TYPE_UNKNOWN; } } function _parseLongname($longname) { if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) { switch ($longname[0]) { case '-': return NET_SFTP_TYPE_REGULAR; case 'd': return NET_SFTP_TYPE_DIRECTORY; case 'l': return NET_SFTP_TYPE_SYMLINK; default: return NET_SFTP_TYPE_SPECIAL; } } return false; } function _send_sftp_packet($type, $data, $request_id = 1) { $this->curTimeout = $this->timeout; $packet = $this->use_request_id ? pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) : pack('NCa*', strlen($data) + 1, $type, $data); $start = strtok(microtime(), ' ') + strtok(''); $result = $this->_send_channel_packet(self::CHANNEL, $packet); $stop = strtok(microtime(), ' ') + strtok(''); if (defined('NET_SFTP_LOGGING')) { $packet_type = '-> ' . $this->packet_types[$type] . ' (' . round($stop - $start, 4) . 's)'; if (NET_SFTP_LOGGING == self::LOG_REALTIME) { switch (PHP_SAPI) { case 'cli': $start = $stop = "\r\n"; break; default: $start = '
';
$stop = '
'; } echo $start . $this->_format_log(array($data), array($packet_type)) . $stop; @flush(); @ob_flush(); } else { $this->packet_type_log[] = $packet_type; if (NET_SFTP_LOGGING == self::LOG_COMPLEX) { $this->packet_log[] = $data; } } } return $result; } function _reset_connection($reason) { parent::_reset_connection($reason); $this->use_request_id = false; $this->pwd = false; $this->requestBuffer = array(); } function _get_sftp_packet($request_id = null) { $this->channel_close = false; if (isset($request_id) && isset($this->requestBuffer[$request_id])) { $this->packet_type = $this->requestBuffer[$request_id]['packet_type']; $temp = $this->requestBuffer[$request_id]['packet']; unset($this->requestBuffer[$request_id]); return $temp; } $this->curTimeout = $this->timeout; $start = strtok(microtime(), ' ') + strtok(''); while (strlen($this->packet_buffer) < 4) { $temp = $this->_get_channel_packet(self::CHANNEL, true); if ($temp === true) { if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) { $this->channel_close = true; } $this->packet_type = false; $this->packet_buffer = ''; return false; } if ($temp === false) { return false; } $this->packet_buffer.= $temp; } if (strlen($this->packet_buffer) < 4) { return false; } extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4))); $tempLength = $length; $tempLength-= strlen($this->packet_buffer); if (!$this->allow_arbitrary_length_packets && !$this->use_request_id && $tempLength > 256 * 1024) { user_error('Invalid SFTP packet size'); return false; } while ($tempLength > 0) { $temp = $this->_get_channel_packet(self::CHANNEL, true); if (is_bool($temp)) { $this->packet_type = false; $this->packet_buffer = ''; return false; } $this->packet_buffer.= $temp; $tempLength-= strlen($temp); } $stop = strtok(microtime(), ' ') + strtok(''); $this->packet_type = ord($this->_string_shift($this->packet_buffer)); if ($this->use_request_id) { extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); $length-= 5; } else { $length-= 1; } $packet = $this->_string_shift($this->packet_buffer, $length); if (defined('NET_SFTP_LOGGING')) { $packet_type = '<- ' . $this->packet_types[$this->packet_type] . ' (' . round($stop - $start, 4) . 's)'; if (NET_SFTP_LOGGING == self::LOG_REALTIME) { switch (PHP_SAPI) { case 'cli': $start = $stop = "\r\n"; break; default: $start = '
';
$stop = '
'; } echo $start . $this->_format_log(array($packet), array($packet_type)) . $stop; @flush(); @ob_flush(); } else { $this->packet_type_log[] = $packet_type; if (NET_SFTP_LOGGING == self::LOG_COMPLEX) { $this->packet_log[] = $packet; } } } if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) { $this->requestBuffer[$packet_id] = array( 'packet_type' => $this->packet_type, 'packet' => $packet ); return $this->_get_sftp_packet($request_id); } return $packet; } function getSFTPLog() { if (!defined('NET_SFTP_LOGGING')) { return false; } switch (NET_SFTP_LOGGING) { case self::LOG_COMPLEX: return $this->_format_log($this->packet_log, $this->packet_type_log); break; default: return $this->packet_type_log; } } function getSFTPErrors() { return $this->sftp_errors; } function getLastSFTPError() { return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : ''; } function getSupportedVersions() { if (!($this->bitmap & SSH2::MASK_LOGIN)) { return false; } if (!$this->partial_init) { $this->_partial_init_sftp_connection(); } $temp = array('version' => $this->defaultVersion); if (isset($this->extensions['versions'])) { $temp['extensions'] = $this->extensions['versions']; } return $temp; } function getNegotiatedVersion() { if (!$this->_precheck()) { return false; } return $this->version; } function setPreferredVersion($version) { $this->preferredVersion = $version; } function _disconnect($reason) { $this->pwd = false; parent::_disconnect($reason); } function enableDatePreservation() { $this->preserveTime = true; } function disableDatePreservation() { $this->preserveTime = false; } } mode = self::MODE_SSH2; } elseif ($ssh instanceof SSH1) { $this->packet_size = 50000; $this->mode = self::MODE_SSH1; } else { return; } $this->ssh = $ssh; } function put($remote_file, $data, $mode = self::SOURCE_STRING, $callback = null) { if (!isset($this->ssh)) { return false; } if (empty($remote_file)) { user_error('remote_file cannot be blank', E_USER_NOTICE); return false; } if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { return false; } $temp = $this->_receive(); if ($temp !== chr(0)) { return false; } if ($this->mode == self::MODE_SSH2) { $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4; } $remote_file = basename($remote_file); if ($mode == self::SOURCE_STRING) { $size = strlen($data); } else { if (!is_file($data)) { user_error("$data is not a valid file", E_USER_NOTICE); return false; } $fp = @fopen($data, 'rb'); if (!$fp) { return false; } $size = filesize($data); } $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n"); $temp = $this->_receive(); if ($temp !== chr(0)) { return false; } $sent = 0; while ($sent < $size) { $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size); $this->_send($temp); $sent+= strlen($temp); if (is_callable($callback)) { call_user_func($callback, $sent); } } $this->_close(); if ($mode != self::SOURCE_STRING) { fclose($fp); } return true; } function get($remote_file, $local_file = false) { if (!isset($this->ssh)) { return false; } if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { return false; } $this->_send("\0"); if (!preg_match('#(?[^ ]+) (?\d+) (?.+)#', rtrim($this->_receive()), $info)) { return false; } $this->_send("\0"); $size = 0; if ($local_file !== false) { $fp = @fopen($local_file, 'wb'); if (!$fp) { return false; } } $content = ''; while ($size < $info['size']) { $data = $this->_receive(); $size+= strlen($data); if ($local_file === false) { $content.= $data; } else { fputs($fp, $data); } } $this->_close(); if ($local_file !== false) { fclose($fp); return true; } return $content; } function _send($data) { switch ($this->mode) { case self::MODE_SSH2: $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data); break; case self::MODE_SSH1: $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data); $this->ssh->_send_binary_packet($data); } } function _receive() { switch ($this->mode) { case self::MODE_SSH2: return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true); case self::MODE_SSH1: if (!$this->ssh->bitmap) { return false; } while (true) { $response = $this->ssh->_get_binary_packet(); switch ($response[SSH1::RESPONSE_TYPE]) { case NET_SSH1_SMSG_STDOUT_DATA: if (strlen($response[SSH1::RESPONSE_DATA]) < 4) { return false; } extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA])); return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length); case NET_SSH1_SMSG_STDERR_DATA: break; case NET_SSH1_SMSG_EXITSTATUS: $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION)); fclose($this->ssh->fsock); $this->ssh->bitmap = 0; return false; default: user_error('Unknown packet received', E_USER_NOTICE); return false; } } } } function _close() { switch ($this->mode) { case self::MODE_SSH2: $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true); break; case self::MODE_SSH1: $this->ssh->disconnect(); } } } 'No encryption', self::CIPHER_IDEA => 'IDEA in CFB mode', self::CIPHER_DES => 'DES in CBC mode', self::CIPHER_3DES => 'Triple-DES in CBC mode', self::CIPHER_BROKEN_TSS => 'TRI\'s Simple Stream encryption CBC', self::CIPHER_RC4 => 'RC4', self::CIPHER_BLOWFISH => 'Blowfish' ); var $supported_authentications = array( self::AUTH_RHOSTS => '.rhosts or /etc/hosts.equiv', self::AUTH_RSA => 'pure RSA authentication', self::AUTH_PASSWORD => 'password authentication', self::AUTH_RHOSTS_RSA => '.rhosts with RSA host authentication' ); var $server_identification = ''; var $protocol_flags = array(); var $protocol_flag_log = array(); var $message_log = array(); var $realtime_log_file; var $realtime_log_size; var $realtime_log_wrap; var $interactiveBuffer = ''; var $timeout; var $curTimeout; var $log_boundary = ':'; var $log_long_width = 65; var $log_short_width = 16; var $host; var $port; var $connectionTimeout; var $cipher; function __construct($host, $port = 22, $timeout = 10, $cipher = self::CIPHER_3DES) { $this->protocol_flags = array( 1 => 'NET_SSH1_MSG_DISCONNECT', 2 => 'NET_SSH1_SMSG_PUBLIC_KEY', 3 => 'NET_SSH1_CMSG_SESSION_KEY', 4 => 'NET_SSH1_CMSG_USER', 9 => 'NET_SSH1_CMSG_AUTH_PASSWORD', 10 => 'NET_SSH1_CMSG_REQUEST_PTY', 12 => 'NET_SSH1_CMSG_EXEC_SHELL', 13 => 'NET_SSH1_CMSG_EXEC_CMD', 14 => 'NET_SSH1_SMSG_SUCCESS', 15 => 'NET_SSH1_SMSG_FAILURE', 16 => 'NET_SSH1_CMSG_STDIN_DATA', 17 => 'NET_SSH1_SMSG_STDOUT_DATA', 18 => 'NET_SSH1_SMSG_STDERR_DATA', 19 => 'NET_SSH1_CMSG_EOF', 20 => 'NET_SSH1_SMSG_EXITSTATUS', 33 => 'NET_SSH1_CMSG_EXIT_CONFIRMATION' ); $this->_define_array($this->protocol_flags); $this->host = $host; $this->port = $port; $this->connectionTimeout = $timeout; $this->cipher = $cipher; } function _connect() { $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->connectionTimeout); if (!$this->fsock) { user_error(rtrim("Cannot connect to {$this->host}:{$this->port}. Error $errno. $errstr")); return false; } $this->server_identification = $init_line = fgets($this->fsock, 255); if (defined('NET_SSH1_LOGGING')) { $this->_append_log('<-', $this->server_identification); $this->_append_log('->', $this->identifier . "\r\n"); } if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) { user_error('Can only connect to SSH servers'); return false; } if ($parts[1][0] != 1) { user_error("Cannot connect to SSH $parts[1] servers"); return false; } fputs($this->fsock, $this->identifier."\r\n"); $response = $this->_get_binary_packet(); if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) { user_error('Expected SSH_SMSG_PUBLIC_KEY'); return false; } $anti_spoofing_cookie = $this->_string_shift($response[self::RESPONSE_DATA], 8); $this->_string_shift($response[self::RESPONSE_DATA], 4); if (strlen($response[self::RESPONSE_DATA]) < 2) { return false; } $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); $server_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->server_key_public_exponent = $server_key_public_exponent; if (strlen($response[self::RESPONSE_DATA]) < 2) { return false; } $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); $server_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->server_key_public_modulus = $server_key_public_modulus; $this->_string_shift($response[self::RESPONSE_DATA], 4); if (strlen($response[self::RESPONSE_DATA]) < 2) { return false; } $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); $host_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->host_key_public_exponent = $host_key_public_exponent; if (strlen($response[self::RESPONSE_DATA]) < 2) { return false; } $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2)); $host_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->host_key_public_modulus = $host_key_public_modulus; $this->_string_shift($response[self::RESPONSE_DATA], 4); if (strlen($response[self::RESPONSE_DATA]) < 4) { return false; } extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4))); foreach ($this->supported_ciphers as $mask => $name) { if (($supported_ciphers_mask & (1 << $mask)) == 0) { unset($this->supported_ciphers[$mask]); } } if (strlen($response[self::RESPONSE_DATA]) < 4) { return false; } extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4))); foreach ($this->supported_authentications as $mask => $name) { if (($supported_authentications_mask & (1 << $mask)) == 0) { unset($this->supported_authentications[$mask]); } } $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie)); $session_key = Random::string(32); $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0)); if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) { $double_encrypted_session_key = $this->_rsa_crypt( $double_encrypted_session_key, array( $server_key_public_exponent, $server_key_public_modulus ) ); $double_encrypted_session_key = $this->_rsa_crypt( $double_encrypted_session_key, array( $host_key_public_exponent, $host_key_public_modulus ) ); } else { $double_encrypted_session_key = $this->_rsa_crypt( $double_encrypted_session_key, array( $host_key_public_exponent, $host_key_public_modulus ) ); $double_encrypted_session_key = $this->_rsa_crypt( $double_encrypted_session_key, array( $server_key_public_exponent, $server_key_public_modulus ) ); } $cipher = isset($this->supported_ciphers[$this->cipher]) ? $this->cipher : self::CIPHER_3DES; $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_SESSION_KEY'); return false; } switch ($cipher) { case self::CIPHER_DES: $this->crypto = new DES(); $this->crypto->disablePadding(); $this->crypto->enableContinuousBuffer(); $this->crypto->setKey(substr($session_key, 0, 8)); break; case self::CIPHER_3DES: $this->crypto = new TripleDES(TripleDES::MODE_3CBC); $this->crypto->disablePadding(); $this->crypto->enableContinuousBuffer(); $this->crypto->setKey(substr($session_key, 0, 24)); break; } $response = $this->_get_binary_packet(); if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { user_error('Expected SSH_SMSG_SUCCESS'); return false; } $this->bitmap = self::MASK_CONNECTED; return true; } function login($username, $password = '') { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { $this->bitmap |= self::MASK_CONSTRUCTOR; if (!$this->_connect()) { return false; } } if (!($this->bitmap & self::MASK_CONNECTED)) { return false; } $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_USER'); return false; } $response = $this->_get_binary_packet(); if ($response === true) { return false; } if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { $this->bitmap |= self::MASK_LOGIN; return true; } elseif ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) { user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); return false; } $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_AUTH_PASSWORD'); return false; } if (defined('NET_SSH1_LOGGING') && NET_SSH1_LOGGING == self::LOG_COMPLEX) { $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen('password'), 'password'); $this->message_log[count($this->message_log) - 1] = $data; } $response = $this->_get_binary_packet(); if ($response === true) { return false; } if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { $this->bitmap |= self::MASK_LOGIN; return true; } elseif ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) { return false; } else { user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); return false; } } function setTimeout($timeout) { $this->timeout = $this->curTimeout = $timeout; } function exec($cmd, $block = true) { if (!($this->bitmap & self::MASK_LOGIN)) { user_error('Operation disallowed prior to login()'); return false; } $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_EXEC_CMD'); return false; } if (!$block) { return true; } $output = ''; $response = $this->_get_binary_packet(); if ($response !== false) { do { $output.= substr($response[self::RESPONSE_DATA], 4); $response = $this->_get_binary_packet(); } while (is_array($response) && $response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_EXITSTATUS); } $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION); $this->_send_binary_packet($data); fclose($this->fsock); $this->bitmap = 0; return $output; } function _initShell() { $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, self::TTY_OP_END); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_REQUEST_PTY'); return false; } $response = $this->_get_binary_packet(); if ($response === true) { return false; } if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { user_error('Expected SSH_SMSG_SUCCESS'); return false; } $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_EXEC_SHELL'); return false; } $this->bitmap |= self::MASK_SHELL; return true; } function write($cmd) { return $this->interactiveWrite($cmd); } function read($expect, $mode = self::READ_SIMPLE) { if (!($this->bitmap & self::MASK_LOGIN)) { user_error('Operation disallowed prior to login()'); return false; } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { user_error('Unable to initiate an interactive shell session'); return false; } $match = $expect; while (true) { if ($mode == self::READ_REGEX) { preg_match($expect, $this->interactiveBuffer, $matches); $match = isset($matches[0]) ? $matches[0] : ''; } $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false; if ($pos !== false) { return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); } $response = $this->_get_binary_packet(); if ($response === true) { return $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)); } $this->interactiveBuffer.= substr($response[self::RESPONSE_DATA], 4); } } function interactiveWrite($cmd) { if (!($this->bitmap & self::MASK_LOGIN)) { user_error('Operation disallowed prior to login()'); return false; } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { user_error('Unable to initiate an interactive shell session'); return false; } $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd); if (!$this->_send_binary_packet($data)) { user_error('Error sending SSH_CMSG_STDIN'); return false; } return true; } function interactiveRead() { if (!($this->bitmap & self::MASK_LOGIN)) { user_error('Operation disallowed prior to login()'); return false; } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { user_error('Unable to initiate an interactive shell session'); return false; } $read = array($this->fsock); $write = $except = null; if (stream_select($read, $write, $except, 0)) { $response = $this->_get_binary_packet(); return substr($response[self::RESPONSE_DATA], 4); } else { return ''; } } function disconnect() { $this->_disconnect(); } function __destruct() { $this->_disconnect(); } function _disconnect($msg = 'Client Quit') { if ($this->bitmap) { $data = pack('C', NET_SSH1_CMSG_EOF); $this->_send_binary_packet($data); $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg); $this->_send_binary_packet($data); fclose($this->fsock); $this->bitmap = 0; } } function _get_binary_packet() { if (feof($this->fsock)) { return false; } if ($this->curTimeout) { $read = array($this->fsock); $write = $except = null; $start = strtok(microtime(), ' ') + strtok(''); $sec = floor($this->curTimeout); $usec = 1000000 * ($this->curTimeout - $sec); if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { return true; } $elapsed = strtok(microtime(), ' ') + strtok('') - $start; $this->curTimeout-= $elapsed; } $start = strtok(microtime(), ' ') + strtok(''); $data = fread($this->fsock, 4); if (strlen($data) < 4) { return false; } $temp = unpack('Nlength', $data); $padding_length = 8 - ($temp['length'] & 7); $length = $temp['length'] + $padding_length; $raw = ''; while ($length > 0) { $temp = fread($this->fsock, $length); if (strlen($temp) != $length) { return false; } $raw.= $temp; $length-= strlen($temp); } $stop = strtok(microtime(), ' ') + strtok(''); if (strlen($raw) && $this->crypto !== false) { $raw = $this->crypto->decrypt($raw); } $padding = substr($raw, 0, $padding_length); $type = $raw[$padding_length]; $data = substr($raw, $padding_length + 1, -4); if (strlen($raw) < 4) { return false; } $temp = unpack('Ncrc', substr($raw, -4)); $type = ord($type); if (defined('NET_SSH1_LOGGING')) { $temp = isset($this->protocol_flags[$type]) ? $this->protocol_flags[$type] : 'UNKNOWN'; $temp = '<- ' . $temp . ' (' . round($stop - $start, 4) . 's)'; $this->_append_log($temp, $data); } return array( self::RESPONSE_TYPE => $type, self::RESPONSE_DATA => $data ); } function _send_binary_packet($data) { if (feof($this->fsock)) { return false; } $length = strlen($data) + 4; $padding = Random::string(8 - ($length & 7)); $orig = $data; $data = $padding . $data; $data.= pack('N', $this->_crc($data)); if ($this->crypto !== false) { $data = $this->crypto->encrypt($data); } $packet = pack('Na*', $length, $data); $start = strtok(microtime(), ' ') + strtok(''); $result = strlen($packet) == fputs($this->fsock, $packet); $stop = strtok(microtime(), ' ') + strtok(''); if (defined('NET_SSH1_LOGGING')) { $temp = isset($this->protocol_flags[ord($orig[0])]) ? $this->protocol_flags[ord($orig[0])] : 'UNKNOWN'; $temp = '-> ' . $temp . ' (' . round($stop - $start, 4) . 's)'; $this->_append_log($temp, $orig); } return $result; } function _crc($data) { static $crc_lookup_table = array( 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D ); $crc = 0x00000000; $length = strlen($data); for ($i=0; $i<$length; $i++) { $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_lookup_table[($crc & 0xFF) ^ ord($data[$i])]; } return $crc; } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } function _rsa_crypt($m, $key) { $modulus = $key[1]->toBytes(); $length = strlen($modulus) - strlen($m) - 3; $random = ''; while (strlen($random) != $length) { $block = Random::string($length - strlen($random)); $block = str_replace("\x00", '', $block); $random.= $block; } $temp = chr(0) . chr(2) . $random . chr(0) . $m; $m = new BigInteger($temp, 256); $m = $m->modPow($key[0], $key[1]); return $m->toBytes(); } function _define_array() { $args = func_get_args(); foreach ($args as $arg) { foreach ($arg as $key => $value) { if (!defined($value)) { define($value, $key); } else { break 2; } } } } function getLog() { if (!defined('NET_SSH1_LOGGING')) { return false; } switch (NET_SSH1_LOGGING) { case self::LOG_SIMPLE: return $this->message_number_log; break; case self::LOG_COMPLEX: return $this->_format_log($this->message_log, $this->protocol_flags_log); break; default: return false; } } function _format_log($message_log, $message_number_log) { $output = ''; for ($i = 0; $i < count($message_log); $i++) { $output.= $message_number_log[$i] . "\r\n"; $current_log = $message_log[$i]; $j = 0; do { if (strlen($current_log)) { $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; } $fragment = $this->_string_shift($current_log, $this->log_short_width); $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary)); $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n"; $j++; } while (strlen($current_log)); $output.= "\r\n"; } return $output; } function _format_log_helper($matches) { return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT); } function getServerKeyPublicExponent($raw_output = false) { return $raw_output ? $this->server_key_public_exponent->toBytes() : $this->server_key_public_exponent->toString(); } function getServerKeyPublicModulus($raw_output = false) { return $raw_output ? $this->server_key_public_modulus->toBytes() : $this->server_key_public_modulus->toString(); } function getHostKeyPublicExponent($raw_output = false) { return $raw_output ? $this->host_key_public_exponent->toBytes() : $this->host_key_public_exponent->toString(); } function getHostKeyPublicModulus($raw_output = false) { return $raw_output ? $this->host_key_public_modulus->toBytes() : $this->host_key_public_modulus->toString(); } function getSupportedCiphers($raw_output = false) { return $raw_output ? array_keys($this->supported_ciphers) : array_values($this->supported_ciphers); } function getSupportedAuthentications($raw_output = false) { return $raw_output ? array_keys($this->supported_authentications) : array_values($this->supported_authentications); } function getServerIdentification() { return rtrim($this->server_identification); } function _append_log($protocol_flags, $message) { switch (NET_SSH1_LOGGING) { case self::LOG_SIMPLE: $this->protocol_flags_log[] = $protocol_flags; break; case self::LOG_COMPLEX: $this->protocol_flags_log[] = $protocol_flags; $this->_string_shift($message); $this->log_size+= strlen($message); $this->message_log[] = $message; while ($this->log_size > self::LOG_MAX_SIZE) { $this->log_size-= strlen(array_shift($this->message_log)); array_shift($this->protocol_flags_log); } break; case self::LOG_REALTIME: echo "
\r\n" . $this->_format_log(array($message), array($protocol_flags)) . "\r\n
\r\n"; @flush(); @ob_flush(); break; case self::LOG_REALTIME_FILE: if (!isset($this->realtime_log_file)) { $filename = self::LOG_REALTIME_FILE; $fp = fopen($filename, 'w'); $this->realtime_log_file = $fp; } if (!is_resource($this->realtime_log_file)) { break; } $entry = $this->_format_log(array($message), array($protocol_flags)); if ($this->realtime_log_wrap) { $temp = "<<< START >>>\r\n"; $entry.= $temp; fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); } $this->realtime_log_size+= strlen($entry); if ($this->realtime_log_size > self::LOG_MAX_SIZE) { fseek($this->realtime_log_file, 0); $this->realtime_log_size = strlen($entry); $this->realtime_log_wrap = true; } fputs($this->realtime_log_file, $entry); } } } ', '', strip_tags($matches[2][$i]))); if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { $versions[$matches[1][$i]] = $fullVersion; } else { $versions[$matches[1][$i]] = $m[0]; } } } } switch (true) { case !isset($versions['Header']): case !isset($versions['Library']): case $versions['Header'] == $versions['Library']: case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0: define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); break; default: define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); } } if (!defined('PHP_INT_SIZE')) { define('PHP_INT_SIZE', 4); } if (empty(self::$base) && MATH_BIGINTEGER_MODE == self::MODE_INTERNAL) { switch (PHP_INT_SIZE) { case 8: self::$base = 31; self::$baseFull = 0x80000000; self::$maxDigit = 0x7FFFFFFF; self::$msb = 0x40000000; self::$max10 = 1000000000; self::$max10Len = 9; self::$maxDigit2 = pow(2, 62); break; default: self::$base = 26; self::$baseFull = 0x4000000; self::$maxDigit = 0x3FFFFFF; self::$msb = 0x2000000; self::$max10 = 10000000; self::$max10Len = 7; self::$maxDigit2 = pow(2, 52); } } switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: switch (true) { case is_resource($x) && get_resource_type($x) == 'GMP integer': case $x instanceof \GMP: $this->value = $x; return; } $this->value = gmp_init(0); break; case self::MODE_BCMATH: $this->value = '0'; break; default: $this->value = array(); } if (empty($x) && (abs($base) != 256 || $x !== '0')) { return; } switch ($base) { case -256: if (ord($x[0]) & 0x80) { $x = ~$x; $this->is_negative = true; } case 256: switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $this->value = function_exists('gmp_import') ? gmp_import($x) : gmp_init('0x' . bin2hex($x)); if ($this->is_negative) { $this->value = gmp_neg($this->value); } break; case self::MODE_BCMATH: $len = (strlen($x) + 3) & 0xFFFFFFFC; $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); for ($i = 0; $i < $len; $i+= 4) { $this->value = bcmul($this->value, '4294967296', 0); $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); } if ($this->is_negative) { $this->value = '-' . $this->value; } break; default: while (strlen($x)) { $this->value[] = $this->_bytes2int($this->_base256_rshift($x, self::$base)); } } if ($this->is_negative) { if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { $this->is_negative = false; } $temp = $this->add(new static('-1')); $this->value = $temp->value; } break; case 16: case -16: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); $is_negative = false; if ($base < 0 && hexdec($x[0]) >= 8) { $this->is_negative = $is_negative = true; $x = bin2hex(~pack('H*', $x)); } switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; $this->value = gmp_init($temp); $this->is_negative = false; break; case self::MODE_BCMATH: $x = (strlen($x) & 1) ? '0' . $x : $x; $temp = new static(pack('H*', $x), 256); $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; $this->is_negative = false; break; default: $x = (strlen($x) & 1) ? '0' . $x : $x; $temp = new static(pack('H*', $x), 256); $this->value = $temp->value; } if ($is_negative) { $temp = $this->add(new static('-1')); $this->value = $temp->value; } break; case 10: case -10: $x = preg_replace('#(?value = gmp_init($x); break; case self::MODE_BCMATH: $this->value = $x === '-' ? '0' : (string) $x; break; default: $temp = new static(); $multiplier = new static(); $multiplier->value = array(self::$max10); if ($x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = str_pad($x, strlen($x) + ((self::$max10Len - 1) * strlen($x)) % self::$max10Len, 0, STR_PAD_LEFT); while (strlen($x)) { $temp = $temp->multiply($multiplier); $temp = $temp->add(new static($this->_int2bytes(substr($x, 0, self::$max10Len)), 256)); $x = substr($x, self::$max10Len); } $this->value = $temp->value; } break; case 2: case -2: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^([01]*).*#', '$1', $x); $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); $str = '0x'; while (strlen($x)) { $part = substr($x, 0, 4); $str.= dechex(bindec($part)); $x = substr($x, 4); } if ($this->is_negative) { $str = '-' . $str; } $temp = new static($str, 8 * $base); $this->value = $temp->value; $this->is_negative = $temp->is_negative; break; default: } } function toBytes($twos_compliment = false) { if ($twos_compliment) { $comparison = $this->compare(new static()); if ($comparison == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $temp = $comparison < 0 ? $this->add(new static(1)) : $this->copy(); $bytes = $temp->toBytes(); if (!strlen($bytes)) { $bytes = chr(0); } if ($this->precision <= 0 && (ord($bytes[0]) & 0x80)) { $bytes = chr(0) . $bytes; } return $comparison < 0 ? ~$bytes : $bytes; } switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: if (gmp_cmp($this->value, gmp_init(0)) == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } if (function_exists('gmp_export')) { $temp = gmp_export($this->value); } else { $temp = gmp_strval(gmp_abs($this->value), 16); $temp = (strlen($temp) & 1) ? '0' . $temp : $temp; $temp = pack('H*', $temp); } return $this->precision > 0 ? substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($temp, chr(0)); case self::MODE_BCMATH: if ($this->value === '0') { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $value = ''; $current = $this->value; if ($current[0] == '-') { $current = substr($current, 1); } while (bccomp($current, '0', 0) > 0) { $temp = bcmod($current, '16777216'); $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; $current = bcdiv($current, '16777216', 0); } return $this->precision > 0 ? substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($value, chr(0)); } if (!count($this->value)) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $result = $this->_int2bytes($this->value[count($this->value) - 1]); $temp = $this->copy(); for ($i = count($temp->value) - 2; $i >= 0; --$i) { $temp->_base256_lshift($result, self::$base); $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); } return $this->precision > 0 ? str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : $result; } function toHex($twos_compliment = false) { return bin2hex($this->toBytes($twos_compliment)); } function toBits($twos_compliment = false) { $hex = $this->toHex($twos_compliment); $bits = ''; for ($i = strlen($hex) - 6, $start = strlen($hex) % 6; $i >= $start; $i-=6) { $bits = str_pad(decbin(hexdec(substr($hex, $i, 6))), 24, '0', STR_PAD_LEFT) . $bits; } if ($start) { $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8 * $start, '0', STR_PAD_LEFT) . $bits; } $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) { return '0' . $result; } return $result; } function toString() { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: return gmp_strval($this->value); case self::MODE_BCMATH: if ($this->value === '0') { return '0'; } return ltrim($this->value, '0'); } if (!count($this->value)) { return '0'; } $temp = $this->copy(); $temp->bitmask = false; $temp->is_negative = false; $divisor = new static(); $divisor->value = array(self::$max10); $result = ''; while (count($temp->value)) { list($temp, $mod) = $temp->divide($divisor); $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', self::$max10Len, '0', STR_PAD_LEFT) . $result; } $result = ltrim($result, '0'); if (empty($result)) { $result = '0'; } if ($this->is_negative) { $result = '-' . $result; } return $result; } function copy() { $temp = new static(); $temp->value = $this->value; $temp->is_negative = $this->is_negative; $temp->precision = $this->precision; $temp->bitmask = $this->bitmask; return $temp; } function __toString() { return $this->toString(); } function __clone() { return $this->copy(); } function __sleep() { $this->hex = $this->toHex(true); $vars = array('hex'); if ($this->precision > 0) { $vars[] = 'precision'; } return $vars; } function __wakeup() { $temp = new static($this->hex, -16); $this->value = $temp->value; $this->is_negative = $temp->is_negative; if ($this->precision > 0) { $this->setPrecision($this->precision); } } function __debugInfo() { $opts = array(); switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $engine = 'gmp'; break; case self::MODE_BCMATH: $engine = 'bcmath'; break; case self::MODE_INTERNAL: $engine = 'internal'; $opts[] = PHP_INT_SIZE == 8 ? '64-bit' : '32-bit'; } if (MATH_BIGINTEGER_MODE != self::MODE_GMP && defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { $opts[] = 'OpenSSL'; } if (!empty($opts)) { $engine.= ' (' . implode('.', $opts) . ')'; } return array( 'value' => '0x' . $this->toHex(true), 'engine' => $engine ); } function add($y) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_add($this->value, $y->value); return $this->_normalize($temp); case self::MODE_BCMATH: $temp = new static(); $temp->value = bcadd($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new static(); $result->value = $temp[self::VALUE]; $result->is_negative = $temp[self::SIGN]; return $this->_normalize($result); } function _add($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( self::VALUE => $y_value, self::SIGN => $y_negative ); } elseif ($y_size == 0) { return array( self::VALUE => $x_value, self::SIGN => $x_negative ); } if ($x_negative != $y_negative) { if ($x_value == $y_value) { return array( self::VALUE => array(), self::SIGN => false ); } $temp = $this->_subtract($x_value, false, $y_value, false); $temp[self::SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? $x_negative : $y_negative; return $temp; } if ($x_size < $y_size) { $size = $x_size; $value = $y_value; } else { $size = $y_size; $value = $x_value; } $value[count($value)] = 0; $carry = 0; for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { $sum = $x_value[$j] * self::$baseFull + $x_value[$i] + $y_value[$j] * self::$baseFull + $y_value[$i] + $carry; $carry = $sum >= self::$maxDigit2; $sum = $carry ? $sum - self::$maxDigit2 : $sum; $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31); $value[$i] = (int) ($sum - self::$baseFull * $temp); $value[$j] = $temp; } if ($j == $size) { $sum = $x_value[$i] + $y_value[$i] + $carry; $carry = $sum >= self::$baseFull; $value[$i] = $carry ? $sum - self::$baseFull : $sum; ++$i; } if ($carry) { for (; $value[$i] == self::$maxDigit; ++$i) { $value[$i] = 0; } ++$value[$i]; } return array( self::VALUE => $this->_trim($value), self::SIGN => $x_negative ); } function subtract($y) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_sub($this->value, $y->value); return $this->_normalize($temp); case self::MODE_BCMATH: $temp = new static(); $temp->value = bcsub($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new static(); $result->value = $temp[self::VALUE]; $result->is_negative = $temp[self::SIGN]; return $this->_normalize($result); } function _subtract($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( self::VALUE => $y_value, self::SIGN => !$y_negative ); } elseif ($y_size == 0) { return array( self::VALUE => $x_value, self::SIGN => $x_negative ); } if ($x_negative != $y_negative) { $temp = $this->_add($x_value, false, $y_value, false); $temp[self::SIGN] = $x_negative; return $temp; } $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); if (!$diff) { return array( self::VALUE => array(), self::SIGN => false ); } if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_negative = !$x_negative; $x_size = count($x_value); $y_size = count($y_value); } $carry = 0; for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { $sum = $x_value[$j] * self::$baseFull + $x_value[$i] - $y_value[$j] * self::$baseFull - $y_value[$i] - $carry; $carry = $sum < 0; $sum = $carry ? $sum + self::$maxDigit2 : $sum; $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31); $x_value[$i] = (int) ($sum - self::$baseFull * $temp); $x_value[$j] = $temp; } if ($j == $y_size) { $sum = $x_value[$i] - $y_value[$i] - $carry; $carry = $sum < 0; $x_value[$i] = $carry ? $sum + self::$baseFull : $sum; ++$i; } if ($carry) { for (; !$x_value[$i]; ++$i) { $x_value[$i] = self::$maxDigit; } --$x_value[$i]; } return array( self::VALUE => $this->_trim($x_value), self::SIGN => $x_negative ); } function multiply($x) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_mul($this->value, $x->value); return $this->_normalize($temp); case self::MODE_BCMATH: $temp = new static(); $temp->value = bcmul($this->value, $x->value, 0); return $this->_normalize($temp); } $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); $product = new static(); $product->value = $temp[self::VALUE]; $product->is_negative = $temp[self::SIGN]; return $this->_normalize($product); } function _multiply($x_value, $x_negative, $y_value, $y_negative) { $x_length = count($x_value); $y_length = count($y_value); if (!$x_length || !$y_length) { return array( self::VALUE => array(), self::SIGN => false ); } return array( self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ? $this->_trim($this->_regularMultiply($x_value, $y_value)) : $this->_trim($this->_karatsuba($x_value, $y_value)), self::SIGN => $x_negative != $y_negative ); } function _regularMultiply($x_value, $y_value) { $x_length = count($x_value); $y_length = count($y_value); if (!$x_length || !$y_length) { return array(); } if ($x_length < $y_length) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); $carry = 0; for ($j = 0; $j < $x_length; ++$j) { $temp = $x_value[$j] * $y_value[0] + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $product_value[$j] = (int) ($temp - self::$baseFull * $carry); } $product_value[$j] = $carry; for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $product_value[$k] = (int) ($temp - self::$baseFull * $carry); } $product_value[$k] = $carry; } return $product_value; } function _karatsuba($x_value, $y_value) { $m = min(count($x_value) >> 1, count($y_value) >> 1); if ($m < self::KARATSUBA_CUTOFF) { return $this->_regularMultiply($x_value, $y_value); } $x1 = array_slice($x_value, $m); $x0 = array_slice($x_value, 0, $m); $y1 = array_slice($y_value, $m); $y0 = array_slice($y_value, 0, $m); $z2 = $this->_karatsuba($x1, $y1); $z0 = $this->_karatsuba($x0, $y0); $z1 = $this->_add($x1, false, $x0, false); $temp = $this->_add($y1, false, $y0, false); $z1 = $this->_karatsuba($z1[self::VALUE], $temp[self::VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); $xy = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]); $xy = $this->_add($xy[self::VALUE], $xy[self::SIGN], $z0, false); return $xy[self::VALUE]; } function _square($x = false) { return count($x) < 2 * self::KARATSUBA_CUTOFF ? $this->_trim($this->_baseSquare($x)) : $this->_trim($this->_karatsubaSquare($x)); } function _baseSquare($value) { if (empty($value)) { return array(); } $square_value = $this->_array_repeat(0, 2 * count($value)); for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { $i2 = $i << 1; $temp = $square_value[$i2] + $value[$i] * $value[$i]; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $square_value[$i2] = (int) ($temp - self::$baseFull * $carry); for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $square_value[$k] = (int) ($temp - self::$baseFull * $carry); } $square_value[$i + $max_index + 1] = $carry; } return $square_value; } function _karatsubaSquare($value) { $m = count($value) >> 1; if ($m < self::KARATSUBA_CUTOFF) { return $this->_baseSquare($value); } $x1 = array_slice($value, $m); $x0 = array_slice($value, 0, $m); $z2 = $this->_karatsubaSquare($x1); $z0 = $this->_karatsubaSquare($x0); $z1 = $this->_add($x1, false, $x0, false); $z1 = $this->_karatsubaSquare($z1[self::VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]); $xx = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]); $xx = $this->_add($xx[self::VALUE], $xx[self::SIGN], $z0, false); return $xx[self::VALUE]; } function divide($y) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $quotient = new static(); $remainder = new static(); list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); if (gmp_sign($remainder->value) < 0) { $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); } return array($this->_normalize($quotient), $this->_normalize($remainder)); case self::MODE_BCMATH: $quotient = new static(); $remainder = new static(); $quotient->value = bcdiv($this->value, $y->value, 0); $remainder->value = bcmod($this->value, $y->value); if ($remainder->value[0] == '-') { $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); } return array($this->_normalize($quotient), $this->_normalize($remainder)); } if (count($y->value) == 1) { list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); $quotient = new static(); $remainder = new static(); $quotient->value = $q; $remainder->value = array($r); $quotient->is_negative = $this->is_negative != $y->is_negative; return array($this->_normalize($quotient), $this->_normalize($remainder)); } static $zero; if (!isset($zero)) { $zero = new static(); } $x = $this->copy(); $y = $y->copy(); $x_sign = $x->is_negative; $y_sign = $y->is_negative; $x->is_negative = $y->is_negative = false; $diff = $x->compare($y); if (!$diff) { $temp = new static(); $temp->value = array(1); $temp->is_negative = $x_sign != $y_sign; return array($this->_normalize($temp), $this->_normalize(new static())); } if ($diff < 0) { if ($x_sign) { $x = $y->subtract($x); } return array($this->_normalize(new static()), $this->_normalize($x)); } $msb = $y->value[count($y->value) - 1]; for ($shift = 0; !($msb & self::$msb); ++$shift) { $msb <<= 1; } $x->_lshift($shift); $y->_lshift($shift); $y_value = &$y->value; $x_max = count($x->value) - 1; $y_max = count($y->value) - 1; $quotient = new static(); $quotient_value = &$quotient->value; $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); static $temp, $lhs, $rhs; if (!isset($temp)) { $temp = new static(); $lhs = new static(); $rhs = new static(); } $temp_value = &$temp->value; $rhs_value = &$rhs->value; $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); while ($x->compare($temp) >= 0) { ++$quotient_value[$x_max - $y_max]; $x = $x->subtract($temp); $x_max = count($x->value) - 1; } for ($i = $x_max; $i >= $y_max + 1; --$i) { $x_value = &$x->value; $x_window = array( isset($x_value[$i]) ? $x_value[$i] : 0, isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 ); $y_window = array( $y_value[$y_max], ($y_max > 0) ? $y_value[$y_max - 1] : 0 ); $q_index = $i - $y_max - 1; if ($x_window[0] == $y_window[0]) { $quotient_value[$q_index] = self::$maxDigit; } else { $quotient_value[$q_index] = $this->_safe_divide( $x_window[0] * self::$baseFull + $x_window[1], $y_window[0] ); } $temp_value = array($y_window[1], $y_window[0]); $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); while ($lhs->compare($rhs) > 0) { --$quotient_value[$q_index]; $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); } $adjust = $this->_array_repeat(0, $q_index); $temp_value = array($quotient_value[$q_index]); $temp = $temp->multiply($y); $temp_value = &$temp->value; if (count($temp_value)) { $temp_value = array_merge($adjust, $temp_value); } $x = $x->subtract($temp); if ($x->compare($zero) < 0) { $temp_value = array_merge($adjust, $y_value); $x = $x->add($temp); --$quotient_value[$q_index]; } $x_max = count($x_value) - 1; } $x->_rshift($shift); $quotient->is_negative = $x_sign != $y_sign; if ($x_sign) { $y->_rshift($shift); $x = $y->subtract($x); } return array($this->_normalize($quotient), $this->_normalize($x)); } function _divide_digit($dividend, $divisor) { $carry = 0; $result = array(); for ($i = count($dividend) - 1; $i >= 0; --$i) { $temp = self::$baseFull * $carry + $dividend[$i]; $result[$i] = $this->_safe_divide($temp, $divisor); $carry = (int) ($temp - $divisor * $result[$i]); } return array($result, $carry); } function modPow($e, $n) { $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); if ($e->compare(new static()) < 0) { $e = $e->abs(); $temp = $this->modInverse($n); if ($temp === false) { return false; } return $this->_normalize($temp->modPow($e, $n)); } if (MATH_BIGINTEGER_MODE == self::MODE_GMP) { $temp = new static(); $temp->value = gmp_powm($this->value, $e->value, $n->value); return $this->_normalize($temp); } if ($this->compare(new static()) < 0 || $this->compare($n) > 0) { list(, $temp) = $this->divide($n); return $temp->modPow($e, $n); } if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { $components = array( 'modulus' => $n->toBytes(true), 'publicExponent' => $e->toBytes(true) ); $components = array( 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) ); $RSAPublicKey = pack( 'Ca*a*a*', 48, $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent'] ); $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); $RSAPublicKey = chr(0) . $RSAPublicKey; $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; $encapsulated = pack( 'Ca*a*', 48, $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey ); $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(base64_encode($encapsulated)) . '-----END PUBLIC KEY-----'; $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { return new static($result, 256); } } if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { $temp = new static(); $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); return $this->_normalize($temp); } if (empty($e->value)) { $temp = new static(); $temp->value = array(1); return $this->_normalize($temp); } if ($e->value == array(1)) { list(, $temp) = $this->divide($n); return $this->_normalize($temp); } if ($e->value == array(2)) { $temp = new static(); $temp->value = $this->_square($this->value); list(, $temp) = $temp->divide($n); return $this->_normalize($temp); } return $this->_normalize($this->_slidingWindow($e, $n, self::BARRETT)); if ($n->value[0] & 1) { return $this->_normalize($this->_slidingWindow($e, $n, self::MONTGOMERY)); } for ($i = 0; $i < count($n->value); ++$i) { if ($n->value[$i]) { $temp = decbin($n->value[$i]); $j = strlen($temp) - strrpos($temp, '1') - 1; $j+= 26 * $i; break; } } $mod1 = $n->copy(); $mod1->_rshift($j); $mod2 = new static(); $mod2->value = array(1); $mod2->_lshift($j); $part1 = ($mod1->value != array(1)) ? $this->_slidingWindow($e, $mod1, self::MONTGOMERY) : new static(); $part2 = $this->_slidingWindow($e, $mod2, self::POWEROF2); $y1 = $mod2->modInverse($mod1); $y2 = $mod1->modInverse($mod2); $result = $part1->multiply($mod2); $result = $result->multiply($y1); $temp = $part2->multiply($mod1); $temp = $temp->multiply($y2); $result = $result->add($temp); list(, $result) = $result->divide($n); return $this->_normalize($result); } function powMod($e, $n) { return $this->modPow($e, $n); } function _slidingWindow($e, $n, $mode) { static $window_ranges = array(7, 25, 81, 241, 673, 1793); $e_value = $e->value; $e_length = count($e_value) - 1; $e_bits = decbin($e_value[$e_length]); for ($i = $e_length - 1; $i >= 0; --$i) { $e_bits.= str_pad(decbin($e_value[$i]), self::$base, '0', STR_PAD_LEFT); } $e_length = strlen($e_bits); for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) { } $n_value = $n->value; $powers = array(); $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); $temp = 1 << ($window_size - 1); for ($i = 1; $i < $temp; ++$i) { $i2 = $i << 1; $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); } $result = array(1); $result = $this->_prepareReduce($result, $n_value, $mode); for ($i = 0; $i < $e_length;) { if (!$e_bits[$i]) { $result = $this->_squareReduce($result, $n_value, $mode); ++$i; } else { for ($j = $window_size - 1; $j > 0; --$j) { if (!empty($e_bits[$i + $j])) { break; } } for ($k = 0; $k <= $j; ++$k) { $result = $this->_squareReduce($result, $n_value, $mode); } $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); $i += $j + 1; } } $temp = new static(); $temp->value = $this->_reduce($result, $n_value, $mode); return $temp; } function _reduce($x, $n, $mode) { switch ($mode) { case self::MONTGOMERY: return $this->_montgomery($x, $n); case self::BARRETT: return $this->_barrett($x, $n); case self::POWEROF2: $lhs = new static(); $lhs->value = $x; $rhs = new static(); $rhs->value = $n; return $x->_mod2($n); case self::CLASSIC: $lhs = new static(); $lhs->value = $x; $rhs = new static(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; case self::NONE: return $x; default: } } function _prepareReduce($x, $n, $mode) { if ($mode == self::MONTGOMERY) { return $this->_prepMontgomery($x, $n); } return $this->_reduce($x, $n, $mode); } function _multiplyReduce($x, $y, $n, $mode) { if ($mode == self::MONTGOMERY) { return $this->_montgomeryMultiply($x, $y, $n); } $temp = $this->_multiply($x, false, $y, false); return $this->_reduce($temp[self::VALUE], $n, $mode); } function _squareReduce($x, $n, $mode) { if ($mode == self::MONTGOMERY) { return $this->_montgomeryMultiply($x, $x, $n); } return $this->_reduce($this->_square($x), $n, $mode); } function _mod2($n) { $temp = new static(); $temp->value = array(1); return $this->bitwise_and($n->subtract($temp)); } function _barrett($n, $m) { static $cache = array( self::VARIABLE => array(), self::DATA => array() ); $m_length = count($m); if (count($n) > 2 * $m_length) { $lhs = new static(); $rhs = new static(); $lhs->value = $n; $rhs->value = $m; list(, $temp) = $lhs->divide($rhs); return $temp->value; } if ($m_length < 5) { return $this->_regularBarrett($n, $m); } if (($key = array_search($m, $cache[self::VARIABLE])) === false) { $key = count($cache[self::VARIABLE]); $cache[self::VARIABLE][] = $m; $lhs = new static(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); $lhs_value[] = 1; $rhs = new static(); $rhs->value = $m; list($u, $m1) = $lhs->divide($rhs); $u = $u->value; $m1 = $m1->value; $cache[self::DATA][] = array( 'u' => $u, 'm1'=> $m1 ); } else { extract($cache[self::DATA][$key]); } $cutoff = $m_length + ($m_length >> 1); $lsd = array_slice($n, 0, $cutoff); $msd = array_slice($n, $cutoff); $lsd = $this->_trim($lsd); $temp = $this->_multiply($msd, false, $m1, false); $n = $this->_add($lsd, false, $temp[self::VALUE], false); if ($m_length & 1) { return $this->_regularBarrett($n[self::VALUE], $m); } $temp = array_slice($n[self::VALUE], $m_length - 1); $temp = $this->_multiply($temp, false, $u, false); $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1); $temp = $this->_multiply($temp, false, $m, false); $result = $this->_subtract($n[self::VALUE], false, $temp[self::VALUE], false); while ($this->_compare($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) { $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $m, false); } return $result[self::VALUE]; } function _regularBarrett($x, $n) { static $cache = array( self::VARIABLE => array(), self::DATA => array() ); $n_length = count($n); if (count($x) > 2 * $n_length) { $lhs = new static(); $rhs = new static(); $lhs->value = $x; $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } if (($key = array_search($n, $cache[self::VARIABLE])) === false) { $key = count($cache[self::VARIABLE]); $cache[self::VARIABLE][] = $n; $lhs = new static(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, 2 * $n_length); $lhs_value[] = 1; $rhs = new static(); $rhs->value = $n; list($temp, ) = $lhs->divide($rhs); $cache[self::DATA][] = $temp->value; } $temp = array_slice($x, $n_length - 1); $temp = $this->_multiply($temp, false, $cache[self::DATA][$key], false); $temp = array_slice($temp[self::VALUE], $n_length + 1); $result = array_slice($x, 0, $n_length + 1); $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); if ($this->_compare($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) { $corrector_value = $this->_array_repeat(0, $n_length + 1); $corrector_value[count($corrector_value)] = 1; $result = $this->_add($result, false, $corrector_value, false); $result = $result[self::VALUE]; } $result = $this->_subtract($result, false, $temp[self::VALUE], $temp[self::SIGN]); while ($this->_compare($result[self::VALUE], $result[self::SIGN], $n, false) > 0) { $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $n, false); } return $result[self::VALUE]; } function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) { $x_length = count($x_value); $y_length = count($y_value); if (!$x_length || !$y_length) { return array( self::VALUE => array(), self::SIGN => false ); } if ($x_length < $y_length) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); $carry = 0; for ($j = 0; $j < $x_length; ++$j) { $temp = $x_value[$j] * $y_value[0] + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $product_value[$j] = (int) ($temp - self::$baseFull * $carry); } if ($j < $stop) { $product_value[$j] = $carry; } for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $product_value[$k] = (int) ($temp - self::$baseFull * $carry); } if ($k < $stop) { $product_value[$k] = $carry; } } return array( self::VALUE => $this->_trim($product_value), self::SIGN => $x_negative != $y_negative ); } function _montgomery($x, $n) { static $cache = array( self::VARIABLE => array(), self::DATA => array() ); if (($key = array_search($n, $cache[self::VARIABLE])) === false) { $key = count($cache[self::VARIABLE]); $cache[self::VARIABLE][] = $x; $cache[self::DATA][] = $this->_modInverse67108864($n); } $k = count($n); $result = array(self::VALUE => $x); for ($i = 0; $i < $k; ++$i) { $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key]; $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); $temp = $this->_regularMultiply(array($temp), $n); $temp = array_merge($this->_array_repeat(0, $i), $temp); $result = $this->_add($result[self::VALUE], false, $temp, false); } $result[self::VALUE] = array_slice($result[self::VALUE], $k); if ($this->_compare($result, false, $n, false) >= 0) { $result = $this->_subtract($result[self::VALUE], false, $n, false); } return $result[self::VALUE]; } function _montgomeryMultiply($x, $y, $m) { $temp = $this->_multiply($x, false, $y, false); return $this->_montgomery($temp[self::VALUE], $m); static $cache = array( self::VARIABLE => array(), self::DATA => array() ); if (($key = array_search($m, $cache[self::VARIABLE])) === false) { $key = count($cache[self::VARIABLE]); $cache[self::VARIABLE][] = $m; $cache[self::DATA][] = $this->_modInverse67108864($m); } $n = max(count($x), count($y), count($m)); $x = array_pad($x, $n, 0); $y = array_pad($y, $n, 0); $m = array_pad($m, $n, 0); $a = array(self::VALUE => $this->_array_repeat(0, $n + 1)); for ($i = 0; $i < $n; ++$i) { $temp = $a[self::VALUE][0] + $x[$i] * $y[0]; $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); $temp = $temp * $cache[self::DATA][$key]; $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); $a = $this->_add($a[self::VALUE], false, $temp[self::VALUE], false); $a[self::VALUE] = array_slice($a[self::VALUE], 1); } if ($this->_compare($a[self::VALUE], false, $m, false) >= 0) { $a = $this->_subtract($a[self::VALUE], false, $m, false); } return $a[self::VALUE]; } function _prepMontgomery($x, $n) { $lhs = new static(); $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); $rhs = new static(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } function _modInverse67108864($x) { $x = -$x[0]; $result = $x & 0x3; $result = ($result * (2 - $x * $result)) & 0xF; $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; $result = fmod($result * (2 - fmod($x * $result, self::$baseFull)), self::$baseFull); return $result & self::$maxDigit; } function modInverse($n) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_invert($this->value, $n->value); return ($temp->value === false) ? false : $this->_normalize($temp); } static $zero, $one; if (!isset($zero)) { $zero = new static(); $one = new static(1); } $n = $n->abs(); if ($this->compare($zero) < 0) { $temp = $this->abs(); $temp = $temp->modInverse($n); return $this->_normalize($n->subtract($temp)); } extract($this->extendedGCD($n)); if (!$gcd->equals($one)) { return false; } $x = $x->compare($zero) < 0 ? $x->add($n) : $x; return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); } function extendedGCD($n) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: extract(gmp_gcdext($this->value, $n->value)); return array( 'gcd' => $this->_normalize(new static($g)), 'x' => $this->_normalize(new static($s)), 'y' => $this->_normalize(new static($t)) ); case self::MODE_BCMATH: $u = $this->value; $v = $n->value; $a = '1'; $b = '0'; $c = '0'; $d = '1'; while (bccomp($v, '0', 0) != 0) { $q = bcdiv($u, $v, 0); $temp = $u; $u = $v; $v = bcsub($temp, bcmul($v, $q, 0), 0); $temp = $a; $a = $c; $c = bcsub($temp, bcmul($a, $q, 0), 0); $temp = $b; $b = $d; $d = bcsub($temp, bcmul($b, $q, 0), 0); } return array( 'gcd' => $this->_normalize(new static($u)), 'x' => $this->_normalize(new static($a)), 'y' => $this->_normalize(new static($b)) ); } $y = $n->copy(); $x = $this->copy(); $g = new static(); $g->value = array(1); while (!(($x->value[0] & 1)|| ($y->value[0] & 1))) { $x->_rshift(1); $y->_rshift(1); $g->_lshift(1); } $u = $x->copy(); $v = $y->copy(); $a = new static(); $b = new static(); $c = new static(); $d = new static(); $a->value = $d->value = $g->value = array(1); $b->value = $c->value = array(); while (!empty($u->value)) { while (!($u->value[0] & 1)) { $u->_rshift(1); if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1))) { $a = $a->add($y); $b = $b->subtract($x); } $a->_rshift(1); $b->_rshift(1); } while (!($v->value[0] & 1)) { $v->_rshift(1); if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1))) { $c = $c->add($y); $d = $d->subtract($x); } $c->_rshift(1); $d->_rshift(1); } if ($u->compare($v) >= 0) { $u = $u->subtract($v); $a = $a->subtract($c); $b = $b->subtract($d); } else { $v = $v->subtract($u); $c = $c->subtract($a); $d = $d->subtract($b); } } return array( 'gcd' => $this->_normalize($g->multiply($v)), 'x' => $this->_normalize($c), 'y' => $this->_normalize($d) ); } function gcd($n) { extract($this->extendedGCD($n)); return $gcd; } function abs() { $temp = new static(); switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp->value = gmp_abs($this->value); break; case self::MODE_BCMATH: $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; break; default: $temp->value = $this->value; } return $temp; } function compare($y) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $r = gmp_cmp($this->value, $y->value); if ($r < -1) { $r = -1; } if ($r > 1) { $r = 1; } return $r; case self::MODE_BCMATH: return bccomp($this->value, $y->value, 0); } return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); } function _compare($x_value, $x_negative, $y_value, $y_negative) { if ($x_negative != $y_negative) { return (!$x_negative && $y_negative) ? 1 : -1; } $result = $x_negative ? -1 : 1; if (count($x_value) != count($y_value)) { return (count($x_value) > count($y_value)) ? $result : -$result; } $size = max(count($x_value), count($y_value)); $x_value = array_pad($x_value, $size, 0); $y_value = array_pad($y_value, $size, 0); for ($i = count($x_value) - 1; $i >= 0; --$i) { if ($x_value[$i] != $y_value[$i]) { return ($x_value[$i] > $y_value[$i]) ? $result : -$result; } } return 0; } function equals($x) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: return gmp_cmp($this->value, $x->value) == 0; default: return $this->value === $x->value && $this->is_negative == $x->is_negative; } } function setPrecision($bits) { $this->precision = $bits; if (MATH_BIGINTEGER_MODE != self::MODE_BCMATH) { $this->bitmask = new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); } else { $this->bitmask = new static(bcpow('2', $bits, 0)); } $temp = $this->_normalize($this); $this->value = $temp->value; } function bitwise_and($x) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_and($this->value, $x->value); return $this->_normalize($temp); case self::MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new static($left & $right, 256)); } $result = $this->copy(); $length = min(count($x->value), count($this->value)); $result->value = array_slice($result->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i]&= $x->value[$i]; } return $this->_normalize($result); } function bitwise_or($x) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_or($this->value, $x->value); return $this->_normalize($temp); case self::MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new static($left | $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->value = array_pad($result->value, $length, 0); $x->value = array_pad($x->value, $length, 0); for ($i = 0; $i < $length; ++$i) { $result->value[$i]|= $x->value[$i]; } return $this->_normalize($result); } function bitwise_xor($x) { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); $temp->value = gmp_xor(gmp_abs($this->value), gmp_abs($x->value)); return $this->_normalize($temp); case self::MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new static($left ^ $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->is_negative = false; $result->value = array_pad($result->value, $length, 0); $x->value = array_pad($x->value, $length, 0); for ($i = 0; $i < $length; ++$i) { $result->value[$i]^= $x->value[$i]; } return $this->_normalize($result); } function bitwise_not() { $temp = $this->toBytes(); if ($temp == '') { return $this->_normalize(new static()); } $pre_msb = decbin(ord($temp[0])); $temp = ~$temp; $msb = decbin(ord($temp[0])); if (strlen($msb) == 8) { $msb = substr($msb, strpos($msb, '0')); } $temp[0] = chr(bindec($msb)); $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; $new_bits = $this->precision - $current_bits; if ($new_bits <= 0) { return $this->_normalize(new static($temp, 256)); } $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); $this->_base256_lshift($leading_ones, $current_bits); $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT); return $this->_normalize(new static($leading_ones | $temp, 256)); } function bitwise_rightShift($shift) { $temp = new static(); switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); break; case self::MODE_BCMATH: $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); break; default: $temp->value = $this->value; $temp->_rshift($shift); } return $this->_normalize($temp); } function bitwise_leftShift($shift) { $temp = new static(); switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); break; case self::MODE_BCMATH: $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); break; default: $temp->value = $this->value; $temp->_lshift($shift); } return $this->_normalize($temp); } function bitwise_leftRotate($shift) { $bits = $this->toBytes(); if ($this->precision > 0) { $precision = $this->precision; if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { $mask = $this->bitmask->subtract(new static(1)); $mask = $mask->toBytes(); } else { $mask = $this->bitmask->toBytes(); } } else { $temp = ord($bits[0]); for ($i = 0; $temp >> $i; ++$i) { } $precision = 8 * strlen($bits) - 8 + $i; $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); } if ($shift < 0) { $shift+= $precision; } $shift%= $precision; if (!$shift) { return $this->copy(); } $left = $this->bitwise_leftShift($shift); $left = $left->bitwise_and(new static($mask, 256)); $right = $this->bitwise_rightShift($precision - $shift); $result = MATH_BIGINTEGER_MODE != self::MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); return $this->_normalize($result); } function bitwise_rightRotate($shift) { return $this->bitwise_leftRotate(-$shift); } function _random_number_helper($size) { if (class_exists('\phpseclib\Crypt\Random')) { $random = Random::string($size); } else { $random = ''; if ($size & 1) { $random.= chr(mt_rand(0, 255)); } $blocks = $size >> 1; for ($i = 0; $i < $blocks; ++$i) { $random.= pack('n', mt_rand(0, 0xFFFF)); } } return new static($random, 256); } function random($arg1, $arg2 = false) { if ($arg1 === false) { return false; } if ($arg2 === false) { $max = $arg1; $min = $this; } else { $min = $arg1; $max = $arg2; } $compare = $max->compare($min); if (!$compare) { return $this->_normalize($min); } elseif ($compare < 0) { $temp = $max; $max = $min; $min = $temp; } static $one; if (!isset($one)) { $one = new static(1); } $max = $max->subtract($min->subtract($one)); $size = strlen(ltrim($max->toBytes(), chr(0))); $random_max = new static(chr(1) . str_repeat("\0", $size), 256); $random = $this->_random_number_helper($size); list($max_multiple) = $random_max->divide($max); $max_multiple = $max_multiple->multiply($max); while ($random->compare($max_multiple) >= 0) { $random = $random->subtract($max_multiple); $random_max = $random_max->subtract($max_multiple); $random = $random->bitwise_leftShift(8); $random = $random->add($this->_random_number_helper(1)); $random_max = $random_max->bitwise_leftShift(8); list($max_multiple) = $random_max->divide($max); $max_multiple = $max_multiple->multiply($max); } list(, $random) = $random->divide($max); return $this->_normalize($random->add($min)); } function randomPrime($arg1, $arg2 = false, $timeout = false) { if ($arg1 === false) { return false; } if ($arg2 === false) { $max = $arg1; $min = $this; } else { $min = $arg1; $max = $arg2; } $compare = $max->compare($min); if (!$compare) { return $min->isPrime() ? $min : false; } elseif ($compare < 0) { $temp = $max; $max = $min; $min = $temp; } static $one, $two; if (!isset($one)) { $one = new static(1); $two = new static(2); } $start = time(); $x = $this->random($min, $max); if (MATH_BIGINTEGER_MODE == self::MODE_GMP && extension_loaded('gmp')) { $p = new static(); $p->value = gmp_nextprime($x->value); if ($p->compare($max) <= 0) { return $p; } if (!$min->equals($x)) { $x = $x->subtract($one); } return $x->randomPrime($min, $x); } if ($x->equals($two)) { return $x; } $x->_make_odd(); if ($x->compare($max) > 0) { if ($min->equals($max)) { return false; } $x = $min->copy(); $x->_make_odd(); } $initial_x = $x->copy(); while (true) { if ($timeout !== false && time() - $start > $timeout) { return false; } if ($x->isPrime()) { return $x; } $x = $x->add($two); if ($x->compare($max) > 0) { $x = $min->copy(); if ($x->equals($two)) { return $x; } $x->_make_odd(); } if ($x->equals($initial_x)) { return false; } } } function _make_odd() { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: gmp_setbit($this->value, 0); break; case self::MODE_BCMATH: if ($this->value[strlen($this->value) - 1] % 2 == 0) { $this->value = bcadd($this->value, '1'); } break; default: $this->value[0] |= 1; } } function isPrime($t = false) { $length = strlen($this->toBytes()); if (!$t) { if ($length >= 163) { $t = 2; } else if ($length >= 106) { $t = 3; } else if ($length >= 81 ) { $t = 4; } else if ($length >= 68 ) { $t = 5; } else if ($length >= 56 ) { $t = 6; } else if ($length >= 50 ) { $t = 7; } else if ($length >= 43 ) { $t = 8; } else if ($length >= 37 ) { $t = 9; } else if ($length >= 31 ) { $t = 12; } else if ($length >= 25 ) { $t = 15; } else if ($length >= 18 ) { $t = 18; } else { $t = 27; } } switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: return gmp_prob_prime($this->value, $t) != 0; case self::MODE_BCMATH: if ($this->value === '2') { return true; } if ($this->value[strlen($this->value) - 1] % 2 == 0) { return false; } break; default: if ($this->value == array(2)) { return true; } if (~$this->value[0] & 1) { return false; } } static $primes, $zero, $one, $two; if (!isset($primes)) { $primes = array( 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997 ); if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { for ($i = 0; $i < count($primes); ++$i) { $primes[$i] = new static($primes[$i]); } } $zero = new static(); $one = new static(1); $two = new static(2); } if ($this->equals($one)) { return false; } if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) { foreach ($primes as $prime) { list(, $r) = $this->divide($prime); if ($r->equals($zero)) { return $this->equals($prime); } } } else { $value = $this->value; foreach ($primes as $prime) { list(, $r) = $this->_divide_digit($value, $prime); if (!$r) { return count($value) == 1 && $value[0] == $prime; } } } $n = $this->copy(); $n_1 = $n->subtract($one); $n_2 = $n->subtract($two); $r = $n_1->copy(); $r_value = $r->value; if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { $s = 0; while ($r->value[strlen($r->value) - 1] % 2 == 0) { $r->value = bcdiv($r->value, '2', 0); ++$s; } } else { for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { $temp = ~$r_value[$i] & 0xFFFFFF; for ($j = 1; ($temp >> $j) & 1; ++$j) { } if ($j != 25) { break; } } $s = 26 * $i + $j; $r->_rshift($s); } for ($i = 0; $i < $t; ++$i) { $a = $this->random($two, $n_2); $y = $a->modPow($r, $n); if (!$y->equals($one) && !$y->equals($n_1)) { for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { $y = $y->modPow($two, $n); if ($y->equals($one)) { return false; } } if (!$y->equals($n_1)) { return false; } } } return true; } function _lshift($shift) { if ($shift == 0) { return; } $num_digits = (int) ($shift / self::$base); $shift %= self::$base; $shift = 1 << $shift; $carry = 0; for ($i = 0; $i < count($this->value); ++$i) { $temp = $this->value[$i] * $shift + $carry; $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31); $this->value[$i] = (int) ($temp - $carry * self::$baseFull); } if ($carry) { $this->value[count($this->value)] = $carry; } while ($num_digits--) { array_unshift($this->value, 0); } } function _rshift($shift) { if ($shift == 0) { return; } $num_digits = (int) ($shift / self::$base); $shift %= self::$base; $carry_shift = self::$base - $shift; $carry_mask = (1 << $shift) - 1; if ($num_digits) { $this->value = array_slice($this->value, $num_digits); } $carry = 0; for ($i = count($this->value) - 1; $i >= 0; --$i) { $temp = $this->value[$i] >> $shift | $carry; $carry = ($this->value[$i] & $carry_mask) << $carry_shift; $this->value[$i] = $temp; } $this->value = $this->_trim($this->value); } function _normalize($result) { $result->precision = $this->precision; $result->bitmask = $this->bitmask; switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: if ($this->bitmask !== false) { $flip = gmp_cmp($result->value, gmp_init(0)) < 0; if ($flip) { $result->value = gmp_neg($result->value); } $result->value = gmp_and($result->value, $result->bitmask->value); if ($flip) { $result->value = gmp_neg($result->value); } } return $result; case self::MODE_BCMATH: if (!empty($result->bitmask->value)) { $result->value = bcmod($result->value, $result->bitmask->value); } return $result; } $value = &$result->value; if (!count($value)) { $result->is_negative = false; return $result; } $value = $this->_trim($value); if (!empty($result->bitmask->value)) { $length = min(count($value), count($this->bitmask->value)); $value = array_slice($value, 0, $length); for ($i = 0; $i < $length; ++$i) { $value[$i] = $value[$i] & $this->bitmask->value[$i]; } } return $result; } function _trim($value) { for ($i = count($value) - 1; $i >= 0; --$i) { if ($value[$i]) { break; } unset($value[$i]); } return $value; } function _array_repeat($input, $multiplier) { return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); } function _base256_lshift(&$x, $shift) { if ($shift == 0) { return; } $num_bytes = $shift >> 3; $shift &= 7; $carry = 0; for ($i = strlen($x) - 1; $i >= 0; --$i) { $temp = ord($x[$i]) << $shift | $carry; $x[$i] = chr($temp); $carry = $temp >> 8; } $carry = ($carry != 0) ? chr($carry) : ''; $x = $carry . $x . str_repeat(chr(0), $num_bytes); } function _base256_rshift(&$x, $shift) { if ($shift == 0) { $x = ltrim($x, chr(0)); return ''; } $num_bytes = $shift >> 3; $shift &= 7; $remainder = ''; if ($num_bytes) { $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; $remainder = substr($x, $start); $x = substr($x, 0, -$num_bytes); } $carry = 0; $carry_shift = 8 - $shift; for ($i = 0; $i < strlen($x); ++$i) { $temp = (ord($x[$i]) >> $shift) | $carry; $carry = (ord($x[$i]) << $carry_shift) & 0xFF; $x[$i] = chr($temp); } $x = ltrim($x, chr(0)); $remainder = chr($carry >> $carry_shift) . $remainder; return ltrim($remainder, chr(0)); } function _int2bytes($x) { return ltrim(pack('N', $x), chr(0)); } function _bytes2int($x) { $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); return $temp['int']; } function _encodeASN1Length($length) { if ($length <= 0x7F) { return chr($length); } $temp = ltrim(pack('N', $length), chr(0)); return pack('Ca*', 0x80 | strlen($temp), $temp); } function _safe_divide($x, $y) { if (self::$base === 26) { return (int) ($x / $y); } return ($x - ($x % $y)) / $y; } } current_key_length != 128 || strlen($this->orig_key) < 16) { return false; } $this->cipher_name_openssl_ecb = 'rc2-ecb'; $this->cipher_name_openssl = 'rc2-' . $this->_openssl_translate_mode(); } return parent::isValidEngine($engine); } function setKeyLength($length) { if ($length < 8) { $this->default_key_length = 1; } elseif ($length > 1024) { $this->default_key_length = 128; } else { $this->default_key_length = $length; } $this->current_key_length = $this->default_key_length; parent::setKeyLength($length); } function getKeyLength() { return $this->current_key_length; } function setKey($key, $t1 = 0) { $this->orig_key = $key; if ($t1 <= 0) { $t1 = $this->default_key_length; } elseif ($t1 > 1024) { $t1 = 1024; } $this->current_key_length = $t1; $key = strlen($key) ? substr($key, 0, 128) : "\x00"; $t = strlen($key); $l = array_values(unpack('C*', $key)); $t8 = ($t1 + 7) >> 3; $tm = 0xFF >> (8 * $t8 - $t1); $pitable = $this->pitable; for ($i = $t; $i < 128; $i++) { $l[$i] = $pitable[$l[$i - 1] + $l[$i - $t]]; } $i = 128 - $t8; $l[$i] = $pitable[$l[$i] & $tm]; while ($i--) { $l[$i] = $pitable[$l[$i + 1] ^ $l[$i + $t8]]; } $l[0] = $this->invpitable[$l[0]]; array_unshift($l, 'C*'); parent::setKey(call_user_func_array('pack', $l)); } function encrypt($plaintext) { if ($this->engine == self::ENGINE_OPENSSL) { $temp = $this->key; $this->key = $this->orig_key; $result = parent::encrypt($plaintext); $this->key = $temp; return $result; } return parent::encrypt($plaintext); } function decrypt($ciphertext) { if ($this->engine == self::ENGINE_OPENSSL) { $temp = $this->key; $this->key = $this->orig_key; $result = parent::decrypt($ciphertext); $this->key = $temp; return $result; } return parent::decrypt($ciphertext); } function _encryptBlock($in) { list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in)); $keys = $this->keys; $limit = 20; $actions = array($limit => 44, 44 => 64); $j = 0; for (;;) { $r0 = (($r0 + $keys[$j++] + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1; $r0 |= $r0 >> 16; $r1 = (($r1 + $keys[$j++] + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2; $r1 |= $r1 >> 16; $r2 = (($r2 + $keys[$j++] + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3; $r2 |= $r2 >> 16; $r3 = (($r3 + $keys[$j++] + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5; $r3 |= $r3 >> 16; if ($j === $limit) { if ($limit === 64) { break; } $r0 += $keys[$r3 & 0x3F]; $r1 += $keys[$r0 & 0x3F]; $r2 += $keys[$r1 & 0x3F]; $r3 += $keys[$r2 & 0x3F]; $limit = $actions[$limit]; } } return pack('vvvv', $r0, $r1, $r2, $r3); } function _decryptBlock($in) { list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in)); $keys = $this->keys; $limit = 44; $actions = array($limit => 20, 20 => 0); $j = 64; for (;;) { $r3 = ($r3 | ($r3 << 16)) >> 5; $r3 = ($r3 - $keys[--$j] - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF; $r2 = ($r2 | ($r2 << 16)) >> 3; $r2 = ($r2 - $keys[--$j] - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF; $r1 = ($r1 | ($r1 << 16)) >> 2; $r1 = ($r1 - $keys[--$j] - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF; $r0 = ($r0 | ($r0 << 16)) >> 1; $r0 = ($r0 - $keys[--$j] - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF; if ($j === $limit) { if ($limit === 0) { break; } $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF; $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF; $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF; $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF; $limit = $actions[$limit]; } } return pack('vvvv', $r0, $r1, $r2, $r3); } function _setupMcrypt() { if (!isset($this->key)) { $this->setKey(''); } parent::_setupMcrypt(); } function _setupKey() { if (!isset($this->key)) { $this->setKey(''); } $l = unpack('Ca/Cb/v*', $this->key); array_unshift($l, $this->pitable[$l['a']] | ($l['b'] << 8)); unset($l['a']); unset($l['b']); $this->keys = $l; } function _setupInlineCrypt() { $lambda_functions =& self::_getLambdaFunctions(); $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); $code_hash = "Crypt_RC2, {$this->mode}"; if ($gen_hi_opt_code) { $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } if (!isset($lambda_functions[$code_hash])) { $init_crypt = '$keys = $self->keys;'; switch (true) { case $gen_hi_opt_code: $keys = $this->keys; default: $keys = array(); foreach ($this->keys as $k => $v) { $keys[$k] = '$keys[' . $k . ']'; } } $encrypt_block = $decrypt_block = ' $in = unpack("v4", $in); $r0 = $in[1]; $r1 = $in[2]; $r2 = $in[3]; $r3 = $in[4]; '; $limit = 20; $actions = array($limit => 44, 44 => 64); $j = 0; for (;;) { $encrypt_block .= ' $r0 = (($r0 + ' . $keys[$j++] . ' + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1; $r0 |= $r0 >> 16; $r1 = (($r1 + ' . $keys[$j++] . ' + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2; $r1 |= $r1 >> 16; $r2 = (($r2 + ' . $keys[$j++] . ' + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3; $r2 |= $r2 >> 16; $r3 = (($r3 + ' . $keys[$j++] . ' + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5; $r3 |= $r3 >> 16;'; if ($j === $limit) { if ($limit === 64) { break; } $encrypt_block .= ' $r0 += $keys[$r3 & 0x3F]; $r1 += $keys[$r0 & 0x3F]; $r2 += $keys[$r1 & 0x3F]; $r3 += $keys[$r2 & 0x3F];'; $limit = $actions[$limit]; } } $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; $limit = 44; $actions = array($limit => 20, 20 => 0); $j = 64; for (;;) { $decrypt_block .= ' $r3 = ($r3 | ($r3 << 16)) >> 5; $r3 = ($r3 - ' . $keys[--$j] . ' - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF; $r2 = ($r2 | ($r2 << 16)) >> 3; $r2 = ($r2 - ' . $keys[--$j] . ' - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF; $r1 = ($r1 | ($r1 << 16)) >> 2; $r1 = ($r1 - ' . $keys[--$j] . ' - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF; $r0 = ($r0 | ($r0 << 16)) >> 1; $r0 = ($r0 - ' . $keys[--$j] . ' - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;'; if ($j === $limit) { if ($limit === 0) { break; } $decrypt_block .= ' $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF; $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF; $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF; $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;'; $limit = $actions[$limit]; } } $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);'; $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( array( 'init_crypt' => $init_crypt, 'encrypt_block' => $encrypt_block, 'decrypt_block' => $decrypt_block ) ); } $this->inline_crypt = $lambda_functions[$code_hash]; } } =')) { try { return \random_bytes($length); } catch (\Throwable $e) { } } if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { if (extension_loaded('mcrypt') && function_exists('class_alias')) { return @mcrypt_create_iv($length); } if (extension_loaded('openssl') && version_compare(PHP_VERSION, '5.3.4', '>=')) { return openssl_random_pseudo_bytes($length); } } else { if (extension_loaded('openssl')) { return openssl_random_pseudo_bytes($length); } static $fp = true; if ($fp === true) { $fp = @fopen('/dev/urandom', 'rb'); } if ($fp !== true && $fp !== false) { $temp = fread($fp, $length); if (strlen($temp) == $length) { return $temp; } } if (extension_loaded('mcrypt')) { return @mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); } } static $crypto = false, $v; if ($crypto === false) { $old_session_id = session_id(); $old_use_cookies = ini_get('session.use_cookies'); $old_session_cache_limiter = session_cache_limiter(); $_OLD_SESSION = isset($_SESSION) ? $_SESSION : false; if ($old_session_id != '') { session_write_close(); } session_id(1); ini_set('session.use_cookies', 0); session_cache_limiter(''); session_start(); $v = $seed = $_SESSION['seed'] = pack('H*', sha1( (isset($_SERVER) ? phpseclib_safe_serialize($_SERVER) : '') . (isset($_POST) ? phpseclib_safe_serialize($_POST) : '') . (isset($_GET) ? phpseclib_safe_serialize($_GET) : '') . (isset($_COOKIE) ? phpseclib_safe_serialize($_COOKIE) : '') . phpseclib_safe_serialize($GLOBALS) . phpseclib_safe_serialize($_SESSION) . phpseclib_safe_serialize($_OLD_SESSION) )); if (!isset($_SESSION['count'])) { $_SESSION['count'] = 0; } $_SESSION['count']++; session_write_close(); if ($old_session_id != '') { session_id($old_session_id); session_start(); ini_set('session.use_cookies', $old_use_cookies); session_cache_limiter($old_session_cache_limiter); } else { if ($_OLD_SESSION !== false) { $_SESSION = $_OLD_SESSION; unset($_OLD_SESSION); } else { unset($_SESSION); } } $key = pack('H*', sha1($seed . 'A')); $iv = pack('H*', sha1($seed . 'C')); switch (true) { case class_exists('\phpseclib\Crypt\AES'): $crypto = new AES(Base::MODE_CTR); break; case class_exists('\phpseclib\Crypt\Twofish'): $crypto = new Twofish(Base::MODE_CTR); break; case class_exists('\phpseclib\Crypt\Blowfish'): $crypto = new Blowfish(Base::MODE_CTR); break; case class_exists('\phpseclib\Crypt\TripleDES'): $crypto = new TripleDES(Base::MODE_CTR); break; case class_exists('\phpseclib\Crypt\DES'): $crypto = new DES(Base::MODE_CTR); break; case class_exists('\phpseclib\Crypt\RC4'): $crypto = new RC4(); break; default: user_error(__CLASS__ . ' requires at least one symmetric cipher be loaded'); return false; } $crypto->setKey($key); $crypto->setIV($iv); $crypto->enableContinuousBuffer(); } $result = ''; while (strlen($result) < $length) { $i = $crypto->encrypt(microtime()); $r = $crypto->encrypt($i ^ $v); $v = $crypto->encrypt($r ^ $i); $result.= $r; } return substr($result, 0, $length); } } if (!function_exists('phpseclib_safe_serialize')) { function phpseclib_safe_serialize(&$arr) { if (is_object($arr)) { return ''; } if (!is_array($arr)) { return serialize($arr); } if (isset($arr['__phpseclib_marker'])) { return ''; } $safearr = array(); $arr['__phpseclib_marker'] = true; foreach (array_keys($arr) as $key) { if ($key !== '__phpseclib_marker') { $safearr[$key] = phpseclib_safe_serialize($arr[$key]); } } unset($arr['__phpseclib_marker']); return serialize($safearr); } } key_length = 16; break; case $length <= 160: $this->key_length = 20; break; case $length <= 192: $this->key_length = 24; break; case $length <= 224: $this->key_length = 28; break; default: $this->key_length = 32; } parent::setKeyLength($length); } function setBlockLength($length) { $length >>= 5; if ($length > 8) { $length = 8; } elseif ($length < 4) { $length = 4; } $this->Nb = $length; $this->block_size = $length << 2; $this->changed = true; $this->_setEngine(); } function isValidEngine($engine) { switch ($engine) { case self::ENGINE_OPENSSL: if ($this->block_size != 16) { return false; } $this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb'; $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->_openssl_translate_mode(); break; case self::ENGINE_MCRYPT: $this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3); if ($this->key_length % 8) { return false; } } return parent::isValidEngine($engine); } function _encryptBlock($in) { static $tables; if (empty($tables)) { $tables = &$this->_getTables(); } $t0 = $tables[0]; $t1 = $tables[1]; $t2 = $tables[2]; $t3 = $tables[3]; $sbox = $tables[4]; $state = array(); $words = unpack('N*', $in); $c = $this->c; $w = $this->w; $Nb = $this->Nb; $Nr = $this->Nr; $wc = $Nb - 1; foreach ($words as $word) { $state[] = $word ^ $w[++$wc]; } $temp = array(); for ($round = 1; $round < $Nr; ++$round) { $i = 0; $j = $c[1]; $k = $c[2]; $l = $c[3]; while ($i < $Nb) { $temp[$i] = $t0[$state[$i] >> 24 & 0x000000FF] ^ $t1[$state[$j] >> 16 & 0x000000FF] ^ $t2[$state[$k] >> 8 & 0x000000FF] ^ $t3[$state[$l] & 0x000000FF] ^ $w[++$wc]; ++$i; $j = ($j + 1) % $Nb; $k = ($k + 1) % $Nb; $l = ($l + 1) % $Nb; } $state = $temp; } for ($i = 0; $i < $Nb; ++$i) { $state[$i] = $sbox[$state[$i] & 0x000000FF] | ($sbox[$state[$i] >> 8 & 0x000000FF] << 8) | ($sbox[$state[$i] >> 16 & 0x000000FF] << 16) | ($sbox[$state[$i] >> 24 & 0x000000FF] << 24); } $i = 0; $j = $c[1]; $k = $c[2]; $l = $c[3]; while ($i < $Nb) { $temp[$i] = ($state[$i] & 0xFF000000) ^ ($state[$j] & 0x00FF0000) ^ ($state[$k] & 0x0000FF00) ^ ($state[$l] & 0x000000FF) ^ $w[$i]; ++$i; $j = ($j + 1) % $Nb; $k = ($k + 1) % $Nb; $l = ($l + 1) % $Nb; } switch ($Nb) { case 8: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); case 7: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); case 6: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); case 5: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); default: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); } } function _decryptBlock($in) { static $invtables; if (empty($invtables)) { $invtables = &$this->_getInvTables(); } $dt0 = $invtables[0]; $dt1 = $invtables[1]; $dt2 = $invtables[2]; $dt3 = $invtables[3]; $isbox = $invtables[4]; $state = array(); $words = unpack('N*', $in); $c = $this->c; $dw = $this->dw; $Nb = $this->Nb; $Nr = $this->Nr; $wc = $Nb - 1; foreach ($words as $word) { $state[] = $word ^ $dw[++$wc]; } $temp = array(); for ($round = $Nr - 1; $round > 0; --$round) { $i = 0; $j = $Nb - $c[1]; $k = $Nb - $c[2]; $l = $Nb - $c[3]; while ($i < $Nb) { $temp[$i] = $dt0[$state[$i] >> 24 & 0x000000FF] ^ $dt1[$state[$j] >> 16 & 0x000000FF] ^ $dt2[$state[$k] >> 8 & 0x000000FF] ^ $dt3[$state[$l] & 0x000000FF] ^ $dw[++$wc]; ++$i; $j = ($j + 1) % $Nb; $k = ($k + 1) % $Nb; $l = ($l + 1) % $Nb; } $state = $temp; } $i = 0; $j = $Nb - $c[1]; $k = $Nb - $c[2]; $l = $Nb - $c[3]; while ($i < $Nb) { $word = ($state[$i] & 0xFF000000) | ($state[$j] & 0x00FF0000) | ($state[$k] & 0x0000FF00) | ($state[$l] & 0x000000FF); $temp[$i] = $dw[$i] ^ ($isbox[$word & 0x000000FF] | ($isbox[$word >> 8 & 0x000000FF] << 8) | ($isbox[$word >> 16 & 0x000000FF] << 16) | ($isbox[$word >> 24 & 0x000000FF] << 24)); ++$i; $j = ($j + 1) % $Nb; $k = ($k + 1) % $Nb; $l = ($l + 1) % $Nb; } switch ($Nb) { case 8: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); case 7: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); case 6: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); case 5: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); default: return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); } } function _setupKey() { static $rcon = array(0, 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 ); if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_length === $this->kl['key_length'] && $this->block_size === $this->kl['block_size']) { return; } $this->kl = array('key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size); $this->Nk = $this->key_length >> 2; $this->Nr = max($this->Nk, $this->Nb) + 6; switch ($this->Nb) { case 4: case 5: case 6: $this->c = array(0, 1, 2, 3); break; case 7: $this->c = array(0, 1, 2, 4); break; case 8: $this->c = array(0, 1, 3, 4); } $w = array_values(unpack('N*words', $this->key)); $length = $this->Nb * ($this->Nr + 1); for ($i = $this->Nk; $i < $length; $i++) { $temp = $w[$i - 1]; if ($i % $this->Nk == 0) { $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; } elseif ($this->Nk > 6 && $i % $this->Nk == 4) { $temp = $this->_subWord($temp); } $w[$i] = $w[$i - $this->Nk] ^ $temp; } list($dt0, $dt1, $dt2, $dt3) = $this->_getInvTables(); $temp = $this->w = $this->dw = array(); for ($i = $row = $col = 0; $i < $length; $i++, $col++) { if ($col == $this->Nb) { if ($row == 0) { $this->dw[0] = $this->w[0]; } else { $j = 0; while ($j < $this->Nb) { $dw = $this->_subWord($this->w[$row][$j]); $temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^ $dt1[$dw >> 16 & 0x000000FF] ^ $dt2[$dw >> 8 & 0x000000FF] ^ $dt3[$dw & 0x000000FF]; $j++; } $this->dw[$row] = $temp; } $col = 0; $row++; } $this->w[$row][$col] = $w[$i]; } $this->dw[$row] = $this->w[$row]; $this->dw = array_reverse($this->dw); $w = array_pop($this->w); $dw = array_pop($this->dw); foreach ($this->w as $r => $wr) { foreach ($wr as $c => $wc) { $w[] = $wc; $dw[] = $this->dw[$r][$c]; } } $this->w = $w; $this->dw = $dw; } function _subWord($word) { static $sbox; if (empty($sbox)) { list(, , , , $sbox) = $this->_getTables(); } return $sbox[$word & 0x000000FF] | ($sbox[$word >> 8 & 0x000000FF] << 8) | ($sbox[$word >> 16 & 0x000000FF] << 16) | ($sbox[$word >> 24 & 0x000000FF] << 24); } function &_getTables() { static $tables; if (empty($tables)) { $t3 = array_map('intval', array( 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C )); foreach ($t3 as $t3i) { $t0[] = (($t3i << 24) & 0xFF000000) | (($t3i >> 8) & 0x00FFFFFF); $t1[] = (($t3i << 16) & 0xFFFF0000) | (($t3i >> 16) & 0x0000FFFF); $t2[] = (($t3i << 8) & 0xFFFFFF00) | (($t3i >> 24) & 0x000000FF); } $tables = array( $t0, $t1, $t2, $t3, array( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 ) ); } return $tables; } function &_getInvTables() { static $tables; if (empty($tables)) { $dt3 = array_map('intval', array( 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 )); foreach ($dt3 as $dt3i) { $dt0[] = (($dt3i << 24) & 0xFF000000) | (($dt3i >> 8) & 0x00FFFFFF); $dt1[] = (($dt3i << 16) & 0xFFFF0000) | (($dt3i >> 16) & 0x0000FFFF); $dt2[] = (($dt3i << 8) & 0xFFFFFF00) | (($dt3i >> 24) & 0x000000FF); }; $tables = array( $dt0, $dt1, $dt2, $dt3, array( 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D ) ); } return $tables; } function _setupInlineCrypt() { $lambda_functions =& self::_getLambdaFunctions(); $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); $code_hash = "Crypt_Rijndael, {$this->mode}, {$this->Nr}, {$this->Nb}"; if ($gen_hi_opt_code) { $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } if (!isset($lambda_functions[$code_hash])) { switch (true) { case $gen_hi_opt_code: $w = $this->w; $dw = $this->dw; $init_encrypt = ''; $init_decrypt = ''; break; default: for ($i = 0, $cw = count($this->w); $i < $cw; ++$i) { $w[] = '$w[' . $i . ']'; $dw[] = '$dw[' . $i . ']'; } $init_encrypt = '$w = $self->w;'; $init_decrypt = '$dw = $self->dw;'; } $Nr = $this->Nr; $Nb = $this->Nb; $c = $this->c; $init_encrypt.= ' static $tables; if (empty($tables)) { $tables = &$self->_getTables(); } $t0 = $tables[0]; $t1 = $tables[1]; $t2 = $tables[2]; $t3 = $tables[3]; $sbox = $tables[4]; '; $s = 'e'; $e = 's'; $wc = $Nb - 1; $encrypt_block = '$in = unpack("N*", $in);'."\n"; for ($i = 0; $i < $Nb; ++$i) { $encrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$w[++$wc].";\n"; } for ($round = 1; $round < $Nr; ++$round) { list($s, $e) = array($e, $s); for ($i = 0; $i < $Nb; ++$i) { $encrypt_block.= '$'.$e.$i.' = $t0[($'.$s.$i .' >> 24) & 0xff] ^ $t1[($'.$s.(($i + $c[1]) % $Nb).' >> 16) & 0xff] ^ $t2[($'.$s.(($i + $c[2]) % $Nb).' >> 8) & 0xff] ^ $t3[ $'.$s.(($i + $c[3]) % $Nb).' & 0xff] ^ '.$w[++$wc].";\n"; } } for ($i = 0; $i < $Nb; ++$i) { $encrypt_block.= '$'.$e.$i.' = $sbox[ $'.$e.$i.' & 0xff] | ($sbox[($'.$e.$i.' >> 8) & 0xff] << 8) | ($sbox[($'.$e.$i.' >> 16) & 0xff] << 16) | ($sbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; } $encrypt_block .= '$in = pack("N*"'."\n"; for ($i = 0; $i < $Nb; ++$i) { $encrypt_block.= ', ($'.$e.$i .' & '.((int)0xFF000000).') ^ ($'.$e.(($i + $c[1]) % $Nb).' & 0x00FF0000 ) ^ ($'.$e.(($i + $c[2]) % $Nb).' & 0x0000FF00 ) ^ ($'.$e.(($i + $c[3]) % $Nb).' & 0x000000FF ) ^ '.$w[$i]."\n"; } $encrypt_block .= ');'; $init_decrypt.= ' static $invtables; if (empty($invtables)) { $invtables = &$self->_getInvTables(); } $dt0 = $invtables[0]; $dt1 = $invtables[1]; $dt2 = $invtables[2]; $dt3 = $invtables[3]; $isbox = $invtables[4]; '; $s = 'e'; $e = 's'; $wc = $Nb - 1; $decrypt_block = '$in = unpack("N*", $in);'."\n"; for ($i = 0; $i < $Nb; ++$i) { $decrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$dw[++$wc].';'."\n"; } for ($round = 1; $round < $Nr; ++$round) { list($s, $e) = array($e, $s); for ($i = 0; $i < $Nb; ++$i) { $decrypt_block.= '$'.$e.$i.' = $dt0[($'.$s.$i .' >> 24) & 0xff] ^ $dt1[($'.$s.(($Nb + $i - $c[1]) % $Nb).' >> 16) & 0xff] ^ $dt2[($'.$s.(($Nb + $i - $c[2]) % $Nb).' >> 8) & 0xff] ^ $dt3[ $'.$s.(($Nb + $i - $c[3]) % $Nb).' & 0xff] ^ '.$dw[++$wc].";\n"; } } for ($i = 0; $i < $Nb; ++$i) { $decrypt_block.= '$'.$e.$i.' = $isbox[ $'.$e.$i.' & 0xff] | ($isbox[($'.$e.$i.' >> 8) & 0xff] << 8) | ($isbox[($'.$e.$i.' >> 16) & 0xff] << 16) | ($isbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; } $decrypt_block .= '$in = pack("N*"'."\n"; for ($i = 0; $i < $Nb; ++$i) { $decrypt_block.= ', ($'.$e.$i. ' & '.((int)0xFF000000).') ^ ($'.$e.(($Nb + $i - $c[1]) % $Nb).' & 0x00FF0000 ) ^ ($'.$e.(($Nb + $i - $c[2]) % $Nb).' & 0x0000FF00 ) ^ ($'.$e.(($Nb + $i - $c[3]) % $Nb).' & 0x000000FF ) ^ '.$dw[$i]."\n"; } $decrypt_block .= ');'; $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( array( 'init_crypt' => '', 'init_encrypt' => $init_encrypt, 'init_decrypt' => $init_decrypt, 'encrypt_block' => $encrypt_block, 'decrypt_block' => $decrypt_block ) ); } $this->inline_crypt = $lambda_functions[$code_hash]; } } key_length = 4; } elseif ($length > 448) { $this->key_length = 56; } else { $this->key_length = $length >> 3; } parent::setKeyLength($length); } function isValidEngine($engine) { if ($engine == self::ENGINE_OPENSSL) { if (version_compare(PHP_VERSION, '5.3.7') < 0 && $this->key_length != 16) { return false; } if ($this->key_length < 16) { return false; } $this->cipher_name_openssl_ecb = 'bf-ecb'; $this->cipher_name_openssl = 'bf-' . $this->_openssl_translate_mode(); } return parent::isValidEngine($engine); } function _setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key']) { return; } $this->kl = array('key' => $this->key); $this->bctx = array( 'p' => array(), 'sb' => array( $this->sbox0, $this->sbox1, $this->sbox2, $this->sbox3 ) ); $key = array_values(unpack('C*', $this->key)); $keyl = count($key); for ($j = 0, $i = 0; $i < 18; ++$i) { for ($data = 0, $k = 0; $k < 4; ++$k) { $data = ($data << 8) | $key[$j]; if (++$j >= $keyl) { $j = 0; } } $this->bctx['p'][] = $this->parray[$i] ^ $data; } $data = "\0\0\0\0\0\0\0\0"; for ($i = 0; $i < 18; $i += 2) { list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data))); $this->bctx['p'][$i ] = $l; $this->bctx['p'][$i + 1] = $r; } for ($i = 0; $i < 4; ++$i) { for ($j = 0; $j < 256; $j += 2) { list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data))); $this->bctx['sb'][$i][$j ] = $l; $this->bctx['sb'][$i][$j + 1] = $r; } } } function _encryptBlock($in) { $p = $this->bctx["p"]; $sb_0 = $this->bctx["sb"][0]; $sb_1 = $this->bctx["sb"][1]; $sb_2 = $this->bctx["sb"][2]; $sb_3 = $this->bctx["sb"][3]; $in = unpack("N*", $in); $l = $in[1]; $r = $in[2]; for ($i = 0; $i < 16; $i+= 2) { $l^= $p[$i]; $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ $sb_2[$l >> 8 & 0xff]) + $sb_3[$l & 0xff]); $r^= $p[$i + 1]; $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ $sb_2[$r >> 8 & 0xff]) + $sb_3[$r & 0xff]); } return pack("N*", $r ^ $p[17], $l ^ $p[16]); } function _decryptBlock($in) { $p = $this->bctx["p"]; $sb_0 = $this->bctx["sb"][0]; $sb_1 = $this->bctx["sb"][1]; $sb_2 = $this->bctx["sb"][2]; $sb_3 = $this->bctx["sb"][3]; $in = unpack("N*", $in); $l = $in[1]; $r = $in[2]; for ($i = 17; $i > 2; $i-= 2) { $l^= $p[$i]; $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ $sb_2[$l >> 8 & 0xff]) + $sb_3[$l & 0xff]); $r^= $p[$i - 1]; $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ $sb_2[$r >> 8 & 0xff]) + $sb_3[$r & 0xff]); } return pack("N*", $r ^ $p[0], $l ^ $p[1]); } function _setupInlineCrypt() { $lambda_functions =& self::_getLambdaFunctions(); $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); $code_hash = "Crypt_Blowfish, {$this->mode}"; if ($gen_hi_opt_code) { $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } $safeint = $this->safe_intval_inline(); if (!isset($lambda_functions[$code_hash])) { switch (true) { case $gen_hi_opt_code: $p = $this->bctx['p']; $init_crypt = ' static $sb_0, $sb_1, $sb_2, $sb_3; if (!$sb_0) { $sb_0 = $self->bctx["sb"][0]; $sb_1 = $self->bctx["sb"][1]; $sb_2 = $self->bctx["sb"][2]; $sb_3 = $self->bctx["sb"][3]; } '; break; default: $p = array(); for ($i = 0; $i < 18; ++$i) { $p[] = '$p_' . $i; } $init_crypt = ' list($sb_0, $sb_1, $sb_2, $sb_3) = $self->bctx["sb"]; list(' . implode(',', $p) . ') = $self->bctx["p"]; '; } $encrypt_block = ' $in = unpack("N*", $in); $l = $in[1]; $r = $in[2]; '; for ($i = 0; $i < 16; $i+= 2) { $encrypt_block.= ' $l^= ' . $p[$i] . '; $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ $sb_2[$l >> 8 & 0xff]) + $sb_3[$l & 0xff]') . '; $r^= ' . $p[$i + 1] . '; $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ $sb_2[$r >> 8 & 0xff]) + $sb_3[$r & 0xff]') . '; '; } $encrypt_block.= ' $in = pack("N*", $r ^ ' . $p[17] . ', $l ^ ' . $p[16] . ' ); '; $decrypt_block = ' $in = unpack("N*", $in); $l = $in[1]; $r = $in[2]; '; for ($i = 17; $i > 2; $i-= 2) { $decrypt_block.= ' $l^= ' . $p[$i] . '; $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ $sb_2[$l >> 8 & 0xff]) + $sb_3[$l & 0xff]') . '; $r^= ' . $p[$i - 1] . '; $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ $sb_2[$r >> 8 & 0xff]) + $sb_3[$r & 0xff]') . '; '; } $decrypt_block.= ' $in = pack("N*", $r ^ ' . $p[0] . ', $l ^ ' . $p[1] . ' ); '; $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( array( 'init_crypt' => $init_crypt, 'init_encrypt' => '', 'init_decrypt' => '', 'encrypt_block' => $encrypt_block, 'decrypt_block' => $decrypt_block ) ); } $this->inline_crypt = $lambda_functions[$code_hash]; } } key_length = 16; break; case $length <= 192: $this->key_length = 24; break; default: $this->key_length = 32; } parent::setKeyLength($length); } function _setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key']) { return; } $this->kl = array('key' => $this->key); $le_longs = unpack('V*', $this->key); $key = unpack('C*', $this->key); $m0 = $this->m0; $m1 = $this->m1; $m2 = $this->m2; $m3 = $this->m3; $q0 = $this->q0; $q1 = $this->q1; $K = $S0 = $S1 = $S2 = $S3 = array(); switch (strlen($this->key)) { case 16: list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[1], $le_longs[2]); list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[3], $le_longs[4]); for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { $A = $m0[$q0[$q0[$i] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$i] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$i] ^ $key[11]] ^ $key[3]] ^ $m3[$q1[$q1[$i] ^ $key[12]] ^ $key[4]]; $B = $m0[$q0[$q0[$j] ^ $key[13]] ^ $key[5]] ^ $m1[$q0[$q1[$j] ^ $key[14]] ^ $key[6]] ^ $m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); $A = $this->safe_intval($A + $B); $K[] = $A; $A = $this->safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$i] ^ $s4] ^ $s0]; $S1[$i] = $m1[$q0[$q1[$i] ^ $s5] ^ $s1]; $S2[$i] = $m2[$q1[$q0[$i] ^ $s6] ^ $s2]; $S3[$i] = $m3[$q1[$q1[$i] ^ $s7] ^ $s3]; } break; case 24: list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[1], $le_longs[2]); list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[3], $le_longs[4]); list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[5], $le_longs[6]); for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { $A = $m0[$q0[$q0[$q1[$i] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$q1[$i] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$q0[$i] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^ $m3[$q1[$q1[$q0[$i] ^ $key[20]] ^ $key[12]] ^ $key[4]]; $B = $m0[$q0[$q0[$q1[$j] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^ $m1[$q0[$q1[$q1[$j] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^ $m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); $A = $this->safe_intval($A + $B); $K[] = $A; $A = $this->safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$q1[$i] ^ $s8] ^ $s4] ^ $s0]; $S1[$i] = $m1[$q0[$q1[$q1[$i] ^ $s9] ^ $s5] ^ $s1]; $S2[$i] = $m2[$q1[$q0[$q0[$i] ^ $sa] ^ $s6] ^ $s2]; $S3[$i] = $m3[$q1[$q1[$q0[$i] ^ $sb] ^ $s7] ^ $s3]; } break; default: list($sf, $se, $sd, $sc) = $this->_mdsrem($le_longs[1], $le_longs[2]); list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[3], $le_longs[4]); list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[5], $le_longs[6]); list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[7], $le_longs[8]); for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) { $A = $m0[$q0[$q0[$q1[$q1[$i] ^ $key[25]] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^ $m1[$q0[$q1[$q1[$q0[$i] ^ $key[26]] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^ $m2[$q1[$q0[$q0[$q0[$i] ^ $key[27]] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^ $m3[$q1[$q1[$q0[$q1[$i] ^ $key[28]] ^ $key[20]] ^ $key[12]] ^ $key[4]]; $B = $m0[$q0[$q0[$q1[$q1[$j] ^ $key[29]] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^ $m1[$q0[$q1[$q1[$q0[$j] ^ $key[30]] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^ $m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); $A = $this->safe_intval($A + $B); $K[] = $A; $A = $this->safe_intval($A + $B); $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$q1[$q1[$i] ^ $sc] ^ $s8] ^ $s4] ^ $s0]; $S1[$i] = $m1[$q0[$q1[$q1[$q0[$i] ^ $sd] ^ $s9] ^ $s5] ^ $s1]; $S2[$i] = $m2[$q1[$q0[$q0[$q0[$i] ^ $se] ^ $sa] ^ $s6] ^ $s2]; $S3[$i] = $m3[$q1[$q1[$q0[$q1[$i] ^ $sf] ^ $sb] ^ $s7] ^ $s3]; } } $this->K = $K; $this->S0 = $S0; $this->S1 = $S1; $this->S2 = $S2; $this->S3 = $S3; } function _mdsrem($A, $B) { for ($i = 0; $i < 8; ++$i) { $t = 0xff & ($B >> 24); $B = ($B << 8) | (0xff & ($A >> 24)); $A<<= 8; $u = $t << 1; if ($t & 0x80) { $u^= 0x14d; } $B ^= $t ^ ($u << 16); $u^= 0x7fffffff & ($t >> 1); if ($t & 0x01) { $u^= 0xa6 ; } $B^= ($u << 24) | ($u << 8); } return array( 0xff & $B >> 24, 0xff & $B >> 16, 0xff & $B >> 8, 0xff & $B); } function _encryptBlock($in) { $S0 = $this->S0; $S1 = $this->S1; $S2 = $this->S2; $S3 = $this->S3; $K = $this->K; $in = unpack("V4", $in); $R0 = $K[0] ^ $in[1]; $R1 = $K[1] ^ $in[2]; $R2 = $K[2] ^ $in[3]; $R3 = $K[3] ^ $in[4]; $ki = 7; while ($ki < 39) { $t0 = $S0[ $R0 & 0xff] ^ $S1[($R0 >> 8) & 0xff] ^ $S2[($R0 >> 16) & 0xff] ^ $S3[($R0 >> 24) & 0xff]; $t1 = $S0[($R1 >> 24) & 0xff] ^ $S1[ $R1 & 0xff] ^ $S2[($R1 >> 8) & 0xff] ^ $S3[($R1 >> 16) & 0xff]; $R2^= $this->safe_intval($t0 + $t1 + $K[++$ki]); $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); $t0 = $S0[ $R2 & 0xff] ^ $S1[($R2 >> 8) & 0xff] ^ $S2[($R2 >> 16) & 0xff] ^ $S3[($R2 >> 24) & 0xff]; $t1 = $S0[($R3 >> 24) & 0xff] ^ $S1[ $R3 & 0xff] ^ $S2[($R3 >> 8) & 0xff] ^ $S3[($R3 >> 16) & 0xff]; $R0^= $this->safe_intval($t0 + $t1 + $K[++$ki]); $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); } return pack("V4", $K[4] ^ $R2, $K[5] ^ $R3, $K[6] ^ $R0, $K[7] ^ $R1); } function _decryptBlock($in) { $S0 = $this->S0; $S1 = $this->S1; $S2 = $this->S2; $S3 = $this->S3; $K = $this->K; $in = unpack("V4", $in); $R0 = $K[4] ^ $in[1]; $R1 = $K[5] ^ $in[2]; $R2 = $K[6] ^ $in[3]; $R3 = $K[7] ^ $in[4]; $ki = 40; while ($ki > 8) { $t0 = $S0[$R0 & 0xff] ^ $S1[$R0 >> 8 & 0xff] ^ $S2[$R0 >> 16 & 0xff] ^ $S3[$R0 >> 24 & 0xff]; $t1 = $S0[$R1 >> 24 & 0xff] ^ $S1[$R1 & 0xff] ^ $S2[$R1 >> 8 & 0xff] ^ $S3[$R1 >> 16 & 0xff]; $R3^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); $t0 = $S0[$R2 & 0xff] ^ $S1[$R2 >> 8 & 0xff] ^ $S2[$R2 >> 16 & 0xff] ^ $S3[$R2 >> 24 & 0xff]; $t1 = $S0[$R3 >> 24 & 0xff] ^ $S1[$R3 & 0xff] ^ $S2[$R3 >> 8 & 0xff] ^ $S3[$R3 >> 16 & 0xff]; $R1^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); } return pack("V4", $K[0] ^ $R2, $K[1] ^ $R3, $K[2] ^ $R0, $K[3] ^ $R1); } function _setupInlineCrypt() { $lambda_functions =& self::_getLambdaFunctions(); $gen_hi_opt_code = (bool)(count($lambda_functions) < 10); $code_hash = "Crypt_Twofish, {$this->mode}"; if ($gen_hi_opt_code) { $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } $safeint = $this->safe_intval_inline(); if (!isset($lambda_functions[$code_hash])) { switch (true) { case $gen_hi_opt_code: $K = $this->K; $init_crypt = ' static $S0, $S1, $S2, $S3; if (!$S0) { for ($i = 0; $i < 256; ++$i) { $S0[] = (int)$self->S0[$i]; $S1[] = (int)$self->S1[$i]; $S2[] = (int)$self->S2[$i]; $S3[] = (int)$self->S3[$i]; } } '; break; default: $K = array(); for ($i = 0; $i < 40; ++$i) { $K[] = '$K_' . $i; } $init_crypt = ' $S0 = $self->S0; $S1 = $self->S1; $S2 = $self->S2; $S3 = $self->S3; list(' . implode(',', $K) . ') = $self->K; '; } $encrypt_block = ' $in = unpack("V4", $in); $R0 = '.$K[0].' ^ $in[1]; $R1 = '.$K[1].' ^ $in[2]; $R2 = '.$K[2].' ^ $in[3]; $R3 = '.$K[3].' ^ $in[4]; '; for ($ki = 7, $i = 0; $i < 8; ++$i) { $encrypt_block.= ' $t0 = $S0[ $R0 & 0xff] ^ $S1[($R0 >> 8) & 0xff] ^ $S2[($R0 >> 16) & 0xff] ^ $S3[($R0 >> 24) & 0xff]; $t1 = $S0[($R1 >> 24) & 0xff] ^ $S1[ $R1 & 0xff] ^ $S2[($R1 >> 8) & 0xff] ^ $S3[($R1 >> 16) & 0xff]; $R2^= ' . sprintf($safeint, '$t0 + $t1 + ' . $K[++$ki]) . '; $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; $t0 = $S0[ $R2 & 0xff] ^ $S1[($R2 >> 8) & 0xff] ^ $S2[($R2 >> 16) & 0xff] ^ $S3[($R2 >> 24) & 0xff]; $t1 = $S0[($R3 >> 24) & 0xff] ^ $S1[ $R3 & 0xff] ^ $S2[($R3 >> 8) & 0xff] ^ $S3[($R3 >> 16) & 0xff]; $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . '; $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; '; } $encrypt_block.= ' $in = pack("V4", ' . $K[4] . ' ^ $R2, ' . $K[5] . ' ^ $R3, ' . $K[6] . ' ^ $R0, ' . $K[7] . ' ^ $R1); '; $decrypt_block = ' $in = unpack("V4", $in); $R0 = '.$K[4].' ^ $in[1]; $R1 = '.$K[5].' ^ $in[2]; $R2 = '.$K[6].' ^ $in[3]; $R3 = '.$K[7].' ^ $in[4]; '; for ($ki = 40, $i = 0; $i < 8; ++$i) { $decrypt_block.= ' $t0 = $S0[$R0 & 0xff] ^ $S1[$R0 >> 8 & 0xff] ^ $S2[$R0 >> 16 & 0xff] ^ $S3[$R0 >> 24 & 0xff]; $t1 = $S0[$R1 >> 24 & 0xff] ^ $S1[$R1 & 0xff] ^ $S2[$R1 >> 8 & 0xff] ^ $S3[$R1 >> 16 & 0xff]; $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; $t0 = $S0[$R2 & 0xff] ^ $S1[$R2 >> 8 & 0xff] ^ $S2[$R2 >> 16 & 0xff] ^ $S3[$R2 >> 24 & 0xff]; $t1 = $S0[$R3 >> 24 & 0xff] ^ $S1[$R3 & 0xff] ^ $S2[$R3 >> 8 & 0xff] ^ $S3[$R3 >> 16 & 0xff]; $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; '; } $decrypt_block.= ' $in = pack("V4", ' . $K[0] . ' ^ $R2, ' . $K[1] . ' ^ $R3, ' . $K[2] . ' ^ $R0, ' . $K[3] . ' ^ $R1); '; $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( array( 'init_crypt' => $init_crypt, 'init_encrypt' => '', 'init_decrypt' => '', 'encrypt_block' => $encrypt_block, 'decrypt_block' => $decrypt_block ) ); } $this->inline_crypt = $lambda_functions[$code_hash]; } } explicit_key_length) { $length = strlen($key); switch (true) { case $length <= 16: $this->key_length = 16; break; case $length <= 24: $this->key_length = 24; break; default: $this->key_length = 32; } $this->_setEngine(); } } } paddable = true; $this->mode = self::MODE_ECB; break; case self::MODE_CTR: case self::MODE_CFB: case self::MODE_CFB8: case self::MODE_OFB8: case self::MODE_OFB: case self::MODE_STREAM: $this->mode = $mode; break; case self::MODE_CBC: default: $this->paddable = true; $this->mode = self::MODE_CBC; } $this->_setEngine(); } function setIV($iv) { if ($this->mode == self::MODE_ECB) { return; } $this->iv = $iv; $this->changed = true; } function setKeyLength($length) { $this->explicit_key_length = true; $this->changed = true; $this->_setEngine(); } function getKeyLength() { return $this->key_length << 3; } function getBlockLength() { return $this->block_size << 3; } function setKey($key) { if (!$this->explicit_key_length) { $this->setKeyLength(strlen($key) << 3); $this->explicit_key_length = false; } $this->key = $key; $this->changed = true; $this->_setEngine(); } function setPassword($password, $method = 'pbkdf2') { $key = ''; switch ($method) { default: $func_args = func_get_args(); $hash = isset($func_args[2]) ? $func_args[2] : 'sha1'; $salt = isset($func_args[3]) ? $func_args[3] : $this->password_default_salt; $count = isset($func_args[4]) ? $func_args[4] : 1000; if (isset($func_args[5])) { $dkLen = $func_args[5]; } else { $dkLen = $method == 'pbkdf1' ? 2 * $this->key_length : $this->key_length; } switch (true) { case $method == 'pbkdf1': $hashObj = new Hash(); $hashObj->setHash($hash); if ($dkLen > $hashObj->getLength()) { user_error('Derived key too long'); return false; } $t = $password . $salt; for ($i = 0; $i < $count; ++$i) { $t = $hashObj->hash($t); } $key = substr($t, 0, $dkLen); $this->setKey(substr($key, 0, $dkLen >> 1)); $this->setIV(substr($key, $dkLen >> 1)); return true; case !function_exists('hash_pbkdf2'): case !function_exists('hash_algos'): case !in_array($hash, hash_algos()): $i = 1; $hmac = new Hash(); $hmac->setHash($hash); $hmac->setKey($password); while (strlen($key) < $dkLen) { $f = $u = $hmac->hash($salt . pack('N', $i++)); for ($j = 2; $j <= $count; ++$j) { $u = $hmac->hash($u); $f^= $u; } $key.= $f; } $key = substr($key, 0, $dkLen); break; default: $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true); } } $this->setKey($key); return true; } function encrypt($plaintext) { if ($this->paddable) { $plaintext = $this->_pad($plaintext); } if ($this->engine === self::ENGINE_OPENSSL) { if ($this->changed) { $this->_clearBuffers(); $this->changed = false; } switch ($this->mode) { case self::MODE_STREAM: return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); case self::MODE_ECB: $result = @openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); return !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; case self::MODE_CBC: $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); if (!defined('OPENSSL_RAW_DATA')) { $result = substr($result, 0, -$this->block_size); } if ($this->continuousBuffer) { $this->encryptIV = substr($result, -$this->block_size); } return $result; case self::MODE_CTR: return $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer); case self::MODE_CFB: $ciphertext = ''; if ($this->continuousBuffer) { $iv = &$this->encryptIV; $pos = &$this->enbuffer['pos']; } else { $iv = $this->encryptIV; $pos = 0; } $len = strlen($plaintext); $i = 0; if ($pos) { $orig_pos = $pos; $max = $this->block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $ciphertext = substr($iv, $orig_pos) ^ $plaintext; $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); $plaintext = substr($plaintext, $i); } $overflow = $len % $this->block_size; if ($overflow) { $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); $iv = $this->_string_pop($ciphertext, $this->block_size); $size = $len - $overflow; $block = $iv ^ substr($plaintext, -$overflow); $iv = substr_replace($iv, $block, 0, $overflow); $ciphertext.= $block; $pos = $overflow; } elseif ($len) { $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); $iv = substr($ciphertext, -$this->block_size); } return $ciphertext; case self::MODE_CFB8: $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); if ($this->continuousBuffer) { if (($len = strlen($ciphertext)) >= $this->block_size) { $this->encryptIV = substr($ciphertext, -$this->block_size); } else { $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len); } } return $ciphertext; case self::MODE_OFB8: $ciphertext = ''; $len = strlen($plaintext); $iv = $this->encryptIV; for ($i = 0; $i < $len; ++$i) { $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV); $ciphertext.= $plaintext[$i] ^ $xor; $iv = substr($iv, 1) . $xor[0]; } if ($this->continuousBuffer) { $this->encryptIV = $iv; } break; case self::MODE_OFB: return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer); } } if ($this->engine === self::ENGINE_MCRYPT) { set_error_handler(array($this, 'do_nothing')); if ($this->changed) { $this->_setupMcrypt(); $this->changed = false; } if ($this->enchanged) { mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); $this->enchanged = false; } if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { $block_size = $this->block_size; $iv = &$this->encryptIV; $pos = &$this->enbuffer['pos']; $len = strlen($plaintext); $ciphertext = ''; $i = 0; if ($pos) { $orig_pos = $pos; $max = $block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $ciphertext = substr($iv, $orig_pos) ^ $plaintext; $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); $this->enbuffer['enmcrypt_init'] = true; } if ($len >= $block_size) { if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) { if ($this->enbuffer['enmcrypt_init'] === true) { mcrypt_generic_init($this->enmcrypt, $this->key, $iv); $this->enbuffer['enmcrypt_init'] = false; } $ciphertext.= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size)); $iv = substr($ciphertext, -$block_size); $len%= $block_size; } else { while ($len >= $block_size) { $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size); $ciphertext.= $iv; $len-= $block_size; $i+= $block_size; } } } if ($len) { $iv = mcrypt_generic($this->ecb, $iv); $block = $iv ^ substr($plaintext, -$len); $iv = substr_replace($iv, $block, 0, $len); $ciphertext.= $block; $pos = $len; } restore_error_handler(); return $ciphertext; } $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); if (!$this->continuousBuffer) { mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); } restore_error_handler(); return $ciphertext; } if ($this->changed) { $this->_setup(); $this->changed = false; } if ($this->use_inline_crypt) { $inline = $this->inline_crypt; return $inline('encrypt', $this, $plaintext); } $buffer = &$this->enbuffer; $block_size = $this->block_size; $ciphertext = ''; switch ($this->mode) { case self::MODE_ECB: for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); } break; case self::MODE_CBC: $xor = $this->encryptIV; for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); $block = $this->_encryptBlock($block ^ $xor); $xor = $block; $ciphertext.= $block; } if ($this->continuousBuffer) { $this->encryptIV = $xor; } break; case self::MODE_CTR: $xor = $this->encryptIV; if (strlen($buffer['ciphertext'])) { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); if (strlen($block) > strlen($buffer['ciphertext'])) { $buffer['ciphertext'].= $this->_encryptBlock($xor); $this->_increment_str($xor); } $key = $this->_string_shift($buffer['ciphertext'], $block_size); $ciphertext.= $block ^ $key; } } else { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); $key = $this->_encryptBlock($xor); $this->_increment_str($xor); $ciphertext.= $block ^ $key; } } if ($this->continuousBuffer) { $this->encryptIV = $xor; if ($start = strlen($plaintext) % $block_size) { $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; } } break; case self::MODE_CFB: if ($this->continuousBuffer) { $iv = &$this->encryptIV; $pos = &$buffer['pos']; } else { $iv = $this->encryptIV; $pos = 0; } $len = strlen($plaintext); $i = 0; if ($pos) { $orig_pos = $pos; $max = $block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $ciphertext = substr($iv, $orig_pos) ^ $plaintext; $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); } while ($len >= $block_size) { $iv = $this->_encryptBlock($iv) ^ substr($plaintext, $i, $block_size); $ciphertext.= $iv; $len-= $block_size; $i+= $block_size; } if ($len) { $iv = $this->_encryptBlock($iv); $block = $iv ^ substr($plaintext, $i); $iv = substr_replace($iv, $block, 0, $len); $ciphertext.= $block; $pos = $len; } break; case self::MODE_CFB8: $ciphertext = ''; $len = strlen($plaintext); $iv = $this->encryptIV; for ($i = 0; $i < $len; ++$i) { $ciphertext.= ($c = $plaintext[$i] ^ $this->_encryptBlock($iv)); $iv = substr($iv, 1) . $c; } if ($this->continuousBuffer) { if ($len >= $block_size) { $this->encryptIV = substr($ciphertext, -$block_size); } else { $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len); } } break; case self::MODE_OFB8: $ciphertext = ''; $len = strlen($plaintext); $iv = $this->encryptIV; for ($i = 0; $i < $len; ++$i) { $xor = $this->_encryptBlock($iv); $ciphertext.= $plaintext[$i] ^ $xor; $iv = substr($iv, 1) . $xor[0]; } if ($this->continuousBuffer) { $this->encryptIV = $iv; } break; case self::MODE_OFB: $xor = $this->encryptIV; if (strlen($buffer['xor'])) { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); if (strlen($block) > strlen($buffer['xor'])) { $xor = $this->_encryptBlock($xor); $buffer['xor'].= $xor; } $key = $this->_string_shift($buffer['xor'], $block_size); $ciphertext.= $block ^ $key; } } else { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $xor = $this->_encryptBlock($xor); $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor; } $key = $xor; } if ($this->continuousBuffer) { $this->encryptIV = $xor; if ($start = strlen($plaintext) % $block_size) { $buffer['xor'] = substr($key, $start) . $buffer['xor']; } } break; case self::MODE_STREAM: $ciphertext = $this->_encryptBlock($plaintext); break; } return $ciphertext; } function decrypt($ciphertext) { if ($this->paddable) { $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0)); } if ($this->engine === self::ENGINE_OPENSSL) { if ($this->changed) { $this->_clearBuffers(); $this->changed = false; } switch ($this->mode) { case self::MODE_STREAM: $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options); break; case self::MODE_ECB: if (!defined('OPENSSL_RAW_DATA')) { $ciphertext.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true); } $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options); break; case self::MODE_CBC: if (!defined('OPENSSL_RAW_DATA')) { $padding = str_repeat(chr($this->block_size), $this->block_size) ^ substr($ciphertext, -$this->block_size); $ciphertext.= substr(@openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size); $offset = 2 * $this->block_size; } else { $offset = $this->block_size; } $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV); if ($this->continuousBuffer) { $this->decryptIV = substr($ciphertext, -$offset, $this->block_size); } break; case self::MODE_CTR: $plaintext = $this->_openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer); break; case self::MODE_CFB: $plaintext = ''; if ($this->continuousBuffer) { $iv = &$this->decryptIV; $pos = &$this->buffer['pos']; } else { $iv = $this->decryptIV; $pos = 0; } $len = strlen($ciphertext); $i = 0; if ($pos) { $orig_pos = $pos; $max = $this->block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $plaintext = substr($iv, $orig_pos) ^ $ciphertext; $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); $ciphertext = substr($ciphertext, $i); } $overflow = $len % $this->block_size; if ($overflow) { $plaintext.= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); if ($len - $overflow) { $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow); } $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); $plaintext.= $iv ^ substr($ciphertext, -$overflow); $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow); $pos = $overflow; } elseif ($len) { $plaintext.= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv); $iv = substr($ciphertext, -$this->block_size); } break; case self::MODE_CFB8: $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV); if ($this->continuousBuffer) { if (($len = strlen($ciphertext)) >= $this->block_size) { $this->decryptIV = substr($ciphertext, -$this->block_size); } else { $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len); } } break; case self::MODE_OFB8: $plaintext = ''; $len = strlen($ciphertext); $iv = $this->decryptIV; for ($i = 0; $i < $len; ++$i) { $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV); $plaintext.= $ciphertext[$i] ^ $xor; $iv = substr($iv, 1) . $xor[0]; } if ($this->continuousBuffer) { $this->decryptIV = $iv; } break; case self::MODE_OFB: $plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer); } return $this->paddable ? $this->_unpad($plaintext) : $plaintext; } if ($this->engine === self::ENGINE_MCRYPT) { set_error_handler(array($this, 'do_nothing')); $block_size = $this->block_size; if ($this->changed) { $this->_setupMcrypt(); $this->changed = false; } if ($this->dechanged) { mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); $this->dechanged = false; } if ($this->mode == self::MODE_CFB && $this->continuousBuffer) { $iv = &$this->decryptIV; $pos = &$this->debuffer['pos']; $len = strlen($ciphertext); $plaintext = ''; $i = 0; if ($pos) { $orig_pos = $pos; $max = $block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $plaintext = substr($iv, $orig_pos) ^ $ciphertext; $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); } if ($len >= $block_size) { $cb = substr($ciphertext, $i, $len - $len % $block_size); $plaintext.= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb; $iv = substr($cb, -$block_size); $len%= $block_size; } if ($len) { $iv = mcrypt_generic($this->ecb, $iv); $plaintext.= $iv ^ substr($ciphertext, -$len); $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len); $pos = $len; } restore_error_handler(); return $plaintext; } $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); if (!$this->continuousBuffer) { mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); } restore_error_handler(); return $this->paddable ? $this->_unpad($plaintext) : $plaintext; } if ($this->changed) { $this->_setup(); $this->changed = false; } if ($this->use_inline_crypt) { $inline = $this->inline_crypt; return $inline('decrypt', $this, $ciphertext); } $block_size = $this->block_size; $buffer = &$this->debuffer; $plaintext = ''; switch ($this->mode) { case self::MODE_ECB: for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); } break; case self::MODE_CBC: $xor = $this->decryptIV; for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $block = substr($ciphertext, $i, $block_size); $plaintext.= $this->_decryptBlock($block) ^ $xor; $xor = $block; } if ($this->continuousBuffer) { $this->decryptIV = $xor; } break; case self::MODE_CTR: $xor = $this->decryptIV; if (strlen($buffer['ciphertext'])) { for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $block = substr($ciphertext, $i, $block_size); if (strlen($block) > strlen($buffer['ciphertext'])) { $buffer['ciphertext'].= $this->_encryptBlock($xor); $this->_increment_str($xor); } $key = $this->_string_shift($buffer['ciphertext'], $block_size); $plaintext.= $block ^ $key; } } else { for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $block = substr($ciphertext, $i, $block_size); $key = $this->_encryptBlock($xor); $this->_increment_str($xor); $plaintext.= $block ^ $key; } } if ($this->continuousBuffer) { $this->decryptIV = $xor; if ($start = strlen($ciphertext) % $block_size) { $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; } } break; case self::MODE_CFB: if ($this->continuousBuffer) { $iv = &$this->decryptIV; $pos = &$buffer['pos']; } else { $iv = $this->decryptIV; $pos = 0; } $len = strlen($ciphertext); $i = 0; if ($pos) { $orig_pos = $pos; $max = $block_size - $pos; if ($len >= $max) { $i = $max; $len-= $max; $pos = 0; } else { $i = $len; $pos+= $len; $len = 0; } $plaintext = substr($iv, $orig_pos) ^ $ciphertext; $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); } while ($len >= $block_size) { $iv = $this->_encryptBlock($iv); $cb = substr($ciphertext, $i, $block_size); $plaintext.= $iv ^ $cb; $iv = $cb; $len-= $block_size; $i+= $block_size; } if ($len) { $iv = $this->_encryptBlock($iv); $plaintext.= $iv ^ substr($ciphertext, $i); $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len); $pos = $len; } break; case self::MODE_CFB8: $plaintext = ''; $len = strlen($ciphertext); $iv = $this->decryptIV; for ($i = 0; $i < $len; ++$i) { $plaintext.= $ciphertext[$i] ^ $this->_encryptBlock($iv); $iv = substr($iv, 1) . $ciphertext[$i]; } if ($this->continuousBuffer) { if ($len >= $block_size) { $this->decryptIV = substr($ciphertext, -$block_size); } else { $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len); } } break; case self::MODE_OFB8: $plaintext = ''; $len = strlen($ciphertext); $iv = $this->decryptIV; for ($i = 0; $i < $len; ++$i) { $xor = $this->_encryptBlock($iv); $plaintext.= $ciphertext[$i] ^ $xor; $iv = substr($iv, 1) . $xor[0]; } if ($this->continuousBuffer) { $this->decryptIV = $iv; } break; case self::MODE_OFB: $xor = $this->decryptIV; if (strlen($buffer['xor'])) { for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $block = substr($ciphertext, $i, $block_size); if (strlen($block) > strlen($buffer['xor'])) { $xor = $this->_encryptBlock($xor); $buffer['xor'].= $xor; } $key = $this->_string_shift($buffer['xor'], $block_size); $plaintext.= $block ^ $key; } } else { for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { $xor = $this->_encryptBlock($xor); $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor; } $key = $xor; } if ($this->continuousBuffer) { $this->decryptIV = $xor; if ($start = strlen($ciphertext) % $block_size) { $buffer['xor'] = substr($key, $start) . $buffer['xor']; } } break; case self::MODE_STREAM: $plaintext = $this->_decryptBlock($ciphertext); break; } return $this->paddable ? $this->_unpad($plaintext) : $plaintext; } function _openssl_ctr_process($plaintext, &$encryptIV, &$buffer) { $ciphertext = ''; $block_size = $this->block_size; $key = $this->key; if ($this->openssl_emulate_ctr) { $xor = $encryptIV; if (strlen($buffer['ciphertext'])) { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); if (strlen($block) > strlen($buffer['ciphertext'])) { $result = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); $result = !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; $buffer['ciphertext'].= $result; } $this->_increment_str($xor); $otp = $this->_string_shift($buffer['ciphertext'], $block_size); $ciphertext.= $block ^ $otp; } } else { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); $otp = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); $otp = !defined('OPENSSL_RAW_DATA') ? substr($otp, 0, -$this->block_size) : $otp; $this->_increment_str($xor); $ciphertext.= $block ^ $otp; } } if ($this->continuousBuffer) { $encryptIV = $xor; if ($start = strlen($plaintext) % $block_size) { $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; } } return $ciphertext; } if (strlen($buffer['ciphertext'])) { $ciphertext = $plaintext ^ $this->_string_shift($buffer['ciphertext'], strlen($plaintext)); $plaintext = substr($plaintext, strlen($ciphertext)); if (!strlen($plaintext)) { return $ciphertext; } } $overflow = strlen($plaintext) % $block_size; if ($overflow) { $plaintext2 = $this->_string_pop($plaintext, $overflow); $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); $temp = $this->_string_pop($encrypted, $block_size); $ciphertext.= $encrypted . ($plaintext2 ^ $temp); if ($this->continuousBuffer) { $buffer['ciphertext'] = substr($temp, $overflow); $encryptIV = $temp; } } elseif (!strlen($buffer['ciphertext'])) { $ciphertext.= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); $temp = $this->_string_pop($ciphertext, $block_size); if ($this->continuousBuffer) { $encryptIV = $temp; } } if ($this->continuousBuffer) { if (!defined('OPENSSL_RAW_DATA')) { $encryptIV.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options); } $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); if ($overflow) { $this->_increment_str($encryptIV); } } return $ciphertext; } function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer) { if (strlen($buffer['xor'])) { $ciphertext = $plaintext ^ $buffer['xor']; $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); $plaintext = substr($plaintext, strlen($ciphertext)); } else { $ciphertext = ''; } $block_size = $this->block_size; $len = strlen($plaintext); $key = $this->key; $overflow = $len % $block_size; if (strlen($plaintext)) { if ($overflow) { $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); $xor = $this->_string_pop($ciphertext, $block_size); if ($this->continuousBuffer) { $encryptIV = $xor; } $ciphertext.= $this->_string_shift($xor, $overflow) ^ substr($plaintext, -$overflow); if ($this->continuousBuffer) { $buffer['xor'] = $xor; } } else { $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); if ($this->continuousBuffer) { $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size); } } } return $ciphertext; } function _openssl_translate_mode() { switch ($this->mode) { case self::MODE_ECB: return 'ecb'; case self::MODE_CBC: return 'cbc'; case self::MODE_CTR: return 'ctr'; case self::MODE_CFB: return 'cfb'; case self::MODE_CFB8: return 'cfb8'; case self::MODE_OFB: return 'ofb'; } } function enablePadding() { $this->padding = true; } function disablePadding() { $this->padding = false; } function enableContinuousBuffer() { if ($this->mode == self::MODE_ECB) { return; } $this->continuousBuffer = true; $this->_setEngine(); } function disableContinuousBuffer() { if ($this->mode == self::MODE_ECB) { return; } if (!$this->continuousBuffer) { return; } $this->continuousBuffer = false; $this->changed = true; $this->_setEngine(); } function isValidEngine($engine) { switch ($engine) { case self::ENGINE_OPENSSL: if ($this->mode == self::MODE_STREAM && $this->continuousBuffer) { return false; } $this->openssl_emulate_ctr = false; $result = $this->cipher_name_openssl && extension_loaded('openssl') && version_compare(PHP_VERSION, '5.3.3', '>='); if (!$result) { return false; } if (!defined('OPENSSL_RAW_DATA')) { $this->openssl_options = true; } else { $this->openssl_options = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING; } $methods = openssl_get_cipher_methods(); if (in_array($this->cipher_name_openssl, $methods)) { return true; } switch ($this->mode) { case self::MODE_CTR: if (in_array($this->cipher_name_openssl_ecb, $methods)) { $this->openssl_emulate_ctr = true; return true; } } return false; case self::ENGINE_MCRYPT: set_error_handler(array($this, 'do_nothing')); $result = $this->cipher_name_mcrypt && extension_loaded('mcrypt') && in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms()); restore_error_handler(); return $result; case self::ENGINE_INTERNAL: return true; } return false; } function setPreferredEngine($engine) { switch ($engine) { case self::ENGINE_MCRYPT: case self::ENGINE_INTERNAL: $this->preferredEngine = $engine; break; default: $this->preferredEngine = self::ENGINE_OPENSSL; } $this->_setEngine(); } function getEngine() { return $this->engine; } function _setEngine() { $this->engine = null; $candidateEngines = array( $this->preferredEngine, self::ENGINE_OPENSSL, self::ENGINE_MCRYPT ); foreach ($candidateEngines as $engine) { if ($this->isValidEngine($engine)) { $this->engine = $engine; break; } } if (!$this->engine) { $this->engine = self::ENGINE_INTERNAL; } if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) { set_error_handler(array($this, 'do_nothing')); mcrypt_module_close($this->enmcrypt); mcrypt_module_close($this->demcrypt); $this->enmcrypt = null; $this->demcrypt = null; if ($this->ecb) { mcrypt_module_close($this->ecb); $this->ecb = null; } restore_error_handler(); } $this->changed = true; } abstract function _encryptBlock($in); abstract function _decryptBlock($in); abstract function _setupKey(); function _setup() { $this->_clearBuffers(); $this->_setupKey(); if ($this->use_inline_crypt) { $this->_setupInlineCrypt(); } } function _setupMcrypt() { $this->_clearBuffers(); $this->enchanged = $this->dechanged = true; if (!isset($this->enmcrypt)) { static $mcrypt_modes = array( self::MODE_CTR => 'ctr', self::MODE_ECB => MCRYPT_MODE_ECB, self::MODE_CBC => MCRYPT_MODE_CBC, self::MODE_CFB => 'ncfb', self::MODE_CFB8 => MCRYPT_MODE_CFB, self::MODE_OFB => MCRYPT_MODE_NOFB, self::MODE_OFB8 => MCRYPT_MODE_OFB, self::MODE_STREAM => MCRYPT_MODE_STREAM, ); $this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); $this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], ''); if ($this->mode == self::MODE_CFB) { $this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, ''); } } if ($this->mode == self::MODE_CFB) { mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size)); } } function _pad($text) { $length = strlen($text); if (!$this->padding) { if ($length % $this->block_size == 0) { return $text; } else { user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})"); $this->padding = true; } } $pad = $this->block_size - ($length % $this->block_size); return str_pad($text, $length + $pad, chr($pad)); } function _unpad($text) { if (!$this->padding) { return $text; } $length = ord($text[strlen($text) - 1]); if (!$length || $length > $this->block_size) { return false; } return substr($text, 0, -$length); } function _clearBuffers() { $this->enbuffer = $this->debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true); $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0"); if (!$this->skip_key_adjustment) { $this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0"); } } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } function _string_pop(&$string, $index = 1) { $substr = substr($string, -$index); $string = substr($string, 0, -$index); return $substr; } function _increment_str(&$var) { if (function_exists('sodium_increment')) { $var = strrev($var); sodium_increment($var); $var = strrev($var); return; } for ($i = 4; $i <= strlen($var); $i+= 4) { $temp = substr($var, -$i, 4); switch ($temp) { case "\xFF\xFF\xFF\xFF": $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4); break; case "\x7F\xFF\xFF\xFF": $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4); return; default: $temp = unpack('Nnum', $temp); $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4); return; } } $remainder = strlen($var) % 4; if ($remainder == 0) { return; } $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT)); $temp = substr(pack('N', $temp['num'] + 1), -$remainder); $var = substr_replace($var, $temp, 0, $remainder); } function _setupInlineCrypt() { $this->use_inline_crypt = false; } function _createInlineCryptFunction($cipher_code) { $block_size = $this->block_size; $init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : ''; $init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : ''; $init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : ''; $encrypt_block = $cipher_code['encrypt_block']; $decrypt_block = $cipher_code['decrypt_block']; switch ($this->mode) { case self::MODE_ECB: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_plaintext_len = strlen($_text); for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $in = substr($_text, $_i, '.$block_size.'); '.$encrypt_block.' $_ciphertext.= $in; } return $_ciphertext; '; $decrypt = $init_decrypt . ' $_plaintext = ""; $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0)); $_ciphertext_len = strlen($_text); for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $in = substr($_text, $_i, '.$block_size.'); '.$decrypt_block.' $_plaintext.= $in; } return $self->_unpad($_plaintext); '; break; case self::MODE_CTR: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_plaintext_len = strlen($_text); $_xor = $self->encryptIV; $_buffer = &$self->enbuffer; if (strlen($_buffer["ciphertext"])) { for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); if (strlen($_block) > strlen($_buffer["ciphertext"])) { $in = $_xor; '.$encrypt_block.' $self->_increment_str($_xor); $_buffer["ciphertext"].= $in; } $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.'); $_ciphertext.= $_block ^ $_key; } } else { for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); $in = $_xor; '.$encrypt_block.' $self->_increment_str($_xor); $_key = $in; $_ciphertext.= $_block ^ $_key; } } if ($self->continuousBuffer) { $self->encryptIV = $_xor; if ($_start = $_plaintext_len % '.$block_size.') { $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; } } return $_ciphertext; '; $decrypt = $init_encrypt . ' $_plaintext = ""; $_ciphertext_len = strlen($_text); $_xor = $self->decryptIV; $_buffer = &$self->debuffer; if (strlen($_buffer["ciphertext"])) { for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); if (strlen($_block) > strlen($_buffer["ciphertext"])) { $in = $_xor; '.$encrypt_block.' $self->_increment_str($_xor); $_buffer["ciphertext"].= $in; } $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.'); $_plaintext.= $_block ^ $_key; } } else { for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); $in = $_xor; '.$encrypt_block.' $self->_increment_str($_xor); $_key = $in; $_plaintext.= $_block ^ $_key; } } if ($self->continuousBuffer) { $self->decryptIV = $_xor; if ($_start = $_ciphertext_len % '.$block_size.') { $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"]; } } return $_plaintext; '; break; case self::MODE_CFB: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_buffer = &$self->enbuffer; if ($self->continuousBuffer) { $_iv = &$self->encryptIV; $_pos = &$_buffer["pos"]; } else { $_iv = $self->encryptIV; $_pos = 0; } $_len = strlen($_text); $_i = 0; if ($_pos) { $_orig_pos = $_pos; $_max = '.$block_size.' - $_pos; if ($_len >= $_max) { $_i = $_max; $_len-= $_max; $_pos = 0; } else { $_i = $_len; $_pos+= $_len; $_len = 0; } $_ciphertext = substr($_iv, $_orig_pos) ^ $_text; $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i); } while ($_len >= '.$block_size.') { $in = $_iv; '.$encrypt_block.'; $_iv = $in ^ substr($_text, $_i, '.$block_size.'); $_ciphertext.= $_iv; $_len-= '.$block_size.'; $_i+= '.$block_size.'; } if ($_len) { $in = $_iv; '.$encrypt_block.' $_iv = $in; $_block = $_iv ^ substr($_text, $_i); $_iv = substr_replace($_iv, $_block, 0, $_len); $_ciphertext.= $_block; $_pos = $_len; } return $_ciphertext; '; $decrypt = $init_encrypt . ' $_plaintext = ""; $_buffer = &$self->debuffer; if ($self->continuousBuffer) { $_iv = &$self->decryptIV; $_pos = &$_buffer["pos"]; } else { $_iv = $self->decryptIV; $_pos = 0; } $_len = strlen($_text); $_i = 0; if ($_pos) { $_orig_pos = $_pos; $_max = '.$block_size.' - $_pos; if ($_len >= $_max) { $_i = $_max; $_len-= $_max; $_pos = 0; } else { $_i = $_len; $_pos+= $_len; $_len = 0; } $_plaintext = substr($_iv, $_orig_pos) ^ $_text; $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i); } while ($_len >= '.$block_size.') { $in = $_iv; '.$encrypt_block.' $_iv = $in; $cb = substr($_text, $_i, '.$block_size.'); $_plaintext.= $_iv ^ $cb; $_iv = $cb; $_len-= '.$block_size.'; $_i+= '.$block_size.'; } if ($_len) { $in = $_iv; '.$encrypt_block.' $_iv = $in; $_plaintext.= $_iv ^ substr($_text, $_i); $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len); $_pos = $_len; } return $_plaintext; '; break; case self::MODE_CFB8: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_len = strlen($_text); $_iv = $self->encryptIV; for ($_i = 0; $_i < $_len; ++$_i) { $in = $_iv; '.$encrypt_block.' $_ciphertext.= ($_c = $_text[$_i] ^ $in); $_iv = substr($_iv, 1) . $_c; } if ($self->continuousBuffer) { if ($_len >= '.$block_size.') { $self->encryptIV = substr($_ciphertext, -'.$block_size.'); } else { $self->encryptIV = substr($self->encryptIV, $_len - '.$block_size.') . substr($_ciphertext, -$_len); } } return $_ciphertext; '; $decrypt = $init_encrypt . ' $_plaintext = ""; $_len = strlen($_text); $_iv = $self->decryptIV; for ($_i = 0; $_i < $_len; ++$_i) { $in = $_iv; '.$encrypt_block.' $_plaintext.= $_text[$_i] ^ $in; $_iv = substr($_iv, 1) . $_text[$_i]; } if ($self->continuousBuffer) { if ($_len >= '.$block_size.') { $self->decryptIV = substr($_text, -'.$block_size.'); } else { $self->decryptIV = substr($self->decryptIV, $_len - '.$block_size.') . substr($_text, -$_len); } } return $_plaintext; '; break; case self::MODE_OFB8: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_len = strlen($_text); $_iv = $self->encryptIV; for ($_i = 0; $_i < $_len; ++$_i) { $in = $_iv; '.$encrypt_block.' $_ciphertext.= $_text[$_i] ^ $in; $_iv = substr($_iv, 1) . $in[0]; } if ($self->continuousBuffer) { $self->encryptIV = $_iv; } return $_ciphertext; '; $decrypt = $init_encrypt . ' $_plaintext = ""; $_len = strlen($_text); $_iv = $self->decryptIV; for ($_i = 0; $_i < $_len; ++$_i) { $in = $_iv; '.$encrypt_block.' $_plaintext.= $_text[$_i] ^ $in; $_iv = substr($_iv, 1) . $in[0]; } if ($self->continuousBuffer) { $self->decryptIV = $_iv; } return $_plaintext; '; break; case self::MODE_OFB: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_plaintext_len = strlen($_text); $_xor = $self->encryptIV; $_buffer = &$self->enbuffer; if (strlen($_buffer["xor"])) { for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); if (strlen($_block) > strlen($_buffer["xor"])) { $in = $_xor; '.$encrypt_block.' $_xor = $in; $_buffer["xor"].= $_xor; } $_key = $self->_string_shift($_buffer["xor"], '.$block_size.'); $_ciphertext.= $_block ^ $_key; } } else { for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $in = $_xor; '.$encrypt_block.' $_xor = $in; $_ciphertext.= substr($_text, $_i, '.$block_size.') ^ $_xor; } $_key = $_xor; } if ($self->continuousBuffer) { $self->encryptIV = $_xor; if ($_start = $_plaintext_len % '.$block_size.') { $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; } } return $_ciphertext; '; $decrypt = $init_encrypt . ' $_plaintext = ""; $_ciphertext_len = strlen($_text); $_xor = $self->decryptIV; $_buffer = &$self->debuffer; if (strlen($_buffer["xor"])) { for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $_block = substr($_text, $_i, '.$block_size.'); if (strlen($_block) > strlen($_buffer["xor"])) { $in = $_xor; '.$encrypt_block.' $_xor = $in; $_buffer["xor"].= $_xor; } $_key = $self->_string_shift($_buffer["xor"], '.$block_size.'); $_plaintext.= $_block ^ $_key; } } else { for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $in = $_xor; '.$encrypt_block.' $_xor = $in; $_plaintext.= substr($_text, $_i, '.$block_size.') ^ $_xor; } $_key = $_xor; } if ($self->continuousBuffer) { $self->decryptIV = $_xor; if ($_start = $_ciphertext_len % '.$block_size.') { $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"]; } } return $_plaintext; '; break; case self::MODE_STREAM: $encrypt = $init_encrypt . ' $_ciphertext = ""; '.$encrypt_block.' return $_ciphertext; '; $decrypt = $init_decrypt . ' $_plaintext = ""; '.$decrypt_block.' return $_plaintext; '; break; default: $encrypt = $init_encrypt . ' $_ciphertext = ""; $_plaintext_len = strlen($_text); $in = $self->encryptIV; for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') { $in = substr($_text, $_i, '.$block_size.') ^ $in; '.$encrypt_block.' $_ciphertext.= $in; } if ($self->continuousBuffer) { $self->encryptIV = $in; } return $_ciphertext; '; $decrypt = $init_decrypt . ' $_plaintext = ""; $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0)); $_ciphertext_len = strlen($_text); $_iv = $self->decryptIV; for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') { $in = $_block = substr($_text, $_i, '.$block_size.'); '.$decrypt_block.' $_plaintext.= $in ^ $_iv; $_iv = $_block; } if ($self->continuousBuffer) { $self->decryptIV = $_iv; } return $self->_unpad($_plaintext); '; break; } eval('$func = function ($_action, &$self, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' } };'); return $func; } function &_getLambdaFunctions() { static $functions = array(); return $functions; } function _hashInlineCryptFunction($bytes) { if (!isset(self::$WHIRLPOOL_AVAILABLE)) { self::$WHIRLPOOL_AVAILABLE = extension_loaded('hash') && in_array('whirlpool', hash_algos()); } $result = ''; $hash = $bytes; switch (true) { case self::$WHIRLPOOL_AVAILABLE: foreach (str_split($bytes, 64) as $t) { $hash = hash('whirlpool', $hash, true); $result .= $t ^ $hash; } return $result . hash('whirlpool', $hash, true); default: $len = strlen($bytes); for ($i = 0; $i < $len; $i+=20) { $t = substr($bytes, $i, 20); $hash = pack('H*', sha1($hash)); $result .= $t ^ $hash; } return $result . pack('H*', sha1($hash)); } } function safe_intval($x) { switch (true) { case is_int($x): case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': return $x; } return (fmod($x, 0x80000000) & 0x7FFFFFFF) | ((fmod(floor($x / 0x80000000), 2) & 1) << 31); } function safe_intval_inline() { switch (true) { case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8: case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': return '%s'; break; default: $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | '; return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))'; } } function do_nothing() { } } mode_3cbc = true; $this->des = array( new DES(Base::MODE_CBC), new DES(Base::MODE_CBC), new DES(Base::MODE_CBC), ); $this->des[0]->disablePadding(); $this->des[1]->disablePadding(); $this->des[2]->disablePadding(); break; default: parent::__construct($mode); } } function isValidEngine($engine) { if ($engine == self::ENGINE_OPENSSL) { $this->cipher_name_openssl_ecb = 'des-ede3'; $mode = $this->_openssl_translate_mode(); $this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode; } return parent::isValidEngine($engine); } function setIV($iv) { parent::setIV($iv); if ($this->mode_3cbc) { $this->des[0]->setIV($iv); $this->des[1]->setIV($iv); $this->des[2]->setIV($iv); } } function setKeyLength($length) { $length >>= 3; switch (true) { case $length <= 8: $this->key_length = 8; break; case $length <= 16: $this->key_length = 16; break; default: $this->key_length = 24; } parent::setKeyLength($length); } function setKey($key) { $length = $this->explicit_key_length ? $this->key_length : strlen($key); if ($length > 8) { $key = str_pad(substr($key, 0, 24), 24, chr(0)); $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); } else { $key = str_pad($key, 8, chr(0)); } parent::setKey($key); if ($this->mode_3cbc && $length > 8) { $this->des[0]->setKey(substr($key, 0, 8)); $this->des[1]->setKey(substr($key, 8, 8)); $this->des[2]->setKey(substr($key, 16, 8)); } } function encrypt($plaintext) { if ($this->mode_3cbc && strlen($this->key) > 8) { return $this->des[2]->encrypt( $this->des[1]->decrypt( $this->des[0]->encrypt( $this->_pad($plaintext) ) ) ); } return parent::encrypt($plaintext); } function decrypt($ciphertext) { if ($this->mode_3cbc && strlen($this->key) > 8) { return $this->_unpad( $this->des[0]->decrypt( $this->des[1]->encrypt( $this->des[2]->decrypt( str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, "\0") ) ) ) ); } return parent::decrypt($ciphertext); } function enableContinuousBuffer() { parent::enableContinuousBuffer(); if ($this->mode_3cbc) { $this->des[0]->enableContinuousBuffer(); $this->des[1]->enableContinuousBuffer(); $this->des[2]->enableContinuousBuffer(); } } function disableContinuousBuffer() { parent::disableContinuousBuffer(); if ($this->mode_3cbc) { $this->des[0]->disableContinuousBuffer(); $this->des[1]->disableContinuousBuffer(); $this->des[2]->disableContinuousBuffer(); } } function _setupKey() { switch (true) { case strlen($this->key) <= 8: $this->des_rounds = 1; break; default: $this->des_rounds = 3; if ($this->mode_3cbc) { $this->des[0]->_setupKey(); $this->des[1]->_setupKey(); $this->des[2]->_setupKey(); return; } } parent::_setupKey(); } function setPreferredEngine($engine) { if ($this->mode_3cbc) { $this->des[0]->setPreferredEngine($engine); $this->des[1]->setPreferredEngine($engine); $this->des[2]->setPreferredEngine($engine); } return parent::setPreferredEngine($engine); } } 'des-ecb', self::MODE_CBC => 'des-cbc', self::MODE_CFB => 'des-cfb', self::MODE_OFB => 'des-ofb' ); var $cfb_init_len = 500; var $des_rounds = 1; var $key_length_max = 8; var $keys; var $shuffle = array( "\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\xFF", "\x00\x00\x00\x00\x00\x00\xFF\x00", "\x00\x00\x00\x00\x00\x00\xFF\xFF", "\x00\x00\x00\x00\x00\xFF\x00\x00", "\x00\x00\x00\x00\x00\xFF\x00\xFF", "\x00\x00\x00\x00\x00\xFF\xFF\x00", "\x00\x00\x00\x00\x00\xFF\xFF\xFF", "\x00\x00\x00\x00\xFF\x00\x00\x00", "\x00\x00\x00\x00\xFF\x00\x00\xFF", "\x00\x00\x00\x00\xFF\x00\xFF\x00", "\x00\x00\x00\x00\xFF\x00\xFF\xFF", "\x00\x00\x00\x00\xFF\xFF\x00\x00", "\x00\x00\x00\x00\xFF\xFF\x00\xFF", "\x00\x00\x00\x00\xFF\xFF\xFF\x00", "\x00\x00\x00\x00\xFF\xFF\xFF\xFF", "\x00\x00\x00\xFF\x00\x00\x00\x00", "\x00\x00\x00\xFF\x00\x00\x00\xFF", "\x00\x00\x00\xFF\x00\x00\xFF\x00", "\x00\x00\x00\xFF\x00\x00\xFF\xFF", "\x00\x00\x00\xFF\x00\xFF\x00\x00", "\x00\x00\x00\xFF\x00\xFF\x00\xFF", "\x00\x00\x00\xFF\x00\xFF\xFF\x00", "\x00\x00\x00\xFF\x00\xFF\xFF\xFF", "\x00\x00\x00\xFF\xFF\x00\x00\x00", "\x00\x00\x00\xFF\xFF\x00\x00\xFF", "\x00\x00\x00\xFF\xFF\x00\xFF\x00", "\x00\x00\x00\xFF\xFF\x00\xFF\xFF", "\x00\x00\x00\xFF\xFF\xFF\x00\x00", "\x00\x00\x00\xFF\xFF\xFF\x00\xFF", "\x00\x00\x00\xFF\xFF\xFF\xFF\x00", "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF", "\x00\x00\xFF\x00\x00\x00\x00\x00", "\x00\x00\xFF\x00\x00\x00\x00\xFF", "\x00\x00\xFF\x00\x00\x00\xFF\x00", "\x00\x00\xFF\x00\x00\x00\xFF\xFF", "\x00\x00\xFF\x00\x00\xFF\x00\x00", "\x00\x00\xFF\x00\x00\xFF\x00\xFF", "\x00\x00\xFF\x00\x00\xFF\xFF\x00", "\x00\x00\xFF\x00\x00\xFF\xFF\xFF", "\x00\x00\xFF\x00\xFF\x00\x00\x00", "\x00\x00\xFF\x00\xFF\x00\x00\xFF", "\x00\x00\xFF\x00\xFF\x00\xFF\x00", "\x00\x00\xFF\x00\xFF\x00\xFF\xFF", "\x00\x00\xFF\x00\xFF\xFF\x00\x00", "\x00\x00\xFF\x00\xFF\xFF\x00\xFF", "\x00\x00\xFF\x00\xFF\xFF\xFF\x00", "\x00\x00\xFF\x00\xFF\xFF\xFF\xFF", "\x00\x00\xFF\xFF\x00\x00\x00\x00", "\x00\x00\xFF\xFF\x00\x00\x00\xFF", "\x00\x00\xFF\xFF\x00\x00\xFF\x00", "\x00\x00\xFF\xFF\x00\x00\xFF\xFF", "\x00\x00\xFF\xFF\x00\xFF\x00\x00", "\x00\x00\xFF\xFF\x00\xFF\x00\xFF", "\x00\x00\xFF\xFF\x00\xFF\xFF\x00", "\x00\x00\xFF\xFF\x00\xFF\xFF\xFF", "\x00\x00\xFF\xFF\xFF\x00\x00\x00", "\x00\x00\xFF\xFF\xFF\x00\x00\xFF", "\x00\x00\xFF\xFF\xFF\x00\xFF\x00", "\x00\x00\xFF\xFF\xFF\x00\xFF\xFF", "\x00\x00\xFF\xFF\xFF\xFF\x00\x00", "\x00\x00\xFF\xFF\xFF\xFF\x00\xFF", "\x00\x00\xFF\xFF\xFF\xFF\xFF\x00", "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF", "\x00\xFF\x00\x00\x00\x00\x00\x00", "\x00\xFF\x00\x00\x00\x00\x00\xFF", "\x00\xFF\x00\x00\x00\x00\xFF\x00", "\x00\xFF\x00\x00\x00\x00\xFF\xFF", "\x00\xFF\x00\x00\x00\xFF\x00\x00", "\x00\xFF\x00\x00\x00\xFF\x00\xFF", "\x00\xFF\x00\x00\x00\xFF\xFF\x00", "\x00\xFF\x00\x00\x00\xFF\xFF\xFF", "\x00\xFF\x00\x00\xFF\x00\x00\x00", "\x00\xFF\x00\x00\xFF\x00\x00\xFF", "\x00\xFF\x00\x00\xFF\x00\xFF\x00", "\x00\xFF\x00\x00\xFF\x00\xFF\xFF", "\x00\xFF\x00\x00\xFF\xFF\x00\x00", "\x00\xFF\x00\x00\xFF\xFF\x00\xFF", "\x00\xFF\x00\x00\xFF\xFF\xFF\x00", "\x00\xFF\x00\x00\xFF\xFF\xFF\xFF", "\x00\xFF\x00\xFF\x00\x00\x00\x00", "\x00\xFF\x00\xFF\x00\x00\x00\xFF", "\x00\xFF\x00\xFF\x00\x00\xFF\x00", "\x00\xFF\x00\xFF\x00\x00\xFF\xFF", "\x00\xFF\x00\xFF\x00\xFF\x00\x00", "\x00\xFF\x00\xFF\x00\xFF\x00\xFF", "\x00\xFF\x00\xFF\x00\xFF\xFF\x00", "\x00\xFF\x00\xFF\x00\xFF\xFF\xFF", "\x00\xFF\x00\xFF\xFF\x00\x00\x00", "\x00\xFF\x00\xFF\xFF\x00\x00\xFF", "\x00\xFF\x00\xFF\xFF\x00\xFF\x00", "\x00\xFF\x00\xFF\xFF\x00\xFF\xFF", "\x00\xFF\x00\xFF\xFF\xFF\x00\x00", "\x00\xFF\x00\xFF\xFF\xFF\x00\xFF", "\x00\xFF\x00\xFF\xFF\xFF\xFF\x00", "\x00\xFF\x00\xFF\xFF\xFF\xFF\xFF", "\x00\xFF\xFF\x00\x00\x00\x00\x00", "\x00\xFF\xFF\x00\x00\x00\x00\xFF", "\x00\xFF\xFF\x00\x00\x00\xFF\x00", "\x00\xFF\xFF\x00\x00\x00\xFF\xFF", "\x00\xFF\xFF\x00\x00\xFF\x00\x00", "\x00\xFF\xFF\x00\x00\xFF\x00\xFF", "\x00\xFF\xFF\x00\x00\xFF\xFF\x00", "\x00\xFF\xFF\x00\x00\xFF\xFF\xFF", "\x00\xFF\xFF\x00\xFF\x00\x00\x00", "\x00\xFF\xFF\x00\xFF\x00\x00\xFF", "\x00\xFF\xFF\x00\xFF\x00\xFF\x00", "\x00\xFF\xFF\x00\xFF\x00\xFF\xFF", "\x00\xFF\xFF\x00\xFF\xFF\x00\x00", "\x00\xFF\xFF\x00\xFF\xFF\x00\xFF", "\x00\xFF\xFF\x00\xFF\xFF\xFF\x00", "\x00\xFF\xFF\x00\xFF\xFF\xFF\xFF", "\x00\xFF\xFF\xFF\x00\x00\x00\x00", "\x00\xFF\xFF\xFF\x00\x00\x00\xFF", "\x00\xFF\xFF\xFF\x00\x00\xFF\x00", "\x00\xFF\xFF\xFF\x00\x00\xFF\xFF", "\x00\xFF\xFF\xFF\x00\xFF\x00\x00", "\x00\xFF\xFF\xFF\x00\xFF\x00\xFF", "\x00\xFF\xFF\xFF\x00\xFF\xFF\x00", "\x00\xFF\xFF\xFF\x00\xFF\xFF\xFF", "\x00\xFF\xFF\xFF\xFF\x00\x00\x00", "\x00\xFF\xFF\xFF\xFF\x00\x00\xFF", "\x00\xFF\xFF\xFF\xFF\x00\xFF\x00", "\x00\xFF\xFF\xFF\xFF\x00\xFF\xFF", "\x00\xFF\xFF\xFF\xFF\xFF\x00\x00", "\x00\xFF\xFF\xFF\xFF\xFF\x00\xFF", "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF", "\xFF\x00\x00\x00\x00\x00\x00\x00", "\xFF\x00\x00\x00\x00\x00\x00\xFF", "\xFF\x00\x00\x00\x00\x00\xFF\x00", "\xFF\x00\x00\x00\x00\x00\xFF\xFF", "\xFF\x00\x00\x00\x00\xFF\x00\x00", "\xFF\x00\x00\x00\x00\xFF\x00\xFF", "\xFF\x00\x00\x00\x00\xFF\xFF\x00", "\xFF\x00\x00\x00\x00\xFF\xFF\xFF", "\xFF\x00\x00\x00\xFF\x00\x00\x00", "\xFF\x00\x00\x00\xFF\x00\x00\xFF", "\xFF\x00\x00\x00\xFF\x00\xFF\x00", "\xFF\x00\x00\x00\xFF\x00\xFF\xFF", "\xFF\x00\x00\x00\xFF\xFF\x00\x00", "\xFF\x00\x00\x00\xFF\xFF\x00\xFF", "\xFF\x00\x00\x00\xFF\xFF\xFF\x00", "\xFF\x00\x00\x00\xFF\xFF\xFF\xFF", "\xFF\x00\x00\xFF\x00\x00\x00\x00", "\xFF\x00\x00\xFF\x00\x00\x00\xFF", "\xFF\x00\x00\xFF\x00\x00\xFF\x00", "\xFF\x00\x00\xFF\x00\x00\xFF\xFF", "\xFF\x00\x00\xFF\x00\xFF\x00\x00", "\xFF\x00\x00\xFF\x00\xFF\x00\xFF", "\xFF\x00\x00\xFF\x00\xFF\xFF\x00", "\xFF\x00\x00\xFF\x00\xFF\xFF\xFF", "\xFF\x00\x00\xFF\xFF\x00\x00\x00", "\xFF\x00\x00\xFF\xFF\x00\x00\xFF", "\xFF\x00\x00\xFF\xFF\x00\xFF\x00", "\xFF\x00\x00\xFF\xFF\x00\xFF\xFF", "\xFF\x00\x00\xFF\xFF\xFF\x00\x00", "\xFF\x00\x00\xFF\xFF\xFF\x00\xFF", "\xFF\x00\x00\xFF\xFF\xFF\xFF\x00", "\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF", "\xFF\x00\xFF\x00\x00\x00\x00\x00", "\xFF\x00\xFF\x00\x00\x00\x00\xFF", "\xFF\x00\xFF\x00\x00\x00\xFF\x00", "\xFF\x00\xFF\x00\x00\x00\xFF\xFF", "\xFF\x00\xFF\x00\x00\xFF\x00\x00", "\xFF\x00\xFF\x00\x00\xFF\x00\xFF", "\xFF\x00\xFF\x00\x00\xFF\xFF\x00", "\xFF\x00\xFF\x00\x00\xFF\xFF\xFF", "\xFF\x00\xFF\x00\xFF\x00\x00\x00", "\xFF\x00\xFF\x00\xFF\x00\x00\xFF", "\xFF\x00\xFF\x00\xFF\x00\xFF\x00", "\xFF\x00\xFF\x00\xFF\x00\xFF\xFF", "\xFF\x00\xFF\x00\xFF\xFF\x00\x00", "\xFF\x00\xFF\x00\xFF\xFF\x00\xFF", "\xFF\x00\xFF\x00\xFF\xFF\xFF\x00", "\xFF\x00\xFF\x00\xFF\xFF\xFF\xFF", "\xFF\x00\xFF\xFF\x00\x00\x00\x00", "\xFF\x00\xFF\xFF\x00\x00\x00\xFF", "\xFF\x00\xFF\xFF\x00\x00\xFF\x00", "\xFF\x00\xFF\xFF\x00\x00\xFF\xFF", "\xFF\x00\xFF\xFF\x00\xFF\x00\x00", "\xFF\x00\xFF\xFF\x00\xFF\x00\xFF", "\xFF\x00\xFF\xFF\x00\xFF\xFF\x00", "\xFF\x00\xFF\xFF\x00\xFF\xFF\xFF", "\xFF\x00\xFF\xFF\xFF\x00\x00\x00", "\xFF\x00\xFF\xFF\xFF\x00\x00\xFF", "\xFF\x00\xFF\xFF\xFF\x00\xFF\x00", "\xFF\x00\xFF\xFF\xFF\x00\xFF\xFF", "\xFF\x00\xFF\xFF\xFF\xFF\x00\x00", "\xFF\x00\xFF\xFF\xFF\xFF\x00\xFF", "\xFF\x00\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF", "\xFF\xFF\x00\x00\x00\x00\x00\x00", "\xFF\xFF\x00\x00\x00\x00\x00\xFF", "\xFF\xFF\x00\x00\x00\x00\xFF\x00", "\xFF\xFF\x00\x00\x00\x00\xFF\xFF", "\xFF\xFF\x00\x00\x00\xFF\x00\x00", "\xFF\xFF\x00\x00\x00\xFF\x00\xFF", "\xFF\xFF\x00\x00\x00\xFF\xFF\x00", "\xFF\xFF\x00\x00\x00\xFF\xFF\xFF", "\xFF\xFF\x00\x00\xFF\x00\x00\x00", "\xFF\xFF\x00\x00\xFF\x00\x00\xFF", "\xFF\xFF\x00\x00\xFF\x00\xFF\x00", "\xFF\xFF\x00\x00\xFF\x00\xFF\xFF", "\xFF\xFF\x00\x00\xFF\xFF\x00\x00", "\xFF\xFF\x00\x00\xFF\xFF\x00\xFF", "\xFF\xFF\x00\x00\xFF\xFF\xFF\x00", "\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF", "\xFF\xFF\x00\xFF\x00\x00\x00\x00", "\xFF\xFF\x00\xFF\x00\x00\x00\xFF", "\xFF\xFF\x00\xFF\x00\x00\xFF\x00", "\xFF\xFF\x00\xFF\x00\x00\xFF\xFF", "\xFF\xFF\x00\xFF\x00\xFF\x00\x00", "\xFF\xFF\x00\xFF\x00\xFF\x00\xFF", "\xFF\xFF\x00\xFF\x00\xFF\xFF\x00", "\xFF\xFF\x00\xFF\x00\xFF\xFF\xFF", "\xFF\xFF\x00\xFF\xFF\x00\x00\x00", "\xFF\xFF\x00\xFF\xFF\x00\x00\xFF", "\xFF\xFF\x00\xFF\xFF\x00\xFF\x00", "\xFF\xFF\x00\xFF\xFF\x00\xFF\xFF", "\xFF\xFF\x00\xFF\xFF\xFF\x00\x00", "\xFF\xFF\x00\xFF\xFF\xFF\x00\xFF", "\xFF\xFF\x00\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF", "\xFF\xFF\xFF\x00\x00\x00\x00\x00", "\xFF\xFF\xFF\x00\x00\x00\x00\xFF", "\xFF\xFF\xFF\x00\x00\x00\xFF\x00", "\xFF\xFF\xFF\x00\x00\x00\xFF\xFF", "\xFF\xFF\xFF\x00\x00\xFF\x00\x00", "\xFF\xFF\xFF\x00\x00\xFF\x00\xFF", "\xFF\xFF\xFF\x00\x00\xFF\xFF\x00", "\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF", "\xFF\xFF\xFF\x00\xFF\x00\x00\x00", "\xFF\xFF\xFF\x00\xFF\x00\x00\xFF", "\xFF\xFF\xFF\x00\xFF\x00\xFF\x00", "\xFF\xFF\xFF\x00\xFF\x00\xFF\xFF", "\xFF\xFF\xFF\x00\xFF\xFF\x00\x00", "\xFF\xFF\xFF\x00\xFF\xFF\x00\xFF", "\xFF\xFF\xFF\x00\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF", "\xFF\xFF\xFF\xFF\x00\x00\x00\x00", "\xFF\xFF\xFF\xFF\x00\x00\x00\xFF", "\xFF\xFF\xFF\xFF\x00\x00\xFF\x00", "\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF", "\xFF\xFF\xFF\xFF\x00\xFF\x00\x00", "\xFF\xFF\xFF\xFF\x00\xFF\x00\xFF", "\xFF\xFF\xFF\xFF\x00\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF", "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", "\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" ); var $ipmap = array( 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x21, 0x31, 0x02, 0x12, 0x03, 0x13, 0x22, 0x32, 0x23, 0x33, 0x40, 0x50, 0x41, 0x51, 0x60, 0x70, 0x61, 0x71, 0x42, 0x52, 0x43, 0x53, 0x62, 0x72, 0x63, 0x73, 0x04, 0x14, 0x05, 0x15, 0x24, 0x34, 0x25, 0x35, 0x06, 0x16, 0x07, 0x17, 0x26, 0x36, 0x27, 0x37, 0x44, 0x54, 0x45, 0x55, 0x64, 0x74, 0x65, 0x75, 0x46, 0x56, 0x47, 0x57, 0x66, 0x76, 0x67, 0x77, 0x80, 0x90, 0x81, 0x91, 0xA0, 0xB0, 0xA1, 0xB1, 0x82, 0x92, 0x83, 0x93, 0xA2, 0xB2, 0xA3, 0xB3, 0xC0, 0xD0, 0xC1, 0xD1, 0xE0, 0xF0, 0xE1, 0xF1, 0xC2, 0xD2, 0xC3, 0xD3, 0xE2, 0xF2, 0xE3, 0xF3, 0x84, 0x94, 0x85, 0x95, 0xA4, 0xB4, 0xA5, 0xB5, 0x86, 0x96, 0x87, 0x97, 0xA6, 0xB6, 0xA7, 0xB7, 0xC4, 0xD4, 0xC5, 0xD5, 0xE4, 0xF4, 0xE5, 0xF5, 0xC6, 0xD6, 0xC7, 0xD7, 0xE6, 0xF6, 0xE7, 0xF7, 0x08, 0x18, 0x09, 0x19, 0x28, 0x38, 0x29, 0x39, 0x0A, 0x1A, 0x0B, 0x1B, 0x2A, 0x3A, 0x2B, 0x3B, 0x48, 0x58, 0x49, 0x59, 0x68, 0x78, 0x69, 0x79, 0x4A, 0x5A, 0x4B, 0x5B, 0x6A, 0x7A, 0x6B, 0x7B, 0x0C, 0x1C, 0x0D, 0x1D, 0x2C, 0x3C, 0x2D, 0x3D, 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, 0x4C, 0x5C, 0x4D, 0x5D, 0x6C, 0x7C, 0x6D, 0x7D, 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, 0x88, 0x98, 0x89, 0x99, 0xA8, 0xB8, 0xA9, 0xB9, 0x8A, 0x9A, 0x8B, 0x9B, 0xAA, 0xBA, 0xAB, 0xBB, 0xC8, 0xD8, 0xC9, 0xD9, 0xE8, 0xF8, 0xE9, 0xF9, 0xCA, 0xDA, 0xCB, 0xDB, 0xEA, 0xFA, 0xEB, 0xFB, 0x8C, 0x9C, 0x8D, 0x9D, 0xAC, 0xBC, 0xAD, 0xBD, 0x8E, 0x9E, 0x8F, 0x9F, 0xAE, 0xBE, 0xAF, 0xBF, 0xCC, 0xDC, 0xCD, 0xDD, 0xEC, 0xFC, 0xED, 0xFD, 0xCE, 0xDE, 0xCF, 0xDF, 0xEE, 0xFE, 0xEF, 0xFF ); var $invipmap = array( 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF ); var $sbox1 = array( 0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, 0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002, 0x00000202, 0x00800200, 0x00800200, 0x00008200, 0x00008200, 0x00808000, 0x00808000, 0x00800202, 0x00008002, 0x00800002, 0x00800002, 0x00008002, 0x00000000, 0x00000202, 0x00008202, 0x00800000, 0x00008000, 0x00808202, 0x00000002, 0x00808000, 0x00808200, 0x00800000, 0x00800000, 0x00000200, 0x00808002, 0x00008000, 0x00008200, 0x00800002, 0x00000200, 0x00000002, 0x00800202, 0x00008202, 0x00808202, 0x00008002, 0x00808000, 0x00800202, 0x00800002, 0x00000202, 0x00008202, 0x00808200, 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002 ); var $sbox2 = array( 0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, 0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010, 0x00084000, 0x00080010, 0x40004010, 0x00000000, 0x40000000, 0x00004000, 0x00084010, 0x40080000, 0x00080010, 0x40000010, 0x00000000, 0x00084000, 0x00004010, 0x40084000, 0x40080000, 0x00004010, 0x00000000, 0x00084010, 0x40080010, 0x00080000, 0x40004010, 0x40080000, 0x40084000, 0x00004000, 0x40080000, 0x40004000, 0x00000010, 0x40084010, 0x00084010, 0x00000010, 0x00004000, 0x40000000, 0x00004010, 0x40084000, 0x00080000, 0x40000010, 0x00080010, 0x40004010, 0x40000010, 0x00080010, 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000 ); var $sbox3 = array( 0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, 0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104, 0x04000000, 0x00000004, 0x04010100, 0x00000100, 0x00010100, 0x04010000, 0x04010004, 0x00010104, 0x04000104, 0x00010100, 0x00010000, 0x04000104, 0x00000004, 0x04010104, 0x00000100, 0x04000000, 0x04010100, 0x04000000, 0x00010004, 0x00000104, 0x00010000, 0x04010100, 0x04000100, 0x00000000, 0x00000100, 0x00010004, 0x04010104, 0x04000100, 0x04000004, 0x00000100, 0x00000000, 0x04010004, 0x04000104, 0x00010000, 0x04000000, 0x04010104, 0x00000004, 0x00010104, 0x00010100, 0x04000004, 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100 ); var $sbox4 = array( 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, 0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000, 0x80000000, 0x00001000, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x80001000, 0x00001040, 0x80400040, 0x80000000, 0x00001040, 0x00400040, 0x00001000, 0x00401040, 0x80401040, 0x80000040, 0x00400040, 0x80400000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00000000, 0x00401000, 0x00001040, 0x00400040, 0x80400040, 0x80000000, 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x80401040, 0x80000040, 0x80000000, 0x00001000, 0x80400000, 0x80001000, 0x00401040, 0x80400040, 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040 ); var $sbox5 = array( 0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, 0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000, 0x01000000, 0x20040000, 0x20040000, 0x00000000, 0x20000080, 0x21040080, 0x21040080, 0x01000080, 0x21040000, 0x20000080, 0x00000000, 0x21000000, 0x01040080, 0x01000000, 0x21000000, 0x00040080, 0x00040000, 0x21000080, 0x00000080, 0x01000000, 0x20000000, 0x01040000, 0x21000080, 0x20040080, 0x01000080, 0x20000000, 0x21040000, 0x01040080, 0x20040080, 0x00000080, 0x01000000, 0x21040000, 0x21040080, 0x00040080, 0x21000000, 0x21040080, 0x01040000, 0x00000000, 0x20040000, 0x21000000, 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080 ); var $sbox6 = array( 0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, 0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008, 0x00000000, 0x00200008, 0x10002008, 0x00002000, 0x00202000, 0x10002008, 0x00000008, 0x10200008, 0x10200008, 0x00000000, 0x00202008, 0x10202000, 0x00002008, 0x00202000, 0x10202000, 0x10000000, 0x10002000, 0x00000008, 0x10200008, 0x00202000, 0x10202008, 0x00200000, 0x00002008, 0x10000008, 0x00200000, 0x10002000, 0x10000000, 0x00002008, 0x10000008, 0x10202008, 0x00202000, 0x10200000, 0x00202008, 0x10202000, 0x00000000, 0x10200008, 0x00000008, 0x00002000, 0x10200000, 0x00202008, 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008 ); var $sbox7 = array( 0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, 0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401, 0x02000400, 0x00100401, 0x00100001, 0x02000400, 0x02000001, 0x02100000, 0x02100400, 0x00100001, 0x02100000, 0x00000400, 0x00000401, 0x02100401, 0x00100400, 0x00000001, 0x02000000, 0x00100400, 0x02000000, 0x00100400, 0x00100000, 0x02000401, 0x02000401, 0x02100001, 0x02100001, 0x00000001, 0x00100001, 0x02000000, 0x02000400, 0x00100000, 0x02100400, 0x00000401, 0x00100401, 0x02100400, 0x00000401, 0x02000001, 0x02100401, 0x02100000, 0x00100400, 0x00000000, 0x00000001, 0x02100401, 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001 ); var $sbox8 = array( 0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, 0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020, 0x08020000, 0x08000020, 0x08000800, 0x00000820, 0x00020800, 0x00020020, 0x08020020, 0x08020800, 0x00000820, 0x00000000, 0x00000000, 0x08020020, 0x08000020, 0x08000800, 0x00020820, 0x00020000, 0x00020820, 0x00020000, 0x08020800, 0x00000800, 0x00000020, 0x08020020, 0x00000800, 0x00020820, 0x08000800, 0x00000020, 0x08000020, 0x08020000, 0x08020020, 0x08000000, 0x00020000, 0x08000820, 0x00000000, 0x08020820, 0x00020020, 0x08000020, 0x08020000, 0x08000800, 0x08000820, 0x00000000, 0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800 ); function isValidEngine($engine) { if ($this->key_length_max == 8) { if ($engine == self::ENGINE_OPENSSL) { $this->cipher_name_openssl_ecb = 'des-ecb'; $this->cipher_name_openssl = 'des-' . $this->_openssl_translate_mode(); } } return parent::isValidEngine($engine); } function setKey($key) { if (strlen($key) > $this->key_length_max) { $key = substr($key, 0, $this->key_length_max); } parent::setKey($key); } function _encryptBlock($in) { return $this->_processBlock($in, self::ENCRYPT); } function _decryptBlock($in) { return $this->_processBlock($in, self::DECRYPT); } function _processBlock($block, $mode) { static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip; if (!$sbox1) { $sbox1 = array_map("intval", $this->sbox1); $sbox2 = array_map("intval", $this->sbox2); $sbox3 = array_map("intval", $this->sbox3); $sbox4 = array_map("intval", $this->sbox4); $sbox5 = array_map("intval", $this->sbox5); $sbox6 = array_map("intval", $this->sbox6); $sbox7 = array_map("intval", $this->sbox7); $sbox8 = array_map("intval", $this->sbox8); for ($i = 0; $i < 256; ++$i) { $shuffleip[] = $this->shuffle[$this->ipmap[$i]]; $shuffleinvip[] = $this->shuffle[$this->invipmap[$i]]; } } $keys = $this->keys[$mode]; $ki = -1; $t = unpack('Nl/Nr', $block); list($l, $r) = array($t['l'], $t['r']); $block = ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01"); $t = unpack('Nl/Nr', $block); list($l, $r) = array($t['l'], $t['r']); for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) { for ($i = 0; $i < 16; $i++) { $b1 = (($r >> 3) & 0x1FFFFFFF) ^ ($r << 29) ^ $keys[++$ki]; $b2 = (($r >> 31) & 0x00000001) ^ ($r << 1) ^ $keys[++$ki]; $t = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^ $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^ $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^ $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ $l; $l = $r; $r = $t; } $t = $l; $l = $r; $r = $t; } return ($shuffleinvip[($r >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | ($shuffleinvip[($l >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | ($shuffleinvip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | ($shuffleinvip[($l >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | ($shuffleinvip[($r >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | ($shuffleinvip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | ($shuffleinvip[ $r & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | ($shuffleinvip[ $l & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01"); } function _setupKey() { if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->des_rounds === $this->kl['des_rounds']) { return; } $this->kl = array('key' => $this->key, 'des_rounds' => $this->des_rounds); static $shifts = array( 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 ); static $pc1map = array( 0x00, 0x00, 0x08, 0x08, 0x04, 0x04, 0x0C, 0x0C, 0x02, 0x02, 0x0A, 0x0A, 0x06, 0x06, 0x0E, 0x0E, 0x10, 0x10, 0x18, 0x18, 0x14, 0x14, 0x1C, 0x1C, 0x12, 0x12, 0x1A, 0x1A, 0x16, 0x16, 0x1E, 0x1E, 0x20, 0x20, 0x28, 0x28, 0x24, 0x24, 0x2C, 0x2C, 0x22, 0x22, 0x2A, 0x2A, 0x26, 0x26, 0x2E, 0x2E, 0x30, 0x30, 0x38, 0x38, 0x34, 0x34, 0x3C, 0x3C, 0x32, 0x32, 0x3A, 0x3A, 0x36, 0x36, 0x3E, 0x3E, 0x40, 0x40, 0x48, 0x48, 0x44, 0x44, 0x4C, 0x4C, 0x42, 0x42, 0x4A, 0x4A, 0x46, 0x46, 0x4E, 0x4E, 0x50, 0x50, 0x58, 0x58, 0x54, 0x54, 0x5C, 0x5C, 0x52, 0x52, 0x5A, 0x5A, 0x56, 0x56, 0x5E, 0x5E, 0x60, 0x60, 0x68, 0x68, 0x64, 0x64, 0x6C, 0x6C, 0x62, 0x62, 0x6A, 0x6A, 0x66, 0x66, 0x6E, 0x6E, 0x70, 0x70, 0x78, 0x78, 0x74, 0x74, 0x7C, 0x7C, 0x72, 0x72, 0x7A, 0x7A, 0x76, 0x76, 0x7E, 0x7E, 0x80, 0x80, 0x88, 0x88, 0x84, 0x84, 0x8C, 0x8C, 0x82, 0x82, 0x8A, 0x8A, 0x86, 0x86, 0x8E, 0x8E, 0x90, 0x90, 0x98, 0x98, 0x94, 0x94, 0x9C, 0x9C, 0x92, 0x92, 0x9A, 0x9A, 0x96, 0x96, 0x9E, 0x9E, 0xA0, 0xA0, 0xA8, 0xA8, 0xA4, 0xA4, 0xAC, 0xAC, 0xA2, 0xA2, 0xAA, 0xAA, 0xA6, 0xA6, 0xAE, 0xAE, 0xB0, 0xB0, 0xB8, 0xB8, 0xB4, 0xB4, 0xBC, 0xBC, 0xB2, 0xB2, 0xBA, 0xBA, 0xB6, 0xB6, 0xBE, 0xBE, 0xC0, 0xC0, 0xC8, 0xC8, 0xC4, 0xC4, 0xCC, 0xCC, 0xC2, 0xC2, 0xCA, 0xCA, 0xC6, 0xC6, 0xCE, 0xCE, 0xD0, 0xD0, 0xD8, 0xD8, 0xD4, 0xD4, 0xDC, 0xDC, 0xD2, 0xD2, 0xDA, 0xDA, 0xD6, 0xD6, 0xDE, 0xDE, 0xE0, 0xE0, 0xE8, 0xE8, 0xE4, 0xE4, 0xEC, 0xEC, 0xE2, 0xE2, 0xEA, 0xEA, 0xE6, 0xE6, 0xEE, 0xEE, 0xF0, 0xF0, 0xF8, 0xF8, 0xF4, 0xF4, 0xFC, 0xFC, 0xF2, 0xF2, 0xFA, 0xFA, 0xF6, 0xF6, 0xFE, 0xFE ); static $pc2mapc1 = array( 0x00000000, 0x00000400, 0x00200000, 0x00200400, 0x00000001, 0x00000401, 0x00200001, 0x00200401, 0x02000000, 0x02000400, 0x02200000, 0x02200400, 0x02000001, 0x02000401, 0x02200001, 0x02200401 ); static $pc2mapc2 = array( 0x00000000, 0x00000800, 0x08000000, 0x08000800, 0x00010000, 0x00010800, 0x08010000, 0x08010800, 0x00000000, 0x00000800, 0x08000000, 0x08000800, 0x00010000, 0x00010800, 0x08010000, 0x08010800, 0x00000100, 0x00000900, 0x08000100, 0x08000900, 0x00010100, 0x00010900, 0x08010100, 0x08010900, 0x00000100, 0x00000900, 0x08000100, 0x08000900, 0x00010100, 0x00010900, 0x08010100, 0x08010900, 0x00000010, 0x00000810, 0x08000010, 0x08000810, 0x00010010, 0x00010810, 0x08010010, 0x08010810, 0x00000010, 0x00000810, 0x08000010, 0x08000810, 0x00010010, 0x00010810, 0x08010010, 0x08010810, 0x00000110, 0x00000910, 0x08000110, 0x08000910, 0x00010110, 0x00010910, 0x08010110, 0x08010910, 0x00000110, 0x00000910, 0x08000110, 0x08000910, 0x00010110, 0x00010910, 0x08010110, 0x08010910, 0x00040000, 0x00040800, 0x08040000, 0x08040800, 0x00050000, 0x00050800, 0x08050000, 0x08050800, 0x00040000, 0x00040800, 0x08040000, 0x08040800, 0x00050000, 0x00050800, 0x08050000, 0x08050800, 0x00040100, 0x00040900, 0x08040100, 0x08040900, 0x00050100, 0x00050900, 0x08050100, 0x08050900, 0x00040100, 0x00040900, 0x08040100, 0x08040900, 0x00050100, 0x00050900, 0x08050100, 0x08050900, 0x00040010, 0x00040810, 0x08040010, 0x08040810, 0x00050010, 0x00050810, 0x08050010, 0x08050810, 0x00040010, 0x00040810, 0x08040010, 0x08040810, 0x00050010, 0x00050810, 0x08050010, 0x08050810, 0x00040110, 0x00040910, 0x08040110, 0x08040910, 0x00050110, 0x00050910, 0x08050110, 0x08050910, 0x00040110, 0x00040910, 0x08040110, 0x08040910, 0x00050110, 0x00050910, 0x08050110, 0x08050910, 0x01000000, 0x01000800, 0x09000000, 0x09000800, 0x01010000, 0x01010800, 0x09010000, 0x09010800, 0x01000000, 0x01000800, 0x09000000, 0x09000800, 0x01010000, 0x01010800, 0x09010000, 0x09010800, 0x01000100, 0x01000900, 0x09000100, 0x09000900, 0x01010100, 0x01010900, 0x09010100, 0x09010900, 0x01000100, 0x01000900, 0x09000100, 0x09000900, 0x01010100, 0x01010900, 0x09010100, 0x09010900, 0x01000010, 0x01000810, 0x09000010, 0x09000810, 0x01010010, 0x01010810, 0x09010010, 0x09010810, 0x01000010, 0x01000810, 0x09000010, 0x09000810, 0x01010010, 0x01010810, 0x09010010, 0x09010810, 0x01000110, 0x01000910, 0x09000110, 0x09000910, 0x01010110, 0x01010910, 0x09010110, 0x09010910, 0x01000110, 0x01000910, 0x09000110, 0x09000910, 0x01010110, 0x01010910, 0x09010110, 0x09010910, 0x01040000, 0x01040800, 0x09040000, 0x09040800, 0x01050000, 0x01050800, 0x09050000, 0x09050800, 0x01040000, 0x01040800, 0x09040000, 0x09040800, 0x01050000, 0x01050800, 0x09050000, 0x09050800, 0x01040100, 0x01040900, 0x09040100, 0x09040900, 0x01050100, 0x01050900, 0x09050100, 0x09050900, 0x01040100, 0x01040900, 0x09040100, 0x09040900, 0x01050100, 0x01050900, 0x09050100, 0x09050900, 0x01040010, 0x01040810, 0x09040010, 0x09040810, 0x01050010, 0x01050810, 0x09050010, 0x09050810, 0x01040010, 0x01040810, 0x09040010, 0x09040810, 0x01050010, 0x01050810, 0x09050010, 0x09050810, 0x01040110, 0x01040910, 0x09040110, 0x09040910, 0x01050110, 0x01050910, 0x09050110, 0x09050910, 0x01040110, 0x01040910, 0x09040110, 0x09040910, 0x01050110, 0x01050910, 0x09050110, 0x09050910 ); static $pc2mapc3 = array( 0x00000000, 0x00000004, 0x00001000, 0x00001004, 0x00000000, 0x00000004, 0x00001000, 0x00001004, 0x10000000, 0x10000004, 0x10001000, 0x10001004, 0x10000000, 0x10000004, 0x10001000, 0x10001004, 0x00000020, 0x00000024, 0x00001020, 0x00001024, 0x00000020, 0x00000024, 0x00001020, 0x00001024, 0x10000020, 0x10000024, 0x10001020, 0x10001024, 0x10000020, 0x10000024, 0x10001020, 0x10001024, 0x00080000, 0x00080004, 0x00081000, 0x00081004, 0x00080000, 0x00080004, 0x00081000, 0x00081004, 0x10080000, 0x10080004, 0x10081000, 0x10081004, 0x10080000, 0x10080004, 0x10081000, 0x10081004, 0x00080020, 0x00080024, 0x00081020, 0x00081024, 0x00080020, 0x00080024, 0x00081020, 0x00081024, 0x10080020, 0x10080024, 0x10081020, 0x10081024, 0x10080020, 0x10080024, 0x10081020, 0x10081024, 0x20000000, 0x20000004, 0x20001000, 0x20001004, 0x20000000, 0x20000004, 0x20001000, 0x20001004, 0x30000000, 0x30000004, 0x30001000, 0x30001004, 0x30000000, 0x30000004, 0x30001000, 0x30001004, 0x20000020, 0x20000024, 0x20001020, 0x20001024, 0x20000020, 0x20000024, 0x20001020, 0x20001024, 0x30000020, 0x30000024, 0x30001020, 0x30001024, 0x30000020, 0x30000024, 0x30001020, 0x30001024, 0x20080000, 0x20080004, 0x20081000, 0x20081004, 0x20080000, 0x20080004, 0x20081000, 0x20081004, 0x30080000, 0x30080004, 0x30081000, 0x30081004, 0x30080000, 0x30080004, 0x30081000, 0x30081004, 0x20080020, 0x20080024, 0x20081020, 0x20081024, 0x20080020, 0x20080024, 0x20081020, 0x20081024, 0x30080020, 0x30080024, 0x30081020, 0x30081024, 0x30080020, 0x30080024, 0x30081020, 0x30081024, 0x00000002, 0x00000006, 0x00001002, 0x00001006, 0x00000002, 0x00000006, 0x00001002, 0x00001006, 0x10000002, 0x10000006, 0x10001002, 0x10001006, 0x10000002, 0x10000006, 0x10001002, 0x10001006, 0x00000022, 0x00000026, 0x00001022, 0x00001026, 0x00000022, 0x00000026, 0x00001022, 0x00001026, 0x10000022, 0x10000026, 0x10001022, 0x10001026, 0x10000022, 0x10000026, 0x10001022, 0x10001026, 0x00080002, 0x00080006, 0x00081002, 0x00081006, 0x00080002, 0x00080006, 0x00081002, 0x00081006, 0x10080002, 0x10080006, 0x10081002, 0x10081006, 0x10080002, 0x10080006, 0x10081002, 0x10081006, 0x00080022, 0x00080026, 0x00081022, 0x00081026, 0x00080022, 0x00080026, 0x00081022, 0x00081026, 0x10080022, 0x10080026, 0x10081022, 0x10081026, 0x10080022, 0x10080026, 0x10081022, 0x10081026, 0x20000002, 0x20000006, 0x20001002, 0x20001006, 0x20000002, 0x20000006, 0x20001002, 0x20001006, 0x30000002, 0x30000006, 0x30001002, 0x30001006, 0x30000002, 0x30000006, 0x30001002, 0x30001006, 0x20000022, 0x20000026, 0x20001022, 0x20001026, 0x20000022, 0x20000026, 0x20001022, 0x20001026, 0x30000022, 0x30000026, 0x30001022, 0x30001026, 0x30000022, 0x30000026, 0x30001022, 0x30001026, 0x20080002, 0x20080006, 0x20081002, 0x20081006, 0x20080002, 0x20080006, 0x20081002, 0x20081006, 0x30080002, 0x30080006, 0x30081002, 0x30081006, 0x30080002, 0x30080006, 0x30081002, 0x30081006, 0x20080022, 0x20080026, 0x20081022, 0x20081026, 0x20080022, 0x20080026, 0x20081022, 0x20081026, 0x30080022, 0x30080026, 0x30081022, 0x30081026, 0x30080022, 0x30080026, 0x30081022, 0x30081026 ); static $pc2mapc4 = array( 0x00000000, 0x00100000, 0x00000008, 0x00100008, 0x00000200, 0x00100200, 0x00000208, 0x00100208, 0x00000000, 0x00100000, 0x00000008, 0x00100008, 0x00000200, 0x00100200, 0x00000208, 0x00100208, 0x04000000, 0x04100000, 0x04000008, 0x04100008, 0x04000200, 0x04100200, 0x04000208, 0x04100208, 0x04000000, 0x04100000, 0x04000008, 0x04100008, 0x04000200, 0x04100200, 0x04000208, 0x04100208, 0x00002000, 0x00102000, 0x00002008, 0x00102008, 0x00002200, 0x00102200, 0x00002208, 0x00102208, 0x00002000, 0x00102000, 0x00002008, 0x00102008, 0x00002200, 0x00102200, 0x00002208, 0x00102208, 0x04002000, 0x04102000, 0x04002008, 0x04102008, 0x04002200, 0x04102200, 0x04002208, 0x04102208, 0x04002000, 0x04102000, 0x04002008, 0x04102008, 0x04002200, 0x04102200, 0x04002208, 0x04102208, 0x00000000, 0x00100000, 0x00000008, 0x00100008, 0x00000200, 0x00100200, 0x00000208, 0x00100208, 0x00000000, 0x00100000, 0x00000008, 0x00100008, 0x00000200, 0x00100200, 0x00000208, 0x00100208, 0x04000000, 0x04100000, 0x04000008, 0x04100008, 0x04000200, 0x04100200, 0x04000208, 0x04100208, 0x04000000, 0x04100000, 0x04000008, 0x04100008, 0x04000200, 0x04100200, 0x04000208, 0x04100208, 0x00002000, 0x00102000, 0x00002008, 0x00102008, 0x00002200, 0x00102200, 0x00002208, 0x00102208, 0x00002000, 0x00102000, 0x00002008, 0x00102008, 0x00002200, 0x00102200, 0x00002208, 0x00102208, 0x04002000, 0x04102000, 0x04002008, 0x04102008, 0x04002200, 0x04102200, 0x04002208, 0x04102208, 0x04002000, 0x04102000, 0x04002008, 0x04102008, 0x04002200, 0x04102200, 0x04002208, 0x04102208, 0x00020000, 0x00120000, 0x00020008, 0x00120008, 0x00020200, 0x00120200, 0x00020208, 0x00120208, 0x00020000, 0x00120000, 0x00020008, 0x00120008, 0x00020200, 0x00120200, 0x00020208, 0x00120208, 0x04020000, 0x04120000, 0x04020008, 0x04120008, 0x04020200, 0x04120200, 0x04020208, 0x04120208, 0x04020000, 0x04120000, 0x04020008, 0x04120008, 0x04020200, 0x04120200, 0x04020208, 0x04120208, 0x00022000, 0x00122000, 0x00022008, 0x00122008, 0x00022200, 0x00122200, 0x00022208, 0x00122208, 0x00022000, 0x00122000, 0x00022008, 0x00122008, 0x00022200, 0x00122200, 0x00022208, 0x00122208, 0x04022000, 0x04122000, 0x04022008, 0x04122008, 0x04022200, 0x04122200, 0x04022208, 0x04122208, 0x04022000, 0x04122000, 0x04022008, 0x04122008, 0x04022200, 0x04122200, 0x04022208, 0x04122208, 0x00020000, 0x00120000, 0x00020008, 0x00120008, 0x00020200, 0x00120200, 0x00020208, 0x00120208, 0x00020000, 0x00120000, 0x00020008, 0x00120008, 0x00020200, 0x00120200, 0x00020208, 0x00120208, 0x04020000, 0x04120000, 0x04020008, 0x04120008, 0x04020200, 0x04120200, 0x04020208, 0x04120208, 0x04020000, 0x04120000, 0x04020008, 0x04120008, 0x04020200, 0x04120200, 0x04020208, 0x04120208, 0x00022000, 0x00122000, 0x00022008, 0x00122008, 0x00022200, 0x00122200, 0x00022208, 0x00122208, 0x00022000, 0x00122000, 0x00022008, 0x00122008, 0x00022200, 0x00122200, 0x00022208, 0x00122208, 0x04022000, 0x04122000, 0x04022008, 0x04122008, 0x04022200, 0x04122200, 0x04022208, 0x04122208, 0x04022000, 0x04122000, 0x04022008, 0x04122008, 0x04022200, 0x04122200, 0x04022208, 0x04122208 ); static $pc2mapd1 = array( 0x00000000, 0x00000001, 0x08000000, 0x08000001, 0x00200000, 0x00200001, 0x08200000, 0x08200001, 0x00000002, 0x00000003, 0x08000002, 0x08000003, 0x00200002, 0x00200003, 0x08200002, 0x08200003 ); static $pc2mapd2 = array( 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x04000000, 0x04100000, 0x04000800, 0x04100800, 0x04000000, 0x04100000, 0x04000800, 0x04100800, 0x00000004, 0x00100004, 0x00000804, 0x00100804, 0x00000004, 0x00100004, 0x00000804, 0x00100804, 0x04000004, 0x04100004, 0x04000804, 0x04100804, 0x04000004, 0x04100004, 0x04000804, 0x04100804, 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x00000000, 0x00100000, 0x00000800, 0x00100800, 0x04000000, 0x04100000, 0x04000800, 0x04100800, 0x04000000, 0x04100000, 0x04000800, 0x04100800, 0x00000004, 0x00100004, 0x00000804, 0x00100804, 0x00000004, 0x00100004, 0x00000804, 0x00100804, 0x04000004, 0x04100004, 0x04000804, 0x04100804, 0x04000004, 0x04100004, 0x04000804, 0x04100804, 0x00000200, 0x00100200, 0x00000A00, 0x00100A00, 0x00000200, 0x00100200, 0x00000A00, 0x00100A00, 0x04000200, 0x04100200, 0x04000A00, 0x04100A00, 0x04000200, 0x04100200, 0x04000A00, 0x04100A00, 0x00000204, 0x00100204, 0x00000A04, 0x00100A04, 0x00000204, 0x00100204, 0x00000A04, 0x00100A04, 0x04000204, 0x04100204, 0x04000A04, 0x04100A04, 0x04000204, 0x04100204, 0x04000A04, 0x04100A04, 0x00000200, 0x00100200, 0x00000A00, 0x00100A00, 0x00000200, 0x00100200, 0x00000A00, 0x00100A00, 0x04000200, 0x04100200, 0x04000A00, 0x04100A00, 0x04000200, 0x04100200, 0x04000A00, 0x04100A00, 0x00000204, 0x00100204, 0x00000A04, 0x00100A04, 0x00000204, 0x00100204, 0x00000A04, 0x00100A04, 0x04000204, 0x04100204, 0x04000A04, 0x04100A04, 0x04000204, 0x04100204, 0x04000A04, 0x04100A04, 0x00020000, 0x00120000, 0x00020800, 0x00120800, 0x00020000, 0x00120000, 0x00020800, 0x00120800, 0x04020000, 0x04120000, 0x04020800, 0x04120800, 0x04020000, 0x04120000, 0x04020800, 0x04120800, 0x00020004, 0x00120004, 0x00020804, 0x00120804, 0x00020004, 0x00120004, 0x00020804, 0x00120804, 0x04020004, 0x04120004, 0x04020804, 0x04120804, 0x04020004, 0x04120004, 0x04020804, 0x04120804, 0x00020000, 0x00120000, 0x00020800, 0x00120800, 0x00020000, 0x00120000, 0x00020800, 0x00120800, 0x04020000, 0x04120000, 0x04020800, 0x04120800, 0x04020000, 0x04120000, 0x04020800, 0x04120800, 0x00020004, 0x00120004, 0x00020804, 0x00120804, 0x00020004, 0x00120004, 0x00020804, 0x00120804, 0x04020004, 0x04120004, 0x04020804, 0x04120804, 0x04020004, 0x04120004, 0x04020804, 0x04120804, 0x00020200, 0x00120200, 0x00020A00, 0x00120A00, 0x00020200, 0x00120200, 0x00020A00, 0x00120A00, 0x04020200, 0x04120200, 0x04020A00, 0x04120A00, 0x04020200, 0x04120200, 0x04020A00, 0x04120A00, 0x00020204, 0x00120204, 0x00020A04, 0x00120A04, 0x00020204, 0x00120204, 0x00020A04, 0x00120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04, 0x00020200, 0x00120200, 0x00020A00, 0x00120A00, 0x00020200, 0x00120200, 0x00020A00, 0x00120A00, 0x04020200, 0x04120200, 0x04020A00, 0x04120A00, 0x04020200, 0x04120200, 0x04020A00, 0x04120A00, 0x00020204, 0x00120204, 0x00020A04, 0x00120A04, 0x00020204, 0x00120204, 0x00020A04, 0x00120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04, 0x04020204, 0x04120204, 0x04020A04, 0x04120A04 ); static $pc2mapd3 = array( 0x00000000, 0x00010000, 0x02000000, 0x02010000, 0x00000020, 0x00010020, 0x02000020, 0x02010020, 0x00040000, 0x00050000, 0x02040000, 0x02050000, 0x00040020, 0x00050020, 0x02040020, 0x02050020, 0x00002000, 0x00012000, 0x02002000, 0x02012000, 0x00002020, 0x00012020, 0x02002020, 0x02012020, 0x00042000, 0x00052000, 0x02042000, 0x02052000, 0x00042020, 0x00052020, 0x02042020, 0x02052020, 0x00000000, 0x00010000, 0x02000000, 0x02010000, 0x00000020, 0x00010020, 0x02000020, 0x02010020, 0x00040000, 0x00050000, 0x02040000, 0x02050000, 0x00040020, 0x00050020, 0x02040020, 0x02050020, 0x00002000, 0x00012000, 0x02002000, 0x02012000, 0x00002020, 0x00012020, 0x02002020, 0x02012020, 0x00042000, 0x00052000, 0x02042000, 0x02052000, 0x00042020, 0x00052020, 0x02042020, 0x02052020, 0x00000010, 0x00010010, 0x02000010, 0x02010010, 0x00000030, 0x00010030, 0x02000030, 0x02010030, 0x00040010, 0x00050010, 0x02040010, 0x02050010, 0x00040030, 0x00050030, 0x02040030, 0x02050030, 0x00002010, 0x00012010, 0x02002010, 0x02012010, 0x00002030, 0x00012030, 0x02002030, 0x02012030, 0x00042010, 0x00052010, 0x02042010, 0x02052010, 0x00042030, 0x00052030, 0x02042030, 0x02052030, 0x00000010, 0x00010010, 0x02000010, 0x02010010, 0x00000030, 0x00010030, 0x02000030, 0x02010030, 0x00040010, 0x00050010, 0x02040010, 0x02050010, 0x00040030, 0x00050030, 0x02040030, 0x02050030, 0x00002010, 0x00012010, 0x02002010, 0x02012010, 0x00002030, 0x00012030, 0x02002030, 0x02012030, 0x00042010, 0x00052010, 0x02042010, 0x02052010, 0x00042030, 0x00052030, 0x02042030, 0x02052030, 0x20000000, 0x20010000, 0x22000000, 0x22010000, 0x20000020, 0x20010020, 0x22000020, 0x22010020, 0x20040000, 0x20050000, 0x22040000, 0x22050000, 0x20040020, 0x20050020, 0x22040020, 0x22050020, 0x20002000, 0x20012000, 0x22002000, 0x22012000, 0x20002020, 0x20012020, 0x22002020, 0x22012020, 0x20042000, 0x20052000, 0x22042000, 0x22052000, 0x20042020, 0x20052020, 0x22042020, 0x22052020, 0x20000000, 0x20010000, 0x22000000, 0x22010000, 0x20000020, 0x20010020, 0x22000020, 0x22010020, 0x20040000, 0x20050000, 0x22040000, 0x22050000, 0x20040020, 0x20050020, 0x22040020, 0x22050020, 0x20002000, 0x20012000, 0x22002000, 0x22012000, 0x20002020, 0x20012020, 0x22002020, 0x22012020, 0x20042000, 0x20052000, 0x22042000, 0x22052000, 0x20042020, 0x20052020, 0x22042020, 0x22052020, 0x20000010, 0x20010010, 0x22000010, 0x22010010, 0x20000030, 0x20010030, 0x22000030, 0x22010030, 0x20040010, 0x20050010, 0x22040010, 0x22050010, 0x20040030, 0x20050030, 0x22040030, 0x22050030, 0x20002010, 0x20012010, 0x22002010, 0x22012010, 0x20002030, 0x20012030, 0x22002030, 0x22012030, 0x20042010, 0x20052010, 0x22042010, 0x22052010, 0x20042030, 0x20052030, 0x22042030, 0x22052030, 0x20000010, 0x20010010, 0x22000010, 0x22010010, 0x20000030, 0x20010030, 0x22000030, 0x22010030, 0x20040010, 0x20050010, 0x22040010, 0x22050010, 0x20040030, 0x20050030, 0x22040030, 0x22050030, 0x20002010, 0x20012010, 0x22002010, 0x22012010, 0x20002030, 0x20012030, 0x22002030, 0x22012030, 0x20042010, 0x20052010, 0x22042010, 0x22052010, 0x20042030, 0x20052030, 0x22042030, 0x22052030 ); static $pc2mapd4 = array( 0x00000000, 0x00000400, 0x01000000, 0x01000400, 0x00000000, 0x00000400, 0x01000000, 0x01000400, 0x00000100, 0x00000500, 0x01000100, 0x01000500, 0x00000100, 0x00000500, 0x01000100, 0x01000500, 0x10000000, 0x10000400, 0x11000000, 0x11000400, 0x10000000, 0x10000400, 0x11000000, 0x11000400, 0x10000100, 0x10000500, 0x11000100, 0x11000500, 0x10000100, 0x10000500, 0x11000100, 0x11000500, 0x00080000, 0x00080400, 0x01080000, 0x01080400, 0x00080000, 0x00080400, 0x01080000, 0x01080400, 0x00080100, 0x00080500, 0x01080100, 0x01080500, 0x00080100, 0x00080500, 0x01080100, 0x01080500, 0x10080000, 0x10080400, 0x11080000, 0x11080400, 0x10080000, 0x10080400, 0x11080000, 0x11080400, 0x10080100, 0x10080500, 0x11080100, 0x11080500, 0x10080100, 0x10080500, 0x11080100, 0x11080500, 0x00000008, 0x00000408, 0x01000008, 0x01000408, 0x00000008, 0x00000408, 0x01000008, 0x01000408, 0x00000108, 0x00000508, 0x01000108, 0x01000508, 0x00000108, 0x00000508, 0x01000108, 0x01000508, 0x10000008, 0x10000408, 0x11000008, 0x11000408, 0x10000008, 0x10000408, 0x11000008, 0x11000408, 0x10000108, 0x10000508, 0x11000108, 0x11000508, 0x10000108, 0x10000508, 0x11000108, 0x11000508, 0x00080008, 0x00080408, 0x01080008, 0x01080408, 0x00080008, 0x00080408, 0x01080008, 0x01080408, 0x00080108, 0x00080508, 0x01080108, 0x01080508, 0x00080108, 0x00080508, 0x01080108, 0x01080508, 0x10080008, 0x10080408, 0x11080008, 0x11080408, 0x10080008, 0x10080408, 0x11080008, 0x11080408, 0x10080108, 0x10080508, 0x11080108, 0x11080508, 0x10080108, 0x10080508, 0x11080108, 0x11080508, 0x00001000, 0x00001400, 0x01001000, 0x01001400, 0x00001000, 0x00001400, 0x01001000, 0x01001400, 0x00001100, 0x00001500, 0x01001100, 0x01001500, 0x00001100, 0x00001500, 0x01001100, 0x01001500, 0x10001000, 0x10001400, 0x11001000, 0x11001400, 0x10001000, 0x10001400, 0x11001000, 0x11001400, 0x10001100, 0x10001500, 0x11001100, 0x11001500, 0x10001100, 0x10001500, 0x11001100, 0x11001500, 0x00081000, 0x00081400, 0x01081000, 0x01081400, 0x00081000, 0x00081400, 0x01081000, 0x01081400, 0x00081100, 0x00081500, 0x01081100, 0x01081500, 0x00081100, 0x00081500, 0x01081100, 0x01081500, 0x10081000, 0x10081400, 0x11081000, 0x11081400, 0x10081000, 0x10081400, 0x11081000, 0x11081400, 0x10081100, 0x10081500, 0x11081100, 0x11081500, 0x10081100, 0x10081500, 0x11081100, 0x11081500, 0x00001008, 0x00001408, 0x01001008, 0x01001408, 0x00001008, 0x00001408, 0x01001008, 0x01001408, 0x00001108, 0x00001508, 0x01001108, 0x01001508, 0x00001108, 0x00001508, 0x01001108, 0x01001508, 0x10001008, 0x10001408, 0x11001008, 0x11001408, 0x10001008, 0x10001408, 0x11001008, 0x11001408, 0x10001108, 0x10001508, 0x11001108, 0x11001508, 0x10001108, 0x10001508, 0x11001108, 0x11001508, 0x00081008, 0x00081408, 0x01081008, 0x01081408, 0x00081008, 0x00081408, 0x01081008, 0x01081408, 0x00081108, 0x00081508, 0x01081108, 0x01081508, 0x00081108, 0x00081508, 0x01081108, 0x01081508, 0x10081008, 0x10081408, 0x11081008, 0x11081408, 0x10081008, 0x10081408, 0x11081008, 0x11081408, 0x10081108, 0x10081508, 0x11081108, 0x11081508, 0x10081108, 0x10081508, 0x11081108, 0x11081508 ); $keys = array(); for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) { $key = str_pad(substr($this->key, $des_round * 8, 8), 8, "\0"); $t = unpack('Nl/Nr', $key); list($l, $r) = array($t['l'], $t['r']); $key = ($this->shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") | ($this->shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") | ($this->shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") | ($this->shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") | ($this->shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") | ($this->shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") | ($this->shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") | ($this->shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00"); $key = unpack('Nc/Nd', $key); $c = ( $key['c'] >> 4) & 0x0FFFFFFF; $d = (($key['d'] >> 4) & 0x0FFFFFF0) | ($key['c'] & 0x0F); $keys[$des_round] = array( self::ENCRYPT => array(), self::DECRYPT => array_fill(0, 32, 0) ); for ($i = 0, $ki = 31; $i < 16; ++$i, $ki-= 2) { $c <<= $shifts[$i]; $c = ($c | ($c >> 28)) & 0x0FFFFFFF; $d <<= $shifts[$i]; $d = ($d | ($d >> 28)) & 0x0FFFFFFF; $cp = $pc2mapc1[ $c >> 24 ] | $pc2mapc2[($c >> 16) & 0xFF] | $pc2mapc3[($c >> 8) & 0xFF] | $pc2mapc4[ $c & 0xFF]; $dp = $pc2mapd1[ $d >> 24 ] | $pc2mapd2[($d >> 16) & 0xFF] | $pc2mapd3[($d >> 8) & 0xFF] | $pc2mapd4[ $d & 0xFF]; $val1 = ( $cp & 0xFF000000) | (($cp << 8) & 0x00FF0000) | (($dp >> 16) & 0x0000FF00) | (($dp >> 8) & 0x000000FF); $val2 = (($cp << 8) & 0xFF000000) | (($cp << 16) & 0x00FF0000) | (($dp >> 8) & 0x0000FF00) | ( $dp & 0x000000FF); $keys[$des_round][self::ENCRYPT][ ] = $val1; $keys[$des_round][self::DECRYPT][$ki - 1] = $val1; $keys[$des_round][self::ENCRYPT][ ] = $val2; $keys[$des_round][self::DECRYPT][$ki ] = $val2; } } switch ($this->des_rounds) { case 3: $this->keys = array( self::ENCRYPT => array_merge( $keys[0][self::ENCRYPT], $keys[1][self::DECRYPT], $keys[2][self::ENCRYPT] ), self::DECRYPT => array_merge( $keys[2][self::DECRYPT], $keys[1][self::ENCRYPT], $keys[0][self::DECRYPT] ) ); break; default: $this->keys = array( self::ENCRYPT => $keys[0][self::ENCRYPT], self::DECRYPT => $keys[0][self::DECRYPT] ); } } function _setupInlineCrypt() { $lambda_functions =& self::_getLambdaFunctions(); $des_rounds = $this->des_rounds; $gen_hi_opt_code = (bool)( count($lambda_functions) < 10 ); $code_hash = "Crypt_DES, $des_rounds, {$this->mode}"; if ($gen_hi_opt_code) { $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } if (!isset($lambda_functions[$code_hash])) { $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip; if (!$sbox1) { $sbox1 = array_map("intval", $self->sbox1); $sbox2 = array_map("intval", $self->sbox2); $sbox3 = array_map("intval", $self->sbox3); $sbox4 = array_map("intval", $self->sbox4); $sbox5 = array_map("intval", $self->sbox5); $sbox6 = array_map("intval", $self->sbox6); $sbox7 = array_map("intval", $self->sbox7); $sbox8 = array_map("intval", $self->sbox8);' . ' for ($i = 0; $i < 256; ++$i) { $shuffleip[] = $self->shuffle[$self->ipmap[$i]]; $shuffleinvip[] = $self->shuffle[$self->invipmap[$i]]; } } '; switch (true) { case $gen_hi_opt_code: $k = array( self::ENCRYPT => $this->keys[self::ENCRYPT], self::DECRYPT => $this->keys[self::DECRYPT] ); $init_encrypt = ''; $init_decrypt = ''; break; default: $k = array( self::ENCRYPT => array(), self::DECRYPT => array() ); for ($i = 0, $c = count($this->keys[self::ENCRYPT]); $i < $c; ++$i) { $k[self::ENCRYPT][$i] = '$ke[' . $i . ']'; $k[self::DECRYPT][$i] = '$kd[' . $i . ']'; } $init_encrypt = '$ke = $self->keys[$self::ENCRYPT];'; $init_decrypt = '$kd = $self->keys[$self::DECRYPT];'; break; } $crypt_block = array(); foreach (array(self::ENCRYPT, self::DECRYPT) as $c) { $crypt_block[$c] = ' $in = unpack("N*", $in); $l = $in[1]; $r = $in[2]; $in = unpack("N*", ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01") ); ' . ' $l = $in[1]; $r = $in[2]; '; $l = '$l'; $r = '$r'; for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) { for ($i = 0; $i < 16; ++$i) { $crypt_block[$c].= ' $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . '; $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' . $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^ $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^ $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^ $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . '; '; list($l, $r) = array($r, $l); } list($l, $r) = array($r, $l); } $crypt_block[$c].= '$in = ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") | ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") | ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") | ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") | ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") | ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") | ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") | ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01"); '; } $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( array( 'init_crypt' => $init_crypt, 'init_encrypt' => $init_encrypt, 'init_decrypt' => $init_decrypt, 'encrypt_block' => $crypt_block[self::ENCRYPT], 'decrypt_block' => $crypt_block[self::DECRYPT] ) ); } $this->inline_crypt = $lambda_functions[$code_hash]; } } configFile = dirname(__FILE__) . '/../openssl.cnf'; if (!defined('CRYPT_RSA_MODE')) { switch (true) { case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'): define('CRYPT_RSA_MODE', self::MODE_INTERNAL); break; case function_exists('phpinfo') && extension_loaded('openssl') && file_exists($this->configFile): $versions = array(); if (strpos(ini_get('disable_functions'), 'phpinfo') === false) { ob_start(); @phpinfo(); $content = ob_get_contents(); ob_end_clean(); preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); if (!empty($matches[1])) { for ($i = 0; $i < count($matches[1]); $i++) { $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { $versions[$matches[1][$i]] = $fullVersion; } else { $versions[$matches[1][$i]] = $m[0]; } } } } switch (true) { case !isset($versions['Header']): case !isset($versions['Library']): case $versions['Header'] == $versions['Library']: case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0: define('CRYPT_RSA_MODE', self::MODE_OPENSSL); break; default: define('CRYPT_RSA_MODE', self::MODE_INTERNAL); define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); } break; default: define('CRYPT_RSA_MODE', self::MODE_INTERNAL); } } $this->zero = new BigInteger(); $this->one = new BigInteger(1); $this->hash = new Hash('sha1'); $this->hLen = $this->hash->getLength(); $this->hashName = 'sha1'; $this->mgfHash = new Hash('sha1'); $this->mgfHLen = $this->mgfHash->getLength(); } function createKey($bits = 1024, $timeout = false, $partial = array()) { if (!defined('CRYPT_RSA_EXPONENT')) { define('CRYPT_RSA_EXPONENT', '65537'); } if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { define('CRYPT_RSA_SMALLEST_PRIME', 4096); } if (CRYPT_RSA_MODE == self::MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { $config = array(); if (isset($this->configFile)) { $config['config'] = $this->configFile; } $rsa = openssl_pkey_new(array('private_key_bits' => $bits) + $config); openssl_pkey_export($rsa, $privatekey, null, $config); $publickey = openssl_pkey_get_details($rsa); $publickey = $publickey['key']; $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, self::PRIVATE_FORMAT_PKCS1))); $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, self::PUBLIC_FORMAT_PKCS1))); while (openssl_error_string() !== false) { } return array( 'privatekey' => $privatekey, 'publickey' => $publickey, 'partialkey' => false ); } static $e; if (!isset($e)) { $e = new BigInteger(CRYPT_RSA_EXPONENT); } extract($this->_generateMinMax($bits)); $absoluteMin = $min; $temp = $bits >> 1; if ($temp > CRYPT_RSA_SMALLEST_PRIME) { $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); $temp = CRYPT_RSA_SMALLEST_PRIME; } else { $num_primes = 2; } extract($this->_generateMinMax($temp + $bits % $temp)); $finalMax = $max; extract($this->_generateMinMax($temp)); $generator = new BigInteger(); $n = $this->one->copy(); if (!empty($partial)) { extract(unserialize($partial)); } else { $exponents = $coefficients = $primes = array(); $lcm = array( 'top' => $this->one->copy(), 'bottom' => false ); } $start = time(); $i0 = count($primes) + 1; do { for ($i = $i0; $i <= $num_primes; $i++) { if ($timeout !== false) { $timeout-= time() - $start; $start = time(); if ($timeout <= 0) { return array( 'privatekey' => '', 'publickey' => '', 'partialkey' => serialize(array( 'primes' => $primes, 'coefficients' => $coefficients, 'lcm' => $lcm, 'exponents' => $exponents )) ); } } if ($i == $num_primes) { list($min, $temp) = $absoluteMin->divide($n); if (!$temp->equals($this->zero)) { $min = $min->add($this->one); } $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); } else { $primes[$i] = $generator->randomPrime($min, $max, $timeout); } if ($primes[$i] === false) { if (count($primes) > 1) { $partialkey = ''; } else { array_pop($primes); $partialkey = serialize(array( 'primes' => $primes, 'coefficients' => $coefficients, 'lcm' => $lcm, 'exponents' => $exponents )); } return array( 'privatekey' => '', 'publickey' => '', 'partialkey' => $partialkey ); } if ($i > 2) { $coefficients[$i] = $n->modInverse($primes[$i]); } $n = $n->multiply($primes[$i]); $temp = $primes[$i]->subtract($this->one); $lcm['top'] = $lcm['top']->multiply($temp); $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); $exponents[$i] = $e->modInverse($temp); } list($temp) = $lcm['top']->divide($lcm['bottom']); $gcd = $temp->gcd($e); $i0 = 1; } while (!$gcd->equals($this->one)); $d = $e->modInverse($temp); $coefficients[2] = $primes[2]->modInverse($primes[1]); return array( 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false ); } function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) { $signed = $this->privateKeyFormat != self::PRIVATE_FORMAT_XML; $num_primes = count($primes); $raw = array( 'version' => $num_primes == 2 ? chr(0) : chr(1), 'modulus' => $n->toBytes($signed), 'publicExponent' => $e->toBytes($signed), 'privateExponent' => $d->toBytes($signed), 'prime1' => $primes[1]->toBytes($signed), 'prime2' => $primes[2]->toBytes($signed), 'exponent1' => $exponents[1]->toBytes($signed), 'exponent2' => $exponents[2]->toBytes($signed), 'coefficient' => $coefficients[2]->toBytes($signed) ); switch ($this->privateKeyFormat) { case self::PRIVATE_FORMAT_XML: if ($num_primes != 2) { return false; } return "\r\n" . ' ' . base64_encode($raw['modulus']) . "\r\n" . ' ' . base64_encode($raw['publicExponent']) . "\r\n" . '

' . base64_encode($raw['prime1']) . "

\r\n" . ' ' . base64_encode($raw['prime2']) . "\r\n" . ' ' . base64_encode($raw['exponent1']) . "\r\n" . ' ' . base64_encode($raw['exponent2']) . "\r\n" . ' ' . base64_encode($raw['coefficient']) . "\r\n" . ' ' . base64_encode($raw['privateExponent']) . "\r\n" . '
'; break; case self::PRIVATE_FORMAT_PUTTY: if ($num_primes != 2) { return false; } $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: "; $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none'; $key.= $encryption; $key.= "\r\nComment: " . $this->comment . "\r\n"; $public = pack( 'Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus'] ); $source = pack( 'Na*Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($encryption), $encryption, strlen($this->comment), $this->comment, strlen($public), $public ); $public = base64_encode($public); $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n"; $key.= chunk_split($public, 64); $private = pack( 'Na*Na*Na*Na*', strlen($raw['privateExponent']), $raw['privateExponent'], strlen($raw['prime1']), $raw['prime1'], strlen($raw['prime2']), $raw['prime2'], strlen($raw['coefficient']), $raw['coefficient'] ); if (empty($this->password) && !is_string($this->password)) { $source.= pack('Na*', strlen($private), $private); $hashkey = 'putty-private-key-file-mac-key'; } else { $private.= Random::string(16 - (strlen($private) & 15)); $source.= pack('Na*', strlen($private), $private); $sequence = 0; $symkey = ''; while (strlen($symkey) < 32) { $temp = pack('Na*', $sequence++, $this->password); $symkey.= pack('H*', sha1($temp)); } $symkey = substr($symkey, 0, 32); $crypto = new AES(); $crypto->setKey($symkey); $crypto->disablePadding(); $private = $crypto->encrypt($private); $hashkey = 'putty-private-key-file-mac-key' . $this->password; } $private = base64_encode($private); $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n"; $key.= chunk_split($private, 64); $hash = new Hash('sha1'); $hash->setKey(pack('H*', sha1($hashkey))); $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n"; return $key; case self::PRIVATE_FORMAT_OPENSSH: if ($num_primes != 2) { return false; } $publicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus']); $privateKey = pack( 'Na*Na*Na*Na*Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['modulus']), $raw['modulus'], strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['privateExponent']), $raw['privateExponent'], strlen($raw['coefficient']), $raw['coefficient'], strlen($raw['prime1']), $raw['prime1'], strlen($raw['prime2']), $raw['prime2'] ); $checkint = Random::string(4); $paddedKey = pack( 'a*Na*', $checkint . $checkint . $privateKey, strlen($this->comment), $this->comment ); $paddingLength = (7 * strlen($paddedKey)) % 8; for ($i = 1; $i <= $paddingLength; $i++) { $paddedKey.= chr($i); } $key = pack( 'Na*Na*Na*NNa*Na*', strlen('none'), 'none', strlen('none'), 'none', 0, '', 1, strlen($publicKey), $publicKey, strlen($paddedKey), $paddedKey ); $key = "openssh-key-v1\0$key"; return "-----BEGIN OPENSSH PRIVATE KEY-----\n" . chunk_split(base64_encode($key), 70, "\n") . "-----END OPENSSH PRIVATE KEY-----\n"; default: $components = array(); foreach ($raw as $name => $value) { $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); } $RSAPrivateKey = implode('', $components); if ($num_primes > 2) { $OtherPrimeInfos = ''; for ($i = 3; $i <= $num_primes; $i++) { $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); } $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); } $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); if ($this->privateKeyFormat == self::PRIVATE_FORMAT_PKCS8) { $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); $RSAPrivateKey = pack( 'Ca*a*Ca*a*', self::ASN1_INTEGER, "\01\00", $rsaOID, 4, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey ); $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); if (!empty($this->password) || is_string($this->password)) { $salt = Random::string(8); $iterationCount = 2048; $crypto = new DES(); $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey); $parameters = pack( 'Ca*a*Ca*N', self::ASN1_OCTETSTRING, $this->_encodeLength(strlen($salt)), $salt, self::ASN1_INTEGER, $this->_encodeLength(4), $iterationCount ); $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03"; $encryptionAlgorithm = pack( 'Ca*a*Ca*a*', self::ASN1_OBJECT, $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)), $pbeWithMD5AndDES_CBC, self::ASN1_SEQUENCE, $this->_encodeLength(strlen($parameters)), $parameters ); $RSAPrivateKey = pack( 'Ca*a*Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($encryptionAlgorithm)), $encryptionAlgorithm, self::ASN1_OCTETSTRING, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey ); $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END ENCRYPTED PRIVATE KEY-----'; } else { $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END PRIVATE KEY-----'; } return $RSAPrivateKey; } if (!empty($this->password) || is_string($this->password)) { $iv = Random::string(8); $symkey = pack('H*', md5($this->password . $iv)); $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); $des = new TripleDES(); $des->setKey($symkey); $des->setIV($iv); $iv = strtoupper(bin2hex($iv)); $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . "Proc-Type: 4,ENCRYPTED\r\n" . "DEK-Info: DES-EDE3-CBC,$iv\r\n" . "\r\n" . chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) . '-----END RSA PRIVATE KEY-----'; } else { $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END RSA PRIVATE KEY-----'; } return $RSAPrivateKey; } } function _convertPublicKey($n, $e) { $signed = $this->publicKeyFormat != self::PUBLIC_FORMAT_XML; $modulus = $n->toBytes($signed); $publicExponent = $e->toBytes($signed); switch ($this->publicKeyFormat) { case self::PUBLIC_FORMAT_RAW: return array('e' => $e->copy(), 'n' => $n->copy()); case self::PUBLIC_FORMAT_XML: return "\r\n" . ' ' . base64_encode($modulus) . "\r\n" . ' ' . base64_encode($publicExponent) . "\r\n" . ''; break; case self::PUBLIC_FORMAT_OPENSSH: $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment; return $RSAPublicKey; default: $components = array( 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) ); $RSAPublicKey = pack( 'Ca*a*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent'] ); if ($this->publicKeyFormat == self::PUBLIC_FORMAT_PKCS1_RAW) { $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . chunk_split(base64_encode($RSAPublicKey), 64) . '-----END RSA PUBLIC KEY-----'; } else { $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); $RSAPublicKey = chr(0) . $RSAPublicKey; $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; $RSAPublicKey = pack( 'Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey ); $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(base64_encode($RSAPublicKey), 64) . '-----END PUBLIC KEY-----'; } return $RSAPublicKey; } } function _parseKey($key, $type) { if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) { return false; } switch ($type) { case self::PUBLIC_FORMAT_RAW: if (!is_array($key)) { return false; } $components = array(); switch (true) { case isset($key['e']): $components['publicExponent'] = $key['e']->copy(); break; case isset($key['exponent']): $components['publicExponent'] = $key['exponent']->copy(); break; case isset($key['publicExponent']): $components['publicExponent'] = $key['publicExponent']->copy(); break; case isset($key[0]): $components['publicExponent'] = $key[0]->copy(); } switch (true) { case isset($key['n']): $components['modulus'] = $key['n']->copy(); break; case isset($key['modulo']): $components['modulus'] = $key['modulo']->copy(); break; case isset($key['modulus']): $components['modulus'] = $key['modulus']->copy(); break; case isset($key[1]): $components['modulus'] = $key[1]->copy(); } return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false; case self::PRIVATE_FORMAT_PKCS1: case self::PRIVATE_FORMAT_PKCS8: case self::PUBLIC_FORMAT_PKCS1: if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { $iv = pack('H*', trim($matches[2])); $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8))); $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); $ciphertext = $this->_extractBER($key); if ($ciphertext === false) { $ciphertext = $key; } switch ($matches[1]) { case 'AES-256-CBC': $crypto = new AES(); break; case 'AES-128-CBC': $symkey = substr($symkey, 0, 16); $crypto = new AES(); break; case 'DES-EDE3-CFB': $crypto = new TripleDES(Base::MODE_CFB); break; case 'DES-EDE3-CBC': $symkey = substr($symkey, 0, 24); $crypto = new TripleDES(); break; case 'DES-CBC': $crypto = new DES(); break; default: return false; } $crypto->setKey($symkey); $crypto->setIV($iv); $decoded = $crypto->decrypt($ciphertext); } else { $decoded = $this->_extractBER($key); } if ($decoded !== false) { $key = $decoded; } $components = array(); if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { return false; } if ($this->_decodeLength($key) != strlen($key)) { return false; } $tag = ord($this->_string_shift($key)); if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { $this->_string_shift($key, 3); $tag = self::ASN1_SEQUENCE; } if ($tag == self::ASN1_SEQUENCE) { $temp = $this->_string_shift($key, $this->_decodeLength($key)); if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) { return false; } $length = $this->_decodeLength($temp); switch ($this->_string_shift($temp, $length)) { case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": case "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0A": break; case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": if (ord($this->_string_shift($temp)) != self::ASN1_SEQUENCE) { return false; } if ($this->_decodeLength($temp) != strlen($temp)) { return false; } $this->_string_shift($temp); $salt = $this->_string_shift($temp, $this->_decodeLength($temp)); if (ord($this->_string_shift($temp)) != self::ASN1_INTEGER) { return false; } $this->_decodeLength($temp); list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); $this->_string_shift($key); $length = $this->_decodeLength($key); if (strlen($key) != $length) { return false; } $crypto = new DES(); $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); $key = $crypto->decrypt($key); if ($key === false) { return false; } return $this->_parseKey($key, self::PRIVATE_FORMAT_PKCS1); default: return false; } $tag = ord($this->_string_shift($key)); $this->_decodeLength($key); if ($tag == self::ASN1_BITSTRING) { $this->_string_shift($key); } if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { return false; } if ($this->_decodeLength($key) != strlen($key)) { return false; } $tag = ord($this->_string_shift($key)); } if ($tag != self::ASN1_INTEGER) { return false; } $length = $this->_decodeLength($key); $temp = $this->_string_shift($key, $length); if (strlen($temp) != 1 || ord($temp) > 2) { $components['modulus'] = new BigInteger($temp, 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256); return $components; } if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) { return false; } $length = $this->_decodeLength($key); $components['modulus'] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['publicExponent'] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['primes'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256)); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['exponents'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256)); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($key, $length), 256)); if (!empty($key)) { if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { return false; } $this->_decodeLength($key); while (!empty($key)) { if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) { return false; } $this->_decodeLength($key); $key = substr($key, 1); $length = $this->_decodeLength($key); $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256); $this->_string_shift($key); $length = $this->_decodeLength($key); $components['coefficients'][] = new BigInteger($this->_string_shift($key, $length), 256); } } return $components; case self::PUBLIC_FORMAT_OPENSSH: $parts = explode(' ', $key, 3); $key = isset($parts[1]) ? base64_decode($parts[1]) : false; if ($key === false) { return false; } $comment = isset($parts[2]) ? $parts[2] : false; $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', $this->_string_shift($key, 4))); $publicExponent = new BigInteger($this->_string_shift($key, $length), -256); if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', $this->_string_shift($key, 4))); $modulus = new BigInteger($this->_string_shift($key, $length), -256); if ($cleanup && strlen($key)) { if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', $this->_string_shift($key, 4))); $realModulus = new BigInteger($this->_string_shift($key, $length), -256); return strlen($key) ? false : array( 'modulus' => $realModulus, 'publicExponent' => $modulus, 'comment' => $comment ); } else { return strlen($key) ? false : array( 'modulus' => $modulus, 'publicExponent' => $publicExponent, 'comment' => $comment ); } case self::PRIVATE_FORMAT_XML: case self::PUBLIC_FORMAT_XML: $this->components = array(); $xml = xml_parser_create('UTF-8'); xml_set_object($xml, $this); xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler'); xml_set_character_data_handler($xml, '_data_handler'); if (!xml_parse($xml, '' . $key . '')) { xml_parser_free($xml); unset($xml); return false; } xml_parser_free($xml); unset($xml); return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false; case self::PRIVATE_FORMAT_PUTTY: $components = array(); $key = preg_split('#\r\n|\r|\n#', $key); if ($this->_string_shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') { return false; } $version = (int) $this->_string_shift($key[0], 3); if ($version != 2 && $version != 3) { return false; } $type = rtrim($key[0]); if ($type != 'ssh-rsa') { return false; } $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2])); $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3])); $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); $public = substr($public, 11); extract(unpack('Nlength', $this->_string_shift($public, 4))); $components['publicExponent'] = new BigInteger($this->_string_shift($public, $length), -256); extract(unpack('Nlength', $this->_string_shift($public, 4))); $components['modulus'] = new BigInteger($this->_string_shift($public, $length), -256); $offset = $publicLength + 4; switch ($encryption) { case 'aes256-cbc': $crypto = new AES(); switch ($version) { case 3: if (!function_exists('sodium_crypto_pwhash')) { return false; } $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++])); switch ($flavour) { case 'Argon2i': $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13; break; case 'Argon2id': $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13; break; default: return false; } $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++])); $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++])); $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++])); $salt = pack('H*', trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++]))); $length = 80; $temp = sodium_crypto_pwhash($length, $this->password, $salt, $passes, $memory << 10, $flavour); $symkey = substr($temp, 0, 32); $symiv = substr($temp, 32, 16); break; case 2: $symkey = ''; $sequence = 0; while (strlen($symkey) < 32) { $temp = pack('Na*', $sequence++, $this->password); $symkey.= pack('H*', sha1($temp)); } $symkey = substr($symkey, 0, 32); $symiv = str_repeat("\0", 16); } } $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++])); $private = base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength)))); if ($encryption != 'none') { $crypto->setKey($symkey); $crypto->setIV($symiv); $crypto->disablePadding(); $private = $crypto->decrypt($private); if ($private === false) { return false; } } extract(unpack('Nlength', $this->_string_shift($private, 4))); if (strlen($private) < $length) { return false; } $components['privateExponent'] = new BigInteger($this->_string_shift($private, $length), -256); extract(unpack('Nlength', $this->_string_shift($private, 4))); if (strlen($private) < $length) { return false; } $components['primes'] = array(1 => new BigInteger($this->_string_shift($private, $length), -256)); extract(unpack('Nlength', $this->_string_shift($private, 4))); if (strlen($private) < $length) { return false; } $components['primes'][] = new BigInteger($this->_string_shift($private, $length), -256); $temp = $components['primes'][1]->subtract($this->one); $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); $temp = $components['primes'][2]->subtract($this->one); $components['exponents'][] = $components['publicExponent']->modInverse($temp); extract(unpack('Nlength', $this->_string_shift($private, 4))); if (strlen($private) < $length) { return false; } $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256)); return $components; case self::PRIVATE_FORMAT_OPENSSH: $components = array(); $decoded = $this->_extractBER($key); $magic = $this->_string_shift($decoded, 15); if ($magic !== "openssh-key-v1\0") { return false; } $options = $this->_string_shift($decoded, 24); if ($options != "\0\0\0\4none\0\0\0\4none\0\0\0\0\0\0\0\1") { return false; } extract(unpack('Nlength', $this->_string_shift($decoded, 4))); if (strlen($decoded) < $length) { return false; } $publicKey = $this->_string_shift($decoded, $length); extract(unpack('Nlength', $this->_string_shift($decoded, 4))); if (strlen($decoded) < $length) { return false; } $paddedKey = $this->_string_shift($decoded, $length); if ($this->_string_shift($publicKey, 11) !== "\0\0\0\7ssh-rsa") { return false; } $checkint1 = $this->_string_shift($paddedKey, 4); $checkint2 = $this->_string_shift($paddedKey, 4); if (strlen($checkint1) != 4 || $checkint1 !== $checkint2) { return false; } if ($this->_string_shift($paddedKey, 11) !== "\0\0\0\7ssh-rsa") { return false; } $values = array( &$components['modulus'], &$components['publicExponent'], &$components['privateExponent'], &$components['coefficients'][2], &$components['primes'][1], &$components['primes'][2] ); foreach ($values as &$value) { extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); if (strlen($paddedKey) < $length) { return false; } $value = new BigInteger($this->_string_shift($paddedKey, $length), -256); } extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); if (strlen($paddedKey) < $length) { return false; } $components['comment'] = $this->_string_shift($decoded, $length); $temp = $components['primes'][1]->subtract($this->one); $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); $temp = $components['primes'][2]->subtract($this->one); $components['exponents'][] = $components['publicExponent']->modInverse($temp); return $components; } return false; } function getSize() { return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits()); } function _start_element_handler($parser, $name, $attribs) { switch ($name) { case 'MODULUS': $this->current = &$this->components['modulus']; break; case 'EXPONENT': $this->current = &$this->components['publicExponent']; break; case 'P': $this->current = &$this->components['primes'][1]; break; case 'Q': $this->current = &$this->components['primes'][2]; break; case 'DP': $this->current = &$this->components['exponents'][1]; break; case 'DQ': $this->current = &$this->components['exponents'][2]; break; case 'INVERSEQ': $this->current = &$this->components['coefficients'][2]; break; case 'D': $this->current = &$this->components['privateExponent']; } $this->current = ''; } function _stop_element_handler($parser, $name) { if (isset($this->current)) { $this->current = new BigInteger(base64_decode($this->current), 256); unset($this->current); } } function _data_handler($parser, $data) { if (!isset($this->current) || is_object($this->current)) { return; } $this->current.= trim($data); } function loadKey($key, $type = false) { if ($key instanceof RSA) { $this->privateKeyFormat = $key->privateKeyFormat; $this->publicKeyFormat = $key->publicKeyFormat; $this->k = $key->k; $this->hLen = $key->hLen; $this->sLen = $key->sLen; $this->mgfHLen = $key->mgfHLen; $this->encryptionMode = $key->encryptionMode; $this->signatureMode = $key->signatureMode; $this->password = $key->password; $this->configFile = $key->configFile; $this->comment = $key->comment; if (is_object($key->hash)) { $this->hash = new Hash($key->hash->getHash()); } if (is_object($key->mgfHash)) { $this->mgfHash = new Hash($key->mgfHash->getHash()); } if (is_object($key->modulus)) { $this->modulus = $key->modulus->copy(); } if (is_object($key->exponent)) { $this->exponent = $key->exponent->copy(); } if (is_object($key->publicExponent)) { $this->publicExponent = $key->publicExponent->copy(); } $this->primes = array(); $this->exponents = array(); $this->coefficients = array(); foreach ($this->primes as $prime) { $this->primes[] = $prime->copy(); } foreach ($this->exponents as $exponent) { $this->exponents[] = $exponent->copy(); } foreach ($this->coefficients as $coefficient) { $this->coefficients[] = $coefficient->copy(); } return true; } if ($type === false) { $types = array( self::PUBLIC_FORMAT_RAW, self::PRIVATE_FORMAT_PKCS1, self::PRIVATE_FORMAT_XML, self::PRIVATE_FORMAT_PUTTY, self::PUBLIC_FORMAT_OPENSSH, self::PRIVATE_FORMAT_OPENSSH ); foreach ($types as $type) { $components = $this->_parseKey($key, $type); if ($components !== false) { break; } } } else { $components = $this->_parseKey($key, $type); } if ($components === false) { $this->comment = null; $this->modulus = null; $this->k = null; $this->exponent = null; $this->primes = null; $this->exponents = null; $this->coefficients = null; $this->publicExponent = null; return false; } if (isset($components['comment']) && $components['comment'] !== false) { $this->comment = $components['comment']; } $this->modulus = $components['modulus']; $this->k = strlen($this->modulus->toBytes()); $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; if (isset($components['primes'])) { $this->primes = $components['primes']; $this->exponents = $components['exponents']; $this->coefficients = $components['coefficients']; $this->publicExponent = $components['publicExponent']; } else { $this->primes = array(); $this->exponents = array(); $this->coefficients = array(); $this->publicExponent = false; } switch ($type) { case self::PUBLIC_FORMAT_OPENSSH: case self::PUBLIC_FORMAT_RAW: $this->setPublicKey(); break; case self::PRIVATE_FORMAT_PKCS1: switch (true) { case strpos($key, '-BEGIN PUBLIC KEY-') !== false: case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false: $this->setPublicKey(); } } return true; } function setPassword($password = false) { $this->password = $password; } function setPublicKey($key = false, $type = false) { if (!empty($this->publicExponent)) { return false; } if ($key === false && !empty($this->modulus)) { $this->publicExponent = $this->exponent; return true; } if ($type === false) { $types = array( self::PUBLIC_FORMAT_RAW, self::PUBLIC_FORMAT_PKCS1, self::PUBLIC_FORMAT_XML, self::PUBLIC_FORMAT_OPENSSH ); foreach ($types as $type) { $components = $this->_parseKey($key, $type); if ($components !== false) { break; } } } else { $components = $this->_parseKey($key, $type); } if ($components === false) { return false; } if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { $this->modulus = $components['modulus']; $this->exponent = $this->publicExponent = $components['publicExponent']; return true; } $this->publicExponent = $components['publicExponent']; return true; } function setPrivateKey($key = false, $type = false) { if ($key === false && !empty($this->publicExponent)) { $this->publicExponent = false; return true; } $rsa = new RSA(); if (!$rsa->loadKey($key, $type)) { return false; } $rsa->publicExponent = false; $this->loadKey($rsa); return true; } function getPublicKey($type = self::PUBLIC_FORMAT_PKCS8) { if (empty($this->modulus) || empty($this->publicExponent)) { return false; } $oldFormat = $this->publicKeyFormat; $this->publicKeyFormat = $type; $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); $this->publicKeyFormat = $oldFormat; return $temp; } function getPublicKeyFingerprint($algorithm = 'md5') { if (empty($this->modulus) || empty($this->publicExponent)) { return false; } $modulus = $this->modulus->toBytes(true); $publicExponent = $this->publicExponent->toBytes(true); $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); switch ($algorithm) { case 'sha256': $hash = new Hash('sha256'); $base = base64_encode($hash->hash($RSAPublicKey)); return substr($base, 0, strlen($base) - 1); case 'md5': return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1); default: return false; } } function getPrivateKey($type = self::PUBLIC_FORMAT_PKCS1) { if (empty($this->primes)) { return false; } $oldFormat = $this->privateKeyFormat; $this->privateKeyFormat = $type; $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients); $this->privateKeyFormat = $oldFormat; return $temp; } function _getPrivatePublicKey($mode = self::PUBLIC_FORMAT_PKCS8) { if (empty($this->modulus) || empty($this->exponent)) { return false; } $oldFormat = $this->publicKeyFormat; $this->publicKeyFormat = $mode; $temp = $this->_convertPublicKey($this->modulus, $this->exponent); $this->publicKeyFormat = $oldFormat; return $temp; } function __toString() { $key = $this->getPrivateKey($this->privateKeyFormat); if ($key !== false) { return $key; } $key = $this->_getPrivatePublicKey($this->publicKeyFormat); return $key !== false ? $key : ''; } function __clone() { $key = new RSA(); $key->loadKey($this); return $key; } function _generateMinMax($bits) { $bytes = $bits >> 3; $min = str_repeat(chr(0), $bytes); $max = str_repeat(chr(0xFF), $bytes); $msb = $bits & 7; if ($msb) { $min = chr(1 << ($msb - 1)) . $min; $max = chr((1 << $msb) - 1) . $max; } else { $min[0] = chr(0x80); } return array( 'min' => new BigInteger($min, 256), 'max' => new BigInteger($max, 256) ); } function _decodeLength(&$string) { $length = ord($this->_string_shift($string)); if ($length & 0x80) { $length&= 0x7F; $temp = $this->_string_shift($string, $length); list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); } return $length; } function _encodeLength($length) { if ($length <= 0x7F) { return chr($length); } $temp = ltrim(pack('N', $length), chr(0)); return pack('Ca*', 0x80 | strlen($temp), $temp); } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } function setPrivateKeyFormat($format) { $this->privateKeyFormat = $format; } function setPublicKeyFormat($format) { $this->publicKeyFormat = $format; } function setHash($hash) { switch ($hash) { case 'md2': case 'md5': case 'sha1': case 'sha256': case 'sha384': case 'sha512': $this->hash = new Hash($hash); $this->hashName = $hash; break; default: $this->hash = new Hash('sha1'); $this->hashName = 'sha1'; } $this->hLen = $this->hash->getLength(); } function setMGFHash($hash) { switch ($hash) { case 'md2': case 'md5': case 'sha1': case 'sha256': case 'sha384': case 'sha512': $this->mgfHash = new Hash($hash); break; default: $this->mgfHash = new Hash('sha1'); } $this->mgfHLen = $this->mgfHash->getLength(); } function setSaltLength($sLen) { $this->sLen = $sLen; } function _i2osp($x, $xLen) { $x = $x->toBytes(); if (strlen($x) > $xLen) { user_error('Integer too large'); return false; } return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); } function _os2ip($x) { return new BigInteger($x, 256); } function _exponentiate($x) { switch (true) { case empty($this->primes): case $this->primes[1]->equals($this->zero): case empty($this->coefficients): case $this->coefficients[2]->equals($this->zero): case empty($this->exponents): case $this->exponents[1]->equals($this->zero): return $x->modPow($this->exponent, $this->modulus); } $num_primes = count($this->primes); if (defined('CRYPT_RSA_DISABLE_BLINDING')) { $m_i = array( 1 => $x->modPow($this->exponents[1], $this->primes[1]), 2 => $x->modPow($this->exponents[2], $this->primes[2]) ); $h = $m_i[1]->subtract($m_i[2]); $h = $h->multiply($this->coefficients[2]); list(, $h) = $h->divide($this->primes[1]); $m = $m_i[2]->add($h->multiply($this->primes[2])); $r = $this->primes[1]; for ($i = 3; $i <= $num_primes; $i++) { $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); $r = $r->multiply($this->primes[$i - 1]); $h = $m_i->subtract($m); $h = $h->multiply($this->coefficients[$i]); list(, $h) = $h->divide($this->primes[$i]); $m = $m->add($r->multiply($h)); } } else { $smallest = $this->primes[1]; for ($i = 2; $i <= $num_primes; $i++) { if ($smallest->compare($this->primes[$i]) > 0) { $smallest = $this->primes[$i]; } } $one = new BigInteger(1); $r = $one->random($one, $smallest->subtract($one)); $m_i = array( 1 => $this->_blind($x, $r, 1), 2 => $this->_blind($x, $r, 2) ); $h = $m_i[1]->subtract($m_i[2]); $h = $h->multiply($this->coefficients[2]); list(, $h) = $h->divide($this->primes[1]); $m = $m_i[2]->add($h->multiply($this->primes[2])); $r = $this->primes[1]; for ($i = 3; $i <= $num_primes; $i++) { $m_i = $this->_blind($x, $r, $i); $r = $r->multiply($this->primes[$i - 1]); $h = $m_i->subtract($m); $h = $h->multiply($this->coefficients[$i]); list(, $h) = $h->divide($this->primes[$i]); $m = $m->add($r->multiply($h)); } } return $m; } function _blind($x, $r, $i) { $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); $x = $x->modPow($this->exponents[$i], $this->primes[$i]); $r = $r->modInverse($this->primes[$i]); $x = $x->multiply($r); list(, $x) = $x->divide($this->primes[$i]); return $x; } function _equals($x, $y) { if (function_exists('hash_equals')) { return hash_equals($x, $y); } if (strlen($x) != strlen($y)) { return false; } $result = "\0"; $x^= $y; for ($i = 0; $i < strlen($x); $i++) { $result|= $x[$i]; } return $result === "\0"; } function _rsaep($m) { if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { user_error('Message representative out of range'); return false; } return $this->_exponentiate($m); } function _rsadp($c) { if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { user_error('Ciphertext representative out of range'); return false; } return $this->_exponentiate($c); } function _rsasp1($m) { if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { user_error('Message representative out of range'); return false; } return $this->_exponentiate($m); } function _rsavp1($s) { if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { user_error('Signature representative out of range'); return false; } return $this->_exponentiate($s); } function _mgf1($mgfSeed, $maskLen) { $t = ''; $count = ceil($maskLen / $this->mgfHLen); for ($i = 0; $i < $count; $i++) { $c = pack('N', $i); $t.= $this->mgfHash->hash($mgfSeed . $c); } return substr($t, 0, $maskLen); } function _rsaes_oaep_encrypt($m, $l = '') { $mLen = strlen($m); if ($mLen > $this->k - 2 * $this->hLen - 2) { user_error('Message too long'); return false; } $lHash = $this->hash->hash($l); $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); $db = $lHash . $ps . chr(1) . $m; $seed = Random::string($this->hLen); $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); $maskedDB = $db ^ $dbMask; $seedMask = $this->_mgf1($maskedDB, $this->hLen); $maskedSeed = $seed ^ $seedMask; $em = chr(0) . $maskedSeed . $maskedDB; $m = $this->_os2ip($em); $c = $this->_rsaep($m); $c = $this->_i2osp($c, $this->k); return $c; } function _rsaes_oaep_decrypt($c, $l = '') { if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { user_error('Decryption error'); return false; } $c = $this->_os2ip($c); $m = $this->_rsadp($c); if ($m === false) { user_error('Decryption error'); return false; } $em = $this->_i2osp($m, $this->k); $lHash = $this->hash->hash($l); $y = ord($em[0]); $maskedSeed = substr($em, 1, $this->hLen); $maskedDB = substr($em, $this->hLen + 1); $seedMask = $this->_mgf1($maskedDB, $this->hLen); $seed = $maskedSeed ^ $seedMask; $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); $db = $maskedDB ^ $dbMask; $lHash2 = substr($db, 0, $this->hLen); $m = substr($db, $this->hLen); $hashesMatch = $this->_equals($lHash, $lHash2); $leadingZeros = 1; $patternMatch = 0; $offset = 0; for ($i = 0; $i < strlen($m); $i++) { $patternMatch|= $leadingZeros & ($m[$i] === "\1"); $leadingZeros&= $m[$i] === "\0"; $offset+= $patternMatch ? 0 : 1; } if (!$hashesMatch | !$patternMatch) { user_error('Decryption error'); return false; } return substr($m, $offset + 1); } function _raw_encrypt($m) { $temp = $this->_os2ip($m); $temp = $this->_rsaep($temp); return $this->_i2osp($temp, $this->k); } function _rsaes_pkcs1_v1_5_encrypt($m) { $mLen = strlen($m); if ($mLen > $this->k - 11) { user_error('Message too long'); return false; } $psLen = $this->k - $mLen - 3; $ps = ''; while (strlen($ps) != $psLen) { $temp = Random::string($psLen - strlen($ps)); $temp = str_replace("\x00", '', $temp); $ps.= $temp; } $type = 2; if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { $type = 1; $ps = str_repeat("\xFF", $psLen); } $em = chr(0) . chr($type) . $ps . chr(0) . $m; $m = $this->_os2ip($em); $c = $this->_rsaep($m); $c = $this->_i2osp($c, $this->k); return $c; } function _rsaes_pkcs1_v1_5_decrypt($c) { if (strlen($c) != $this->k) { user_error('Decryption error'); return false; } $c = $this->_os2ip($c); $m = $this->_rsadp($c); if ($m === false) { user_error('Decryption error'); return false; } $em = $this->_i2osp($m, $this->k); if (ord($em[0]) != 0 || ord($em[1]) > 2) { user_error('Decryption error'); return false; } $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); $m = substr($em, strlen($ps) + 3); if (strlen($ps) < 8) { user_error('Decryption error'); return false; } return $m; } function _emsa_pss_encode($m, $emBits) { $emLen = ($emBits + 1) >> 3; $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; $mHash = $this->hash->hash($m); if ($emLen < $this->hLen + $sLen + 2) { user_error('Encoding error'); return false; } $salt = Random::string($sLen); $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h = $this->hash->hash($m2); $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); $db = $ps . chr(1) . $salt; $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); $maskedDB = $db ^ $dbMask; $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; $em = $maskedDB . $h . chr(0xBC); return $em; } function _emsa_pss_verify($m, $em, $emBits) { $emLen = ($emBits + 7) >> 3; $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; $mHash = $this->hash->hash($m); if ($emLen < $this->hLen + $sLen + 2) { return false; } if ($em[strlen($em) - 1] != chr(0xBC)) { return false; } $maskedDB = substr($em, 0, -$this->hLen - 1); $h = substr($em, -$this->hLen - 1, $this->hLen); $temp = chr(0xFF << ($emBits & 7)); if ((~$maskedDB[0] & $temp) != $temp) { return false; } $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); $db = $maskedDB ^ $dbMask; $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; $temp = $emLen - $this->hLen - $sLen - 2; if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { return false; } $salt = substr($db, $temp + 1); $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h2 = $this->hash->hash($m2); return $this->_equals($h, $h2); } function _rsassa_pss_sign($m) { $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); $m = $this->_os2ip($em); $s = $this->_rsasp1($m); $s = $this->_i2osp($s, $this->k); return $s; } function _rsassa_pss_verify($m, $s) { if (strlen($s) != $this->k) { user_error('Invalid signature'); return false; } $modBits = strlen($this->modulus->toBits()); $s2 = $this->_os2ip($s); $m2 = $this->_rsavp1($s2); if ($m2 === false) { user_error('Invalid signature'); return false; } $em = $this->_i2osp($m2, $this->k); if ($em === false) { user_error('Invalid signature'); return false; } return $this->_emsa_pss_verify($m, $em, $modBits - 1); } function _emsa_pkcs1_v1_5_encode($m, $emLen) { $h = $this->hash->hash($m); if ($h === false) { return false; } switch ($this->hashName) { case 'md2': $t = pack('H*', '3020300c06082a864886f70d020205000410'); break; case 'md5': $t = pack('H*', '3020300c06082a864886f70d020505000410'); break; case 'sha1': $t = pack('H*', '3021300906052b0e03021a05000414'); break; case 'sha256': $t = pack('H*', '3031300d060960864801650304020105000420'); break; case 'sha384': $t = pack('H*', '3041300d060960864801650304020205000430'); break; case 'sha512': $t = pack('H*', '3051300d060960864801650304020305000440'); } $t.= $h; $tLen = strlen($t); if ($emLen < $tLen + 11) { user_error('Intended encoded message length too short'); return false; } $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); $em = "\0\1$ps\0$t"; return $em; } function _emsa_pkcs1_v1_5_encode_without_null($m, $emLen) { $h = $this->hash->hash($m); if ($h === false) { return false; } switch ($this->hashName) { case 'sha1': $t = pack('H*', '301f300706052b0e03021a0414'); break; case 'sha256': $t = pack('H*', '302f300b06096086480165030402010420'); break; case 'sha384': $t = pack('H*', '303f300b06096086480165030402020430'); break; case 'sha512': $t = pack('H*', '304f300b06096086480165030402030440'); break; default: return false; } $t.= $h; $tLen = strlen($t); if ($emLen < $tLen + 11) { user_error('Intended encoded message length too short'); return false; } $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); $em = "\0\1$ps\0$t"; return $em; } function _rsassa_pkcs1_v1_5_sign($m) { $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); if ($em === false) { user_error('RSA modulus too short'); return false; } $m = $this->_os2ip($em); $s = $this->_rsasp1($m); $s = $this->_i2osp($s, $this->k); return $s; } function _rsassa_pkcs1_v1_5_verify($m, $s) { if (strlen($s) != $this->k) { user_error('Invalid signature'); return false; } $s = $this->_os2ip($s); $m2 = $this->_rsavp1($s); if ($m2 === false) { user_error('Invalid signature'); return false; } $em = $this->_i2osp($m2, $this->k); if ($em === false) { user_error('Invalid signature'); return false; } $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); $em3 = $this->_emsa_pkcs1_v1_5_encode_without_null($m, $this->k); if ($em2 === false && $em3 === false) { user_error('RSA modulus too short'); return false; } return ($em2 !== false && $this->_equals($em, $em2)) || ($em3 !== false && $this->_equals($em, $em3)); } function setEncryptionMode($mode) { $this->encryptionMode = $mode; } function setSignatureMode($mode) { $this->signatureMode = $mode; } function setComment($comment) { $this->comment = $comment; } function getComment() { return $this->comment; } function encrypt($plaintext) { switch ($this->encryptionMode) { case self::ENCRYPTION_NONE: $plaintext = str_split($plaintext, $this->k); $ciphertext = ''; foreach ($plaintext as $m) { $ciphertext.= $this->_raw_encrypt($m); } return $ciphertext; case self::ENCRYPTION_PKCS1: $length = $this->k - 11; if ($length <= 0) { return false; } $plaintext = str_split($plaintext, $length); $ciphertext = ''; foreach ($plaintext as $m) { $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); } return $ciphertext; default: $length = $this->k - 2 * $this->hLen - 2; if ($length <= 0) { return false; } $plaintext = str_split($plaintext, $length); $ciphertext = ''; foreach ($plaintext as $m) { $ciphertext.= $this->_rsaes_oaep_encrypt($m); } return $ciphertext; } } function decrypt($ciphertext) { if ($this->k <= 0) { return false; } $ciphertext = str_split($ciphertext, $this->k); $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT); $plaintext = ''; switch ($this->encryptionMode) { case self::ENCRYPTION_NONE: $decrypt = '_raw_encrypt'; break; case self::ENCRYPTION_PKCS1: $decrypt = '_rsaes_pkcs1_v1_5_decrypt'; break; default: $decrypt = '_rsaes_oaep_decrypt'; } foreach ($ciphertext as $c) { $temp = $this->$decrypt($c); if ($temp === false) { return false; } $plaintext.= $temp; } return $plaintext; } function sign($message) { if (empty($this->modulus) || empty($this->exponent)) { return false; } switch ($this->signatureMode) { case self::SIGNATURE_PKCS1: return $this->_rsassa_pkcs1_v1_5_sign($message); default: return $this->_rsassa_pss_sign($message); } } function verify($message, $signature) { if (empty($this->modulus) || empty($this->exponent)) { return false; } switch ($this->signatureMode) { case self::SIGNATURE_PKCS1: return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); default: return $this->_rsassa_pss_verify($message, $signature); } } function _extractBER($str) { $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); $temp = preg_replace('#-+[^-]+-+#', '', $temp); $temp = str_replace(array("\r", "\n", ' '), '', $temp); $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; return $temp != false ? $temp : $str; } } = 0) { $this->cipher_name_openssl = 'rc4-40'; } else { switch (strlen($this->key)) { case 5: $this->cipher_name_openssl = 'rc4-40'; break; case 8: $this->cipher_name_openssl = 'rc4-64'; break; case 16: $this->cipher_name_openssl = 'rc4'; break; default: return false; } } } return parent::isValidEngine($engine); } function setIV($iv) { } function setKeyLength($length) { if ($length < 8) { $this->key_length = 1; } elseif ($length > 2048) { $this->key_length = 256; } else { $this->key_length = $length >> 3; } parent::setKeyLength($length); } function encrypt($plaintext) { if ($this->engine != Base::ENGINE_INTERNAL) { return parent::encrypt($plaintext); } return $this->_crypt($plaintext, self::ENCRYPT); } function decrypt($ciphertext) { if ($this->engine != Base::ENGINE_INTERNAL) { return parent::decrypt($ciphertext); } return $this->_crypt($ciphertext, self::DECRYPT); } function _encryptBlock($in) { } function _decryptBlock($in) { } function _setupKey() { $key = $this->key; $keyLength = strlen($key); $keyStream = range(0, 255); $j = 0; for ($i = 0; $i < 256; $i++) { $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; $temp = $keyStream[$i]; $keyStream[$i] = $keyStream[$j]; $keyStream[$j] = $temp; } $this->stream = array(); $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = array( 0, 0, $keyStream ); } function _crypt($text, $mode) { if ($this->changed) { $this->_setup(); $this->changed = false; } $stream = &$this->stream[$mode]; if ($this->continuousBuffer) { $i = &$stream[0]; $j = &$stream[1]; $keyStream = &$stream[2]; } else { $i = $stream[0]; $j = $stream[1]; $keyStream = $stream[2]; } $len = strlen($text); for ($k = 0; $k < $len; ++$k) { $i = ($i + 1) & 255; $ksi = $keyStream[$i]; $j = ($j + $ksi) & 255; $ksj = $keyStream[$j]; $keyStream[$i] = $ksj; $keyStream[$j] = $ksi; $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]); } return $text; } } setHash($hash); } function setKey($key = false) { $this->key = $key; $this->_computeKey(); } function _computeKey() { if ($this->key === false) { $this->computedKey = false; return; } if (strlen($this->key) <= $this->b) { $this->computedKey = $this->key; return; } switch ($this->engine) { case self::MODE_MHASH: $this->computedKey = mhash($this->hash, $this->key); break; case self::MODE_HASH: $this->computedKey = hash($this->hash, $this->key, true); break; case self::MODE_INTERNAL: $this->computedKey = call_user_func($this->hash, $this->key); } } function getHash() { return $this->hashParam; } function setHash($hash) { $this->hashParam = $hash = strtolower($hash); switch ($hash) { case 'md5-96': case 'sha1-96': case 'sha256-96': case 'sha512-96': $hash = substr($hash, 0, -3); $this->l = 12; break; case 'md2': case 'md5': $this->l = 16; break; case 'sha1': $this->l = 20; break; case 'sha256': $this->l = 32; break; case 'sha384': $this->l = 48; break; case 'sha512': $this->l = 64; } switch ($hash) { case 'md2-96': case 'md2': $this->b = 16; case 'md5-96': case 'sha1-96': case 'sha224-96': case 'sha256-96': case 'md2': case 'md5': case 'sha1': case 'sha224': case 'sha256': $this->b = 64; break; default: $this->b = 128; } switch ($hash) { case 'md2': $this->engine = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ? self::MODE_HASH : self::MODE_INTERNAL; break; case 'sha384': case 'sha512': $this->engine = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE; break; default: $this->engine = CRYPT_HASH_MODE; } switch ($this->engine) { case self::MODE_MHASH: switch ($hash) { case 'md5': $this->hash = MHASH_MD5; break; case 'sha256': $this->hash = MHASH_SHA256; break; case 'sha1': default: $this->hash = MHASH_SHA1; } $this->_computeKey(self::MODE_MHASH); return; case self::MODE_HASH: switch ($hash) { case 'md5': $this->hash = 'md5'; return; case 'md2': case 'sha256': case 'sha384': case 'sha512': $this->hash = $hash; return; case 'sha1': default: $this->hash = 'sha1'; } $this->_computeKey(self::MODE_HASH); return; } switch ($hash) { case 'md2': $this->hash = array($this, '_md2'); break; case 'md5': $this->hash = array($this, '_md5'); break; case 'sha256': $this->hash = array($this, '_sha256'); break; case 'sha384': case 'sha512': $this->hash = array($this, '_sha512'); break; case 'sha1': default: $this->hash = array($this, '_sha1'); } $this->ipad = str_repeat(chr(0x36), $this->b); $this->opad = str_repeat(chr(0x5C), $this->b); $this->_computeKey(self::MODE_INTERNAL); } function hash($text) { if (!empty($this->key) || is_string($this->key)) { switch ($this->engine) { case self::MODE_MHASH: $output = mhash($this->hash, $text, $this->computedKey); break; case self::MODE_HASH: $output = hash_hmac($this->hash, $text, $this->computedKey, true); break; case self::MODE_INTERNAL: $key = str_pad($this->computedKey, $this->b, chr(0)); $temp = $this->ipad ^ $key; $temp .= $text; $temp = call_user_func($this->hash, $temp); $output = $this->opad ^ $key; $output.= $temp; $output = call_user_func($this->hash, $output); } } else { switch ($this->engine) { case self::MODE_MHASH: $output = mhash($this->hash, $text); break; case self::MODE_HASH: $output = hash($this->hash, $text, true); break; case self::MODE_INTERNAL: $output = call_user_func($this->hash, $text); } } return substr($output, 0, $this->l); } function getLength() { return $this->l; } function _md5($m) { return pack('H*', md5($m)); } function _sha1($m) { return pack('H*', sha1($m)); } function _md2($m) { static $s = array( 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 ); $pad = 16 - (strlen($m) & 0xF); $m.= str_repeat(chr($pad), $pad); $length = strlen($m); $c = str_repeat(chr(0), 16); $l = chr(0); for ($i = 0; $i < $length; $i+= 16) { for ($j = 0; $j < 16; $j++) { $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); $l = $c[$j]; } } $m.= $c; $length+= 16; $x = str_repeat(chr(0), 48); for ($i = 0; $i < $length; $i+= 16) { for ($j = 0; $j < 16; $j++) { $x[$j + 16] = $m[$i + $j]; $x[$j + 32] = $x[$j + 16] ^ $x[$j]; } $t = chr(0); for ($j = 0; $j < 18; $j++) { for ($k = 0; $k < 48; $k++) { $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); } $t = chr(ord($t) + $j); } } return substr($x, 0, 16); } function _sha256($m) { if (extension_loaded('suhosin')) { return pack('H*', sha256($m)); } $hash = array( 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ); static $k = array( 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ); $length = strlen($m); $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); $m[$length] = chr(0x80); $m.= pack('N2', 0, $length << 3); $chunks = str_split($m, 64); foreach ($chunks as $chunk) { $w = array(); for ($i = 0; $i < 16; $i++) { extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); $w[] = $temp; } for ($i = 16; $i < 64; $i++) { $s0 = $this->_rightRotate($w[$i - 15], 7) ^ $this->_rightRotate($w[$i - 15], 18) ^ $this->_rightShift( $w[$i - 15], 3); $s1 = $this->_rightRotate($w[$i - 2], 17) ^ $this->_rightRotate($w[$i - 2], 19) ^ $this->_rightShift( $w[$i - 2], 10); $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); } list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; for ($i = 0; $i < 64; $i++) { $s0 = $this->_rightRotate($a, 2) ^ $this->_rightRotate($a, 13) ^ $this->_rightRotate($a, 22); $maj = ($a & $b) ^ ($a & $c) ^ ($b & $c); $t2 = $this->_add($s0, $maj); $s1 = $this->_rightRotate($e, 6) ^ $this->_rightRotate($e, 11) ^ $this->_rightRotate($e, 25); $ch = ($e & $f) ^ ($this->_not($e) & $g); $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); $h = $g; $g = $f; $f = $e; $e = $this->_add($d, $t1); $d = $c; $c = $b; $b = $a; $a = $this->_add($t1, $t2); } $hash = array( $this->_add($hash[0], $a), $this->_add($hash[1], $b), $this->_add($hash[2], $c), $this->_add($hash[3], $d), $this->_add($hash[4], $e), $this->_add($hash[5], $f), $this->_add($hash[6], $g), $this->_add($hash[7], $h) ); } return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); } function _sha512($m) { static $init384, $init512, $k; if (!isset($k)) { $init384 = array( 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' ); $init512 = array( '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' ); for ($i = 0; $i < 8; $i++) { $init384[$i] = new BigInteger($init384[$i], 16); $init384[$i]->setPrecision(64); $init512[$i] = new BigInteger($init512[$i], 16); $init512[$i]->setPrecision(64); } $k = array( '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' ); for ($i = 0; $i < 80; $i++) { $k[$i] = new BigInteger($k[$i], 16); } } $hash = $this->l == 48 ? $init384 : $init512; $length = strlen($m); $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); $m[$length] = chr(0x80); $m.= pack('N4', 0, 0, 0, $length << 3); $chunks = str_split($m, 128); foreach ($chunks as $chunk) { $w = array(); for ($i = 0; $i < 16; $i++) { $temp = new BigInteger($this->_string_shift($chunk, 8), 256); $temp->setPrecision(64); $w[] = $temp; } for ($i = 16; $i < 80; $i++) { $temp = array( $w[$i - 15]->bitwise_rightRotate(1), $w[$i - 15]->bitwise_rightRotate(8), $w[$i - 15]->bitwise_rightShift(7) ); $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); $temp = array( $w[$i - 2]->bitwise_rightRotate(19), $w[$i - 2]->bitwise_rightRotate(61), $w[$i - 2]->bitwise_rightShift(6) ); $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); $w[$i] = $w[$i - 16]->copy(); $w[$i] = $w[$i]->add($s0); $w[$i] = $w[$i]->add($w[$i - 7]); $w[$i] = $w[$i]->add($s1); } $a = $hash[0]->copy(); $b = $hash[1]->copy(); $c = $hash[2]->copy(); $d = $hash[3]->copy(); $e = $hash[4]->copy(); $f = $hash[5]->copy(); $g = $hash[6]->copy(); $h = $hash[7]->copy(); for ($i = 0; $i < 80; $i++) { $temp = array( $a->bitwise_rightRotate(28), $a->bitwise_rightRotate(34), $a->bitwise_rightRotate(39) ); $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); $temp = array( $a->bitwise_and($b), $a->bitwise_and($c), $b->bitwise_and($c) ); $maj = $temp[0]->bitwise_xor($temp[1]); $maj = $maj->bitwise_xor($temp[2]); $t2 = $s0->add($maj); $temp = array( $e->bitwise_rightRotate(14), $e->bitwise_rightRotate(18), $e->bitwise_rightRotate(41) ); $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); $temp = array( $e->bitwise_and($f), $g->bitwise_and($e->bitwise_not()) ); $ch = $temp[0]->bitwise_xor($temp[1]); $t1 = $h->add($s1); $t1 = $t1->add($ch); $t1 = $t1->add($k[$i]); $t1 = $t1->add($w[$i]); $h = $g->copy(); $g = $f->copy(); $f = $e->copy(); $e = $d->add($t1); $d = $c->copy(); $c = $b->copy(); $b = $a->copy(); $a = $t1->add($t2); } $hash = array( $hash[0]->add($a), $hash[1]->add($b), $hash[2]->add($c), $hash[3]->add($d), $hash[4]->add($e), $hash[5]->add($f), $hash[6]->add($g), $hash[7]->add($h) ); } $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . $hash[4]->toBytes() . $hash[5]->toBytes(); if ($this->l != 48) { $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); } return $temp; } function _rightRotate($int, $amt) { $invamt = 32 - $amt; $mask = (1 << $invamt) - 1; return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); } function _rightShift($int, $amt) { $mask = (1 << (32 - $amt)) - 1; return ($int >> $amt) & $mask; } function _not($int) { return ~$int & 0xFFFFFFFF; } function _add() { static $mod; if (!isset($mod)) { $mod = pow(2, 32); } $result = 0; $arguments = func_get_args(); foreach ($arguments as $argument) { $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; } if ((php_uname('m') & "\xDF\xDF\xDF") != 'ARM') { return fmod($result, $mod); } return (fmod($result, 0x80000000) & 0x7FFFFFFF) | ((fmod(floor($result / 0x80000000), 2) & 1) << 31); } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } } DirectoryString = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'teletexString' => array('type' => ASN1::TYPE_TELETEX_STRING), 'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING), 'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING), 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING), 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING) ) ); $this->PKCS9String = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), 'directoryString' => $this->DirectoryString ) ); $this->AttributeValue = array('type' => ASN1::TYPE_ANY); $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); $AttributeTypeAndValue = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'type' => $AttributeType, 'value'=> $this->AttributeValue ) ); $this->RelativeDistinguishedName = array( 'type' => ASN1::TYPE_SET, 'min' => 1, 'max' => -1, 'children' => $AttributeTypeAndValue ); $RDNSequence = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 0, 'max' => -1, 'children' => $this->RelativeDistinguishedName ); $this->Name = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'rdnSequence' => $RDNSequence ) ); $AlgorithmIdentifier = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'parameters' => array( 'type' => ASN1::TYPE_ANY, 'optional' => true ) ) ); $Extension = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'extnId' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'critical' => array( 'type' => ASN1::TYPE_BOOLEAN, 'optional' => true, 'default' => false ), 'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING) ) ); $this->Extensions = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $Extension ); $SubjectPublicKeyInfo = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'algorithm' => $AlgorithmIdentifier, 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING) ) ); $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING); $Time = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'utcTime' => array('type' => ASN1::TYPE_UTC_TIME), 'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME) ) ); $Validity = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'notBefore' => $Time, 'notAfter' => $Time ) ); $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER); $Version = array( 'type' => ASN1::TYPE_INTEGER, 'mapping' => array('v1', 'v2', 'v3') ); $TBSCertificate = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'version' => array( 'constant' => 0, 'optional' => true, 'explicit' => true, 'default' => 'v1' ) + $Version, 'serialNumber' => $CertificateSerialNumber, 'signature' => $AlgorithmIdentifier, 'issuer' => $this->Name, 'validity' => $Validity, 'subject' => $this->Name, 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo, 'issuerUniqueID' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $UniqueIdentifier, 'subjectUniqueID' => array( 'constant' => 2, 'optional' => true, 'implicit' => true ) + $UniqueIdentifier, 'extensions' => array( 'constant' => 3, 'optional' => true, 'explicit' => true ) + $this->Extensions ) ); $this->Certificate = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'tbsCertificate' => $TBSCertificate, 'signatureAlgorithm' => $AlgorithmIdentifier, 'signature' => array('type' => ASN1::TYPE_BIT_STRING) ) ); $this->KeyUsage = array( 'type' => ASN1::TYPE_BIT_STRING, 'mapping' => array( 'digitalSignature', 'nonRepudiation', 'keyEncipherment', 'dataEncipherment', 'keyAgreement', 'keyCertSign', 'cRLSign', 'encipherOnly', 'decipherOnly' ) ); $this->BasicConstraints = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'cA' => array( 'type' => ASN1::TYPE_BOOLEAN, 'optional' => true, 'default' => false ), 'pathLenConstraint' => array( 'type' => ASN1::TYPE_INTEGER, 'optional' => true ) ) ); $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING); $OrganizationalUnitNames = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => 4, 'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING) ); $PersonalName = array( 'type' => ASN1::TYPE_SET, 'children' => array( 'surname' => array( 'type' => ASN1::TYPE_PRINTABLE_STRING, 'constant' => 0, 'optional' => true, 'implicit' => true ), 'given-name' => array( 'type' => ASN1::TYPE_PRINTABLE_STRING, 'constant' => 1, 'optional' => true, 'implicit' => true ), 'initials' => array( 'type' => ASN1::TYPE_PRINTABLE_STRING, 'constant' => 2, 'optional' => true, 'implicit' => true ), 'generation-qualifier' => array( 'type' => ASN1::TYPE_PRINTABLE_STRING, 'constant' => 3, 'optional' => true, 'implicit' => true ) ) ); $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING); $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING); $PrivateDomainName = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) ) ); $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING); $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING); $AdministrationDomainName = array( 'type' => ASN1::TYPE_CHOICE, 'class' => ASN1::CLASS_APPLICATION, 'cast' => 2, 'children' => array( 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) ) ); $CountryName = array( 'type' => ASN1::TYPE_CHOICE, 'class' => ASN1::CLASS_APPLICATION, 'cast' => 1, 'children' => array( 'x121-dcc-code' => array('type' => ASN1::TYPE_NUMERIC_STRING), 'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING) ) ); $AnotherName = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'value' => array( 'type' => ASN1::TYPE_ANY, 'constant' => 0, 'optional' => true, 'explicit' => true ) ) ); $ExtensionAttribute = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'extension-attribute-type' => array( 'type' => ASN1::TYPE_PRINTABLE_STRING, 'constant' => 0, 'optional' => true, 'implicit' => true ), 'extension-attribute-value' => array( 'type' => ASN1::TYPE_ANY, 'constant' => 1, 'optional' => true, 'explicit' => true ) ) ); $ExtensionAttributes = array( 'type' => ASN1::TYPE_SET, 'min' => 1, 'max' => 256, 'children' => $ExtensionAttribute ); $BuiltInDomainDefinedAttribute = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'type' => array('type' => ASN1::TYPE_PRINTABLE_STRING), 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING) ) ); $BuiltInDomainDefinedAttributes = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => 4, 'children' => $BuiltInDomainDefinedAttribute ); $BuiltInStandardAttributes = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'country-name' => array('optional' => true) + $CountryName, 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName, 'network-address' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $NetworkAddress, 'terminal-identifier' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $TerminalIdentifier, 'private-domain-name' => array( 'constant' => 2, 'optional' => true, 'explicit' => true ) + $PrivateDomainName, 'organization-name' => array( 'constant' => 3, 'optional' => true, 'implicit' => true ) + $OrganizationName, 'numeric-user-identifier' => array( 'constant' => 4, 'optional' => true, 'implicit' => true ) + $NumericUserIdentifier, 'personal-name' => array( 'constant' => 5, 'optional' => true, 'implicit' => true ) + $PersonalName, 'organizational-unit-names' => array( 'constant' => 6, 'optional' => true, 'implicit' => true ) + $OrganizationalUnitNames ) ); $ORAddress = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'built-in-standard-attributes' => $BuiltInStandardAttributes, 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes, 'extension-attributes' => array('optional' => true) + $ExtensionAttributes ) ); $EDIPartyName = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'nameAssigner' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $this->DirectoryString, 'partyName' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $this->DirectoryString ) ); $GeneralName = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'otherName' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $AnotherName, 'rfc822Name' => array( 'type' => ASN1::TYPE_IA5_STRING, 'constant' => 1, 'optional' => true, 'implicit' => true ), 'dNSName' => array( 'type' => ASN1::TYPE_IA5_STRING, 'constant' => 2, 'optional' => true, 'implicit' => true ), 'x400Address' => array( 'constant' => 3, 'optional' => true, 'implicit' => true ) + $ORAddress, 'directoryName' => array( 'constant' => 4, 'optional' => true, 'explicit' => true ) + $this->Name, 'ediPartyName' => array( 'constant' => 5, 'optional' => true, 'implicit' => true ) + $EDIPartyName, 'uniformResourceIdentifier' => array( 'type' => ASN1::TYPE_IA5_STRING, 'constant' => 6, 'optional' => true, 'implicit' => true ), 'iPAddress' => array( 'type' => ASN1::TYPE_OCTET_STRING, 'constant' => 7, 'optional' => true, 'implicit' => true ), 'registeredID' => array( 'type' => ASN1::TYPE_OBJECT_IDENTIFIER, 'constant' => 8, 'optional' => true, 'implicit' => true ) ) ); $GeneralNames = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $GeneralName ); $this->IssuerAltName = $GeneralNames; $ReasonFlags = array( 'type' => ASN1::TYPE_BIT_STRING, 'mapping' => array( 'unused', 'keyCompromise', 'cACompromise', 'affiliationChanged', 'superseded', 'cessationOfOperation', 'certificateHold', 'privilegeWithdrawn', 'aACompromise' ) ); $DistributionPointName = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'fullName' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $GeneralNames, 'nameRelativeToCRLIssuer' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $this->RelativeDistinguishedName ) ); $DistributionPoint = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'distributionPoint' => array( 'constant' => 0, 'optional' => true, 'explicit' => true ) + $DistributionPointName, 'reasons' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $ReasonFlags, 'cRLIssuer' => array( 'constant' => 2, 'optional' => true, 'implicit' => true ) + $GeneralNames ) ); $this->CRLDistributionPoints = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $DistributionPoint ); $this->AuthorityKeyIdentifier = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'keyIdentifier' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $this->KeyIdentifier, 'authorityCertIssuer' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $GeneralNames, 'authorityCertSerialNumber' => array( 'constant' => 2, 'optional' => true, 'implicit' => true ) + $CertificateSerialNumber ) ); $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); $PolicyQualifierInfo = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'policyQualifierId' => $PolicyQualifierId, 'qualifier' => array('type' => ASN1::TYPE_ANY) ) ); $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); $PolicyInformation = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'policyIdentifier' => $CertPolicyId, 'policyQualifiers' => array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 0, 'max' => -1, 'optional' => true, 'children' => $PolicyQualifierInfo ) ) ); $this->CertificatePolicies = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $PolicyInformation ); $this->PolicyMappings = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'issuerDomainPolicy' => $CertPolicyId, 'subjectDomainPolicy' => $CertPolicyId ) ) ); $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); $this->ExtKeyUsageSyntax = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $KeyPurposeId ); $AccessDescription = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'accessMethod' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'accessLocation' => $GeneralName ) ); $this->AuthorityInfoAccessSyntax = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $AccessDescription ); $this->SubjectInfoAccessSyntax = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $AccessDescription ); $this->SubjectAltName = $GeneralNames; $this->PrivateKeyUsagePeriod = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'notBefore' => array( 'constant' => 0, 'optional' => true, 'implicit' => true, 'type' => ASN1::TYPE_GENERALIZED_TIME), 'notAfter' => array( 'constant' => 1, 'optional' => true, 'implicit' => true, 'type' => ASN1::TYPE_GENERALIZED_TIME) ) ); $BaseDistance = array('type' => ASN1::TYPE_INTEGER); $GeneralSubtree = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'base' => $GeneralName, 'minimum' => array( 'constant' => 0, 'optional' => true, 'implicit' => true, 'default' => new BigInteger(0) ) + $BaseDistance, 'maximum' => array( 'constant' => 1, 'optional' => true, 'implicit' => true, ) + $BaseDistance ) ); $GeneralSubtrees = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $GeneralSubtree ); $this->NameConstraints = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'permittedSubtrees' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $GeneralSubtrees, 'excludedSubtrees' => array( 'constant' => 1, 'optional' => true, 'implicit' => true ) + $GeneralSubtrees ) ); $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING); $DisplayText = array( 'type' => ASN1::TYPE_CHOICE, 'children' => array( 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), 'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING), 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING), 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING) ) ); $NoticeReference = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'organization' => $DisplayText, 'noticeNumbers' => array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => 200, 'children' => array('type' => ASN1::TYPE_INTEGER) ) ) ); $this->UserNotice = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'noticeRef' => array( 'optional' => true, 'implicit' => true ) + $NoticeReference, 'explicitText' => array( 'optional' => true, 'implicit' => true ) + $DisplayText ) ); $this->netscape_cert_type = array( 'type' => ASN1::TYPE_BIT_STRING, 'mapping' => array( 'SSLClient', 'SSLServer', 'Email', 'ObjectSigning', 'Reserved', 'SSLCA', 'EmailCA', 'ObjectSigningCA' ) ); $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING); $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING); $Attribute = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'type' => $AttributeType, 'value'=> array( 'type' => ASN1::TYPE_SET, 'min' => 1, 'max' => -1, 'children' => $this->AttributeValue ) ) ); $this->SubjectDirectoryAttributes = array( 'type' => ASN1::TYPE_SEQUENCE, 'min' => 1, 'max' => -1, 'children' => $Attribute ); $Attributes = array( 'type' => ASN1::TYPE_SET, 'min' => 1, 'max' => -1, 'children' => $Attribute ); $CertificationRequestInfo = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'version' => array( 'type' => ASN1::TYPE_INTEGER, 'mapping' => array('v1') ), 'subject' => $this->Name, 'subjectPKInfo' => $SubjectPublicKeyInfo, 'attributes' => array( 'constant' => 0, 'optional' => true, 'implicit' => true ) + $Attributes, ) ); $this->CertificationRequest = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'certificationRequestInfo' => $CertificationRequestInfo, 'signatureAlgorithm' => $AlgorithmIdentifier, 'signature' => array('type' => ASN1::TYPE_BIT_STRING) ) ); $RevokedCertificate = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'userCertificate' => $CertificateSerialNumber, 'revocationDate' => $Time, 'crlEntryExtensions' => array( 'optional' => true ) + $this->Extensions ) ); $TBSCertList = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'version' => array( 'optional' => true, 'default' => 'v1' ) + $Version, 'signature' => $AlgorithmIdentifier, 'issuer' => $this->Name, 'thisUpdate' => $Time, 'nextUpdate' => array( 'optional' => true ) + $Time, 'revokedCertificates' => array( 'type' => ASN1::TYPE_SEQUENCE, 'optional' => true, 'min' => 0, 'max' => -1, 'children' => $RevokedCertificate ), 'crlExtensions' => array( 'constant' => 0, 'optional' => true, 'explicit' => true ) + $this->Extensions ) ); $this->CertificateList = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'tbsCertList' => $TBSCertList, 'signatureAlgorithm' => $AlgorithmIdentifier, 'signature' => array('type' => ASN1::TYPE_BIT_STRING) ) ); $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER); $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED, 'mapping' => array( 'unspecified', 'keyCompromise', 'cACompromise', 'affiliationChanged', 'superseded', 'cessationOfOperation', 'certificateHold', 8 => 'removeFromCRL', 'privilegeWithdrawn', 'aACompromise' ) ); $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'distributionPoint' => array( 'constant' => 0, 'optional' => true, 'explicit' => true ) + $DistributionPointName, 'onlyContainsUserCerts' => array( 'type' => ASN1::TYPE_BOOLEAN, 'constant' => 1, 'optional' => true, 'default' => false, 'implicit' => true ), 'onlyContainsCACerts' => array( 'type' => ASN1::TYPE_BOOLEAN, 'constant' => 2, 'optional' => true, 'default' => false, 'implicit' => true ), 'onlySomeReasons' => array( 'constant' => 3, 'optional' => true, 'implicit' => true ) + $ReasonFlags, 'indirectCRL' => array( 'type' => ASN1::TYPE_BOOLEAN, 'constant' => 4, 'optional' => true, 'default' => false, 'implicit' => true ), 'onlyContainsAttributeCerts' => array( 'type' => ASN1::TYPE_BOOLEAN, 'constant' => 5, 'optional' => true, 'default' => false, 'implicit' => true ) ) ); $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME); $this->CertificateIssuer = $GeneralNames; $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); $PublicKeyAndChallenge = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'spki' => $SubjectPublicKeyInfo, 'challenge' => array('type' => ASN1::TYPE_IA5_STRING) ) ); $this->SignedPublicKeyAndChallenge = array( 'type' => ASN1::TYPE_SEQUENCE, 'children' => array( 'publicKeyAndChallenge' => $PublicKeyAndChallenge, 'signatureAlgorithm' => $AlgorithmIdentifier, 'signature' => array('type' => ASN1::TYPE_BIT_STRING) ) ); $this->PostalAddress = array( 'type' => ASN1::TYPE_SEQUENCE, 'optional' => true, 'min' => 1, 'max' => -1, 'children' => $this->DirectoryString ); $this->oids = array( '1.3.6.1.5.5.7' => 'id-pkix', '1.3.6.1.5.5.7.1' => 'id-pe', '1.3.6.1.5.5.7.2' => 'id-qt', '1.3.6.1.5.5.7.3' => 'id-kp', '1.3.6.1.5.5.7.48' => 'id-ad', '1.3.6.1.5.5.7.2.1' => 'id-qt-cps', '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice', '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp', '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers', '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping', '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository', '2.5.4' => 'id-at', '2.5.4.41' => 'id-at-name', '2.5.4.4' => 'id-at-surname', '2.5.4.42' => 'id-at-givenName', '2.5.4.43' => 'id-at-initials', '2.5.4.44' => 'id-at-generationQualifier', '2.5.4.3' => 'id-at-commonName', '2.5.4.7' => 'id-at-localityName', '2.5.4.8' => 'id-at-stateOrProvinceName', '2.5.4.10' => 'id-at-organizationName', '2.5.4.11' => 'id-at-organizationalUnitName', '2.5.4.12' => 'id-at-title', '2.5.4.13' => 'id-at-description', '2.5.4.46' => 'id-at-dnQualifier', '2.5.4.6' => 'id-at-countryName', '2.5.4.5' => 'id-at-serialNumber', '2.5.4.65' => 'id-at-pseudonym', '2.5.4.17' => 'id-at-postalCode', '2.5.4.9' => 'id-at-streetAddress', '2.5.4.45' => 'id-at-uniqueIdentifier', '2.5.4.72' => 'id-at-role', '2.5.4.16' => 'id-at-postalAddress', '0.9.2342.19200300.100.1.25' => 'id-domainComponent', '1.2.840.113549.1.9' => 'pkcs-9', '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress', '2.5.29' => 'id-ce', '2.5.29.35' => 'id-ce-authorityKeyIdentifier', '2.5.29.14' => 'id-ce-subjectKeyIdentifier', '2.5.29.15' => 'id-ce-keyUsage', '2.5.29.16' => 'id-ce-privateKeyUsagePeriod', '2.5.29.32' => 'id-ce-certificatePolicies', '2.5.29.32.0' => 'anyPolicy', '2.5.29.33' => 'id-ce-policyMappings', '2.5.29.17' => 'id-ce-subjectAltName', '2.5.29.18' => 'id-ce-issuerAltName', '2.5.29.9' => 'id-ce-subjectDirectoryAttributes', '2.5.29.19' => 'id-ce-basicConstraints', '2.5.29.30' => 'id-ce-nameConstraints', '2.5.29.36' => 'id-ce-policyConstraints', '2.5.29.31' => 'id-ce-cRLDistributionPoints', '2.5.29.37' => 'id-ce-extKeyUsage', '2.5.29.37.0' => 'anyExtendedKeyUsage', '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth', '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth', '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning', '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection', '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping', '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning', '2.5.29.54' => 'id-ce-inhibitAnyPolicy', '2.5.29.46' => 'id-ce-freshestCRL', '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess', '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess', '2.5.29.20' => 'id-ce-cRLNumber', '2.5.29.28' => 'id-ce-issuingDistributionPoint', '2.5.29.27' => 'id-ce-deltaCRLIndicator', '2.5.29.21' => 'id-ce-cRLReasons', '2.5.29.29' => 'id-ce-certificateIssuer', '2.5.29.23' => 'id-ce-holdInstructionCode', '1.2.840.10040.2' => 'holdInstruction', '1.2.840.10040.2.1' => 'id-holdinstruction-none', '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer', '1.2.840.10040.2.3' => 'id-holdinstruction-reject', '2.5.29.24' => 'id-ce-invalidityDate', '1.2.840.113549.2.2' => 'md2', '1.2.840.113549.2.5' => 'md5', '1.3.14.3.2.26' => 'id-sha1', '1.2.840.10040.4.1' => 'id-dsa', '1.2.840.10040.4.3' => 'id-dsa-with-sha1', '1.2.840.113549.1.1' => 'pkcs-1', '1.2.840.113549.1.1.1' => 'rsaEncryption', '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption', '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption', '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption', '1.2.840.10046.2.1' => 'dhpublicnumber', '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm', '1.2.840.10045' => 'ansi-X9-62', '1.2.840.10045.4' => 'id-ecSigType', '1.2.840.10045.4.1' => 'ecdsa-with-SHA1', '1.2.840.10045.1' => 'id-fieldType', '1.2.840.10045.1.1' => 'prime-field', '1.2.840.10045.1.2' => 'characteristic-two-field', '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis', '1.2.840.10045.1.2.3.1' => 'gnBasis', '1.2.840.10045.1.2.3.2' => 'tpBasis', '1.2.840.10045.1.2.3.3' => 'ppBasis', '1.2.840.10045.2' => 'id-publicKeyType', '1.2.840.10045.2.1' => 'id-ecPublicKey', '1.2.840.10045.3' => 'ellipticCurve', '1.2.840.10045.3.0' => 'c-TwoCurve', '1.2.840.10045.3.0.1' => 'c2pnb163v1', '1.2.840.10045.3.0.2' => 'c2pnb163v2', '1.2.840.10045.3.0.3' => 'c2pnb163v3', '1.2.840.10045.3.0.4' => 'c2pnb176w1', '1.2.840.10045.3.0.5' => 'c2pnb191v1', '1.2.840.10045.3.0.6' => 'c2pnb191v2', '1.2.840.10045.3.0.7' => 'c2pnb191v3', '1.2.840.10045.3.0.8' => 'c2pnb191v4', '1.2.840.10045.3.0.9' => 'c2pnb191v5', '1.2.840.10045.3.0.10' => 'c2pnb208w1', '1.2.840.10045.3.0.11' => 'c2pnb239v1', '1.2.840.10045.3.0.12' => 'c2pnb239v2', '1.2.840.10045.3.0.13' => 'c2pnb239v3', '1.2.840.10045.3.0.14' => 'c2pnb239v4', '1.2.840.10045.3.0.15' => 'c2pnb239v5', '1.2.840.10045.3.0.16' => 'c2pnb272w1', '1.2.840.10045.3.0.17' => 'c2pnb304w1', '1.2.840.10045.3.0.18' => 'c2pnb359v1', '1.2.840.10045.3.0.19' => 'c2pnb368w1', '1.2.840.10045.3.0.20' => 'c2pnb431r1', '1.2.840.10045.3.1' => 'primeCurve', '1.2.840.10045.3.1.1' => 'prime192v1', '1.2.840.10045.3.1.2' => 'prime192v2', '1.2.840.10045.3.1.3' => 'prime192v3', '1.2.840.10045.3.1.4' => 'prime239v1', '1.2.840.10045.3.1.5' => 'prime239v2', '1.2.840.10045.3.1.6' => 'prime239v3', '1.2.840.10045.3.1.7' => 'prime256v1', '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP', '1.2.840.113549.1.1.9' => 'id-pSpecified', '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS', '1.2.840.113549.1.1.8' => 'id-mgf1', '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption', '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption', '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption', '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption', '2.16.840.1.101.3.4.2.4' => 'id-sha224', '2.16.840.1.101.3.4.2.1' => 'id-sha256', '2.16.840.1.101.3.4.2.2' => 'id-sha384', '2.16.840.1.101.3.4.2.3' => 'id-sha512', '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94', '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001', '1.2.643.2.2.20' => 'id-GostR3410-2001', '1.2.643.2.2.19' => 'id-GostR3410-94', '2.16.840.1.113730' => 'netscape', '2.16.840.1.113730.1' => 'netscape-cert-extension', '2.16.840.1.113730.1.1' => 'netscape-cert-type', '2.16.840.1.113730.1.13' => 'netscape-comment', '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url', '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype', '1.2.840.113533.7.65.0' => 'entrustVersInfo', '2.16.840.1.113733.1.6.9' => 'verisignPrivate', '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' ); } function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($cert) && isset($cert['tbsCertificate'])) { unset($this->currentCert); unset($this->currentKeyIdentifier); $this->dn = $cert['tbsCertificate']['subject']; if (!isset($this->dn)) { return false; } $this->currentCert = $cert; $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier'); $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null; unset($this->signatureSubject); return $cert; } $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { $newcert = $this->_extractBER($cert); if ($mode == self::FORMAT_PEM && $cert == $newcert) { return false; } $cert = $newcert; } if ($cert === false) { $this->currentCert = false; return false; } $asn1->loadOIDs($this->oids); $decoded = $asn1->decodeBER($cert); if (!empty($decoded)) { $x509 = $asn1->asn1map($decoded[0], $this->Certificate); } if (!isset($x509) || $x509 === false) { $this->currentCert = false; return false; } $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) { $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1); } $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1); $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1); $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']; $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); $this->currentCert = $x509; $this->dn = $x509['tbsCertificate']['subject']; $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier'); $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null; return $x509; } function saveX509($cert, $format = self::FORMAT_PEM) { if (!is_array($cert) || !isset($cert['tbsCertificate'])) { return false; } switch (true) { case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')): case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): break; default: switch ($algorithm) { case 'rsaEncryption': $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))); $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null; $cert['signatureAlgorithm']['parameters'] = null; $cert['tbsCertificate']['signature']['parameters'] = null; } } $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING); $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string; $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string; $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string; $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string; $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string; $filters['signatureAlgorithm']['parameters'] = $type_utf8_string; $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string; $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string; $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string; $filters['policyQualifiers']['qualifier'] = array('type' => ASN1::TYPE_IA5_STRING); $asn1->loadFilters($filters); $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1); $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1); $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1); $cert = $asn1->encodeDER($cert, $this->Certificate); switch ($format) { case self::FORMAT_DER: return $cert; default: return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----'; } } function _mapInExtensions(&$root, $path, $asn1) { $extensions = &$this->_subArrayUnchecked($root, $path); if ($extensions) { for ($i = 0; $i < count($extensions); $i++) { $id = $extensions[$i]['extnId']; $value = &$extensions[$i]['extnValue']; $value = base64_decode($value); $map = $this->_getMapping($id); if (!is_bool($map)) { $decoder = $id == 'id-ce-nameConstraints' ? array($this, '_decodeNameConstraintIP') : array($this, '_decodeIP'); $decoded = $asn1->decodeBER($value); $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder)); $value = $mapped === false ? $decoded[0] : $mapped; if ($id == 'id-ce-certificatePolicies') { for ($j = 0; $j < count($value); $j++) { if (!isset($value[$j]['policyQualifiers'])) { continue; } for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; $map = $this->_getMapping($subid); $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; if ($map !== false) { $decoded = $asn1->decodeBER($subvalue); $mapped = $asn1->asn1map($decoded[0], $map); $subvalue = $mapped === false ? $decoded[0] : $mapped; } } } } } else { $value = base64_encode($value); } } } } function _mapOutExtensions(&$root, $path, $asn1) { $extensions = &$this->_subArray($root, $path); if (is_array($extensions)) { $size = count($extensions); for ($i = 0; $i < $size; $i++) { if ($extensions[$i] instanceof Element) { continue; } $id = $extensions[$i]['extnId']; $value = &$extensions[$i]['extnValue']; switch ($id) { case 'id-ce-certificatePolicies': for ($j = 0; $j < count($value); $j++) { if (!isset($value[$j]['policyQualifiers'])) { continue; } for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; $map = $this->_getMapping($subid); $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; if ($map !== false) { $subvalue = new Element($asn1->encodeDER($subvalue, $map)); } } } break; case 'id-ce-authorityKeyIdentifier': if (isset($value['authorityCertSerialNumber'])) { if ($value['authorityCertSerialNumber']->toBytes() == '') { $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0"; $value['authorityCertSerialNumber'] = new Element($temp); } } } $map = $this->_getMapping($id); if (is_bool($map)) { if (!$map) { user_error($id . ' is not a currently supported extension'); unset($extensions[$i]); } } else { $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP'))); $value = base64_encode($temp); } } } } function _mapInAttributes(&$root, $path, $asn1) { $attributes = &$this->_subArray($root, $path); if (is_array($attributes)) { for ($i = 0; $i < count($attributes); $i++) { $id = $attributes[$i]['type']; $map = $this->_getMapping($id); if (is_array($attributes[$i]['value'])) { $values = &$attributes[$i]['value']; for ($j = 0; $j < count($values); $j++) { $value = $asn1->encodeDER($values[$j], $this->AttributeValue); $decoded = $asn1->decodeBER($value); if (!is_bool($map)) { $mapped = $asn1->asn1map($decoded[0], $map); if ($mapped !== false) { $values[$j] = $mapped; } if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) { $this->_mapInExtensions($values, $j, $asn1); } } elseif ($map) { $values[$j] = base64_encode($value); } } } } } } function _mapOutAttributes(&$root, $path, $asn1) { $attributes = &$this->_subArray($root, $path); if (is_array($attributes)) { $size = count($attributes); for ($i = 0; $i < $size; $i++) { $id = $attributes[$i]['type']; $map = $this->_getMapping($id); if ($map === false) { user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); unset($attributes[$i]); } elseif (is_array($attributes[$i]['value'])) { $values = &$attributes[$i]['value']; for ($j = 0; $j < count($values); $j++) { switch ($id) { case 'pkcs-9-at-extensionRequest': $this->_mapOutExtensions($values, $j, $asn1); break; } if (!is_bool($map)) { $temp = $asn1->encodeDER($values[$j], $map); $decoded = $asn1->decodeBER($temp); $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue); } } } } } } function _mapInDNs(&$root, $path, $asn1) { $dns = &$this->_subArray($root, $path); if (is_array($dns)) { for ($i = 0; $i < count($dns); $i++) { for ($j = 0; $j < count($dns[$i]); $j++) { $type = $dns[$i][$j]['type']; $value = &$dns[$i][$j]['value']; if (is_object($value) && $value instanceof Element) { $map = $this->_getMapping($type); if (!is_bool($map)) { $decoded = $asn1->decodeBER($value); $value = $asn1->asn1map($decoded[0], $map); } } } } } } function _mapOutDNs(&$root, $path, $asn1) { $dns = &$this->_subArray($root, $path); if (is_array($dns)) { $size = count($dns); for ($i = 0; $i < $size; $i++) { for ($j = 0; $j < count($dns[$i]); $j++) { $type = $dns[$i][$j]['type']; $value = &$dns[$i][$j]['value']; if (is_object($value) && $value instanceof Element) { continue; } $map = $this->_getMapping($type); if (!is_bool($map)) { $value = new Element($asn1->encodeDER($value, $map)); } } } } } function _getMapping($extnId) { if (!is_string($extnId)) { return true; } switch ($extnId) { case 'id-ce-keyUsage': return $this->KeyUsage; case 'id-ce-basicConstraints': return $this->BasicConstraints; case 'id-ce-subjectKeyIdentifier': return $this->KeyIdentifier; case 'id-ce-cRLDistributionPoints': return $this->CRLDistributionPoints; case 'id-ce-authorityKeyIdentifier': return $this->AuthorityKeyIdentifier; case 'id-ce-certificatePolicies': return $this->CertificatePolicies; case 'id-ce-extKeyUsage': return $this->ExtKeyUsageSyntax; case 'id-pe-authorityInfoAccess': return $this->AuthorityInfoAccessSyntax; case 'id-pe-subjectInfoAccess': return $this->SubjectInfoAccessSyntax; case 'id-ce-subjectAltName': return $this->SubjectAltName; case 'id-ce-subjectDirectoryAttributes': return $this->SubjectDirectoryAttributes; case 'id-ce-privateKeyUsagePeriod': return $this->PrivateKeyUsagePeriod; case 'id-ce-issuerAltName': return $this->IssuerAltName; case 'id-ce-policyMappings': return $this->PolicyMappings; case 'id-ce-nameConstraints': return $this->NameConstraints; case 'netscape-cert-type': return $this->netscape_cert_type; case 'netscape-comment': return $this->netscape_comment; case 'netscape-ca-policy-url': return $this->netscape_ca_policy_url; case 'id-qt-unotice': return $this->UserNotice; case 'id-pe-logotype': case 'entrustVersInfo': case '1.3.6.1.4.1.311.20.2': case '1.3.6.1.4.1.311.21.1': case '2.23.42.7.0': case '1.3.6.1.4.1.11129.2.4.2': case '1.3.6.1.5.5.7.1.3': return true; case 'pkcs-9-at-unstructuredName': return $this->PKCS9String; case 'pkcs-9-at-challengePassword': return $this->DirectoryString; case 'pkcs-9-at-extensionRequest': return $this->Extensions; case 'id-ce-cRLNumber': return $this->CRLNumber; case 'id-ce-deltaCRLIndicator': return $this->CRLNumber; case 'id-ce-issuingDistributionPoint': return $this->IssuingDistributionPoint; case 'id-ce-freshestCRL': return $this->CRLDistributionPoints; case 'id-ce-cRLReasons': return $this->CRLReason; case 'id-ce-invalidityDate': return $this->InvalidityDate; case 'id-ce-certificateIssuer': return $this->CertificateIssuer; case 'id-ce-holdInstructionCode': return $this->HoldInstructionCode; case 'id-at-postalAddress': return $this->PostalAddress; } return false; } function loadCA($cert) { $olddn = $this->dn; $oldcert = $this->currentCert; $oldsigsubj = $this->signatureSubject; $oldkeyid = $this->currentKeyIdentifier; $cert = $this->loadX509($cert); if (!$cert) { $this->dn = $olddn; $this->currentCert = $oldcert; $this->signatureSubject = $oldsigsubj; $this->currentKeyIdentifier = $oldkeyid; return false; } $this->CAs[] = $cert; $this->dn = $olddn; $this->currentCert = $oldcert; $this->signatureSubject = $oldsigsubj; return true; } function validateURL($url) { if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; } $components = parse_url($url); if (!isset($components['host'])) { return false; } if ($names = $this->getExtension('id-ce-subjectAltName')) { foreach ($names as $name) { foreach ($name as $key => $value) { $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); switch ($key) { case 'dNSName': if (preg_match('#^' . $value . '$#', $components['host'])) { return true; } break; case 'iPAddress': if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) { return true; } } } } return false; } if ($value = $this->getDNProp('id-at-commonName')) { $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]); return preg_match('#^' . $value . '$#', $components['host']); } return false; } function validateDate($date = null) { if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; } if (!isset($date)) { $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get())); } $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime']; $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter']; $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; if (is_string($date)) { $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); } $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())); $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())); switch (true) { case $date < $notBefore: case $date > $notAfter: return false; } return true; } static function _fetchURL($url) { if (self::$disable_url_fetch) { return false; } $parts = parse_url($url); $data = ''; switch ($parts['scheme']) { case 'http': $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80); if (!$fsock) { return false; } fputs($fsock, "GET $parts[path] HTTP/1.0\r\n"); fputs($fsock, "Host: $parts[host]\r\n\r\n"); $line = fgets($fsock, 1024); if (strlen($line) < 3) { return false; } preg_match('#HTTP/1.\d (\d{3})#', $line, $temp); if ($temp[1] != '200') { return false; } while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") { } while (!feof($fsock)) { $temp = fread($fsock, 1024); if ($temp === false) { return false; } $data.= $temp; } break; } return $data; } function _testForIntermediate($caonly, $count) { $opts = $this->getExtension('id-pe-authorityInfoAccess'); if (!is_array($opts)) { return false; } foreach ($opts as $opt) { if ($opt['accessMethod'] == 'id-ad-caIssuers') { if (isset($opt['accessLocation']['uniformResourceIdentifier'])) { $url = $opt['accessLocation']['uniformResourceIdentifier']; break; } } } if (!isset($url)) { return false; } $cert = static::_fetchURL($url); if (!is_string($cert)) { return false; } $parent = new static(); $parent->CAs = $this->CAs; if (!is_array($parent->loadX509($cert))) { return false; } if (!$parent->_validateSignatureCountable($caonly, ++$count)) { return false; } $this->CAs[] = $parent->currentCert; return true; } function validateSignature($caonly = true) { return $this->_validateSignatureCountable($caonly, 0); } function _validateSignatureCountable($caonly, $count) { if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { return null; } if ($count == self::$recur_limit) { return false; } switch (true) { case isset($this->currentCert['tbsCertificate']): switch (true) { case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']: case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING): $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier'); switch (true) { case !is_array($authorityKey): case !$subjectKeyID: case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: $signingCert = $this->currentCert; } } if (!empty($this->CAs)) { for ($i = 0; $i < count($this->CAs); $i++) { $ca = $this->CAs[$i]; switch (true) { case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']: case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']): $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); switch (true) { case !is_array($authorityKey): case !$subjectKeyID: case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { break 2; } $signingCert = $ca; break 3; } } } if (count($this->CAs) == $i && $caonly) { return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } } elseif (!isset($signingCert) || $caonly) { return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } return $this->_validateSignature( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], substr(base64_decode($this->currentCert['signature']), 1), $this->signatureSubject ); case isset($this->currentCert['certificationRequestInfo']): return $this->_validateSignature( $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'], $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], substr(base64_decode($this->currentCert['signature']), 1), $this->signatureSubject ); case isset($this->currentCert['publicKeyAndChallenge']): return $this->_validateSignature( $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'], $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], substr(base64_decode($this->currentCert['signature']), 1), $this->signatureSubject ); case isset($this->currentCert['tbsCertList']): if (!empty($this->CAs)) { for ($i = 0; $i < count($this->CAs); $i++) { $ca = $this->CAs[$i]; switch (true) { case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']: case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']): $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); switch (true) { case !is_array($authorityKey): case !$subjectKeyID: case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { break 2; } $signingCert = $ca; break 3; } } } } if (!isset($signingCert)) { return false; } return $this->_validateSignature( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $this->currentCert['signatureAlgorithm']['algorithm'], substr(base64_decode($this->currentCert['signature']), 1), $this->signatureSubject ); default: return false; } } function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) { switch ($publicKeyAlgorithm) { case 'rsaEncryption': $rsa = new RSA(); $rsa->loadKey($publicKey); switch ($signatureAlgorithm) { case 'md2WithRSAEncryption': case 'md5WithRSAEncryption': case 'sha1WithRSAEncryption': case 'sha224WithRSAEncryption': case 'sha256WithRSAEncryption': case 'sha384WithRSAEncryption': case 'sha512WithRSAEncryption': $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); if (!@$rsa->verify($signatureSubject, $signature)) { return false; } break; default: return null; } break; default: return null; } return true; } static function setRecurLimit($count) { self::$recur_limit = $count; } static function disableURLFetch() { self::$disable_url_fetch = true; } static function enableURLFetch() { self::$disable_url_fetch = false; } function _reformatKey($algorithm, $key) { switch ($algorithm) { case 'rsaEncryption': return "-----BEGIN RSA PUBLIC KEY-----\r\n" . chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) . '-----END RSA PUBLIC KEY-----'; default: return $key; } } function _decodeIP($ip) { return inet_ntop(base64_decode($ip)); } function _decodeNameConstraintIP($ip) { $ip = base64_decode($ip); $size = strlen($ip) >> 1; $mask = substr($ip, $size); $ip = substr($ip, 0, $size); return array(inet_ntop($ip), inet_ntop($mask)); } function _encodeIP($ip) { return is_string($ip) ? base64_encode(inet_pton($ip)) : base64_encode(inet_pton($ip[0]) . inet_pton($ip[1])); } function _translateDNProp($propName) { switch (strtolower($propName)) { case 'id-at-countryname': case 'countryname': case 'c': return 'id-at-countryName'; case 'id-at-organizationname': case 'organizationname': case 'o': return 'id-at-organizationName'; case 'id-at-dnqualifier': case 'dnqualifier': return 'id-at-dnQualifier'; case 'id-at-commonname': case 'commonname': case 'cn': return 'id-at-commonName'; case 'id-at-stateorprovincename': case 'stateorprovincename': case 'state': case 'province': case 'provincename': case 'st': return 'id-at-stateOrProvinceName'; case 'id-at-localityname': case 'localityname': case 'l': return 'id-at-localityName'; case 'id-emailaddress': case 'emailaddress': return 'pkcs-9-at-emailAddress'; case 'id-at-serialnumber': case 'serialnumber': return 'id-at-serialNumber'; case 'id-at-postalcode': case 'postalcode': return 'id-at-postalCode'; case 'id-at-streetaddress': case 'streetaddress': return 'id-at-streetAddress'; case 'id-at-name': case 'name': return 'id-at-name'; case 'id-at-givenname': case 'givenname': return 'id-at-givenName'; case 'id-at-surname': case 'surname': case 'sn': return 'id-at-surname'; case 'id-at-initials': case 'initials': return 'id-at-initials'; case 'id-at-generationqualifier': case 'generationqualifier': return 'id-at-generationQualifier'; case 'id-at-organizationalunitname': case 'organizationalunitname': case 'ou': return 'id-at-organizationalUnitName'; case 'id-at-pseudonym': case 'pseudonym': return 'id-at-pseudonym'; case 'id-at-title': case 'title': return 'id-at-title'; case 'id-at-description': case 'description': return 'id-at-description'; case 'id-at-role': case 'role': return 'id-at-role'; case 'id-at-uniqueidentifier': case 'uniqueidentifier': case 'x500uniqueidentifier': return 'id-at-uniqueIdentifier'; case 'postaladdress': case 'id-at-postaladdress': return 'id-at-postalAddress'; default: return false; } } function setDNProp($propName, $propValue, $type = 'utf8String') { if (empty($this->dn)) { $this->dn = array('rdnSequence' => array()); } if (($propName = $this->_translateDNProp($propName)) === false) { return false; } foreach ((array) $propValue as $v) { if (!is_array($v) && isset($type)) { $v = array($type => $v); } $this->dn['rdnSequence'][] = array( array( 'type' => $propName, 'value'=> $v ) ); } return true; } function removeDNProp($propName) { if (empty($this->dn)) { return; } if (($propName = $this->_translateDNProp($propName)) === false) { return; } $dn = &$this->dn['rdnSequence']; $size = count($dn); for ($i = 0; $i < $size; $i++) { if ($dn[$i][0]['type'] == $propName) { unset($dn[$i]); } } $dn = array_values($dn); if (!isset($dn[0])) { $dn = array_splice($dn, 0, 0); } } function getDNProp($propName, $dn = null, $withType = false) { if (!isset($dn)) { $dn = $this->dn; } if (empty($dn)) { return false; } if (($propName = $this->_translateDNProp($propName)) === false) { return false; } $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $asn1->loadFilters($filters); $this->_mapOutDNs($dn, 'rdnSequence', $asn1); $dn = $dn['rdnSequence']; $result = array(); for ($i = 0; $i < count($dn); $i++) { if ($dn[$i][0]['type'] == $propName) { $v = $dn[$i][0]['value']; if (!$withType) { if (is_array($v)) { foreach ($v as $type => $s) { $type = array_search($type, $asn1->ANYmap, true); if ($type !== false && isset($asn1->stringTypeSize[$type])) { $s = $asn1->convert($s, $type); if ($s !== false) { $v = $s; break; } } } if (is_array($v)) { $v = array_pop($v); } } elseif (is_object($v) && $v instanceof Element) { $map = $this->_getMapping($propName); if (!is_bool($map)) { $decoded = $asn1->decodeBER($v); $v = $asn1->asn1map($decoded[0], $map); } } } $result[] = $v; } } return $result; } function setDN($dn, $merge = false, $type = 'utf8String') { if (!$merge) { $this->dn = null; } if (is_array($dn)) { if (isset($dn['rdnSequence'])) { $this->dn = $dn; return true; } foreach ($dn as $prop => $value) { if (!$this->setDNProp($prop, $value, $type)) { return false; } } return true; } $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); for ($i = 1; $i < count($results); $i+=2) { $prop = trim($results[$i], ', =/'); $value = $results[$i + 1]; if (!$this->setDNProp($prop, $value, $type)) { return false; } } return true; } function getDN($format = self::DN_ARRAY, $dn = null) { if (!isset($dn)) { $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn; } switch ((int) $format) { case self::DN_ARRAY: return $dn; case self::DN_ASN1: $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $asn1->loadFilters($filters); $this->_mapOutDNs($dn, 'rdnSequence', $asn1); return $asn1->encodeDER($dn, $this->Name); case self::DN_CANON: $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $asn1->loadFilters($filters); $result = ''; $this->_mapOutDNs($dn, 'rdnSequence', $asn1); foreach ($dn['rdnSequence'] as $rdn) { foreach ($rdn as $i => $attr) { $attr = &$rdn[$i]; if (is_array($attr['value'])) { foreach ($attr['value'] as $type => $v) { $type = array_search($type, $asn1->ANYmap, true); if ($type !== false && isset($asn1->stringTypeSize[$type])) { $v = $asn1->convert($v, $type); if ($v !== false) { $v = preg_replace('/\s+/', ' ', $v); $attr['value'] = strtolower(trim($v)); break; } } } } } $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName); } return $result; case self::DN_HASH: $dn = $this->getDN(self::DN_CANON, $dn); $hash = new Hash('sha1'); $hash = $hash->hash($dn); extract(unpack('Vhash', $hash)); return strtolower(bin2hex(pack('N', $hash))); } $start = true; $output = ''; $result = array(); $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $asn1->loadFilters($filters); $this->_mapOutDNs($dn, 'rdnSequence', $asn1); foreach ($dn['rdnSequence'] as $field) { $prop = $field[0]['type']; $value = $field[0]['value']; $delim = ', '; switch ($prop) { case 'id-at-countryName': $desc = 'C'; break; case 'id-at-stateOrProvinceName': $desc = 'ST'; break; case 'id-at-organizationName': $desc = 'O'; break; case 'id-at-organizationalUnitName': $desc = 'OU'; break; case 'id-at-commonName': $desc = 'CN'; break; case 'id-at-localityName': $desc = 'L'; break; case 'id-at-surname': $desc = 'SN'; break; case 'id-at-uniqueIdentifier': $delim = '/'; $desc = 'x500UniqueIdentifier'; break; case 'id-at-postalAddress': $delim = '/'; $desc = 'postalAddress'; break; default: $delim = '/'; $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop); } if (!$start) { $output.= $delim; } if (is_array($value)) { foreach ($value as $type => $v) { $type = array_search($type, $asn1->ANYmap, true); if ($type !== false && isset($asn1->stringTypeSize[$type])) { $v = $asn1->convert($v, $type); if ($v !== false) { $value = $v; break; } } } if (is_array($value)) { $value = array_pop($value); } } elseif (is_object($value) && $value instanceof Element) { $callback = function ($x) { return "\x" . bin2hex($x[0]); }; $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element)); } $output.= $desc . '=' . $value; $result[$desc] = isset($result[$desc]) ? array_merge((array) $result[$desc], array($value)) : $value; $start = false; } return $format == self::DN_OPENSSL ? $result : $output; } function getIssuerDN($format = self::DN_ARRAY) { switch (true) { case !isset($this->currentCert) || !is_array($this->currentCert): break; case isset($this->currentCert['tbsCertificate']): return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']); case isset($this->currentCert['tbsCertList']): return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']); } return false; } function getSubjectDN($format = self::DN_ARRAY) { switch (true) { case !empty($this->dn): return $this->getDN($format); case !isset($this->currentCert) || !is_array($this->currentCert): break; case isset($this->currentCert['tbsCertificate']): return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']); case isset($this->currentCert['certificationRequestInfo']): return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']); } return false; } function getIssuerDNProp($propName, $withType = false) { switch (true) { case !isset($this->currentCert) || !is_array($this->currentCert): break; case isset($this->currentCert['tbsCertificate']): return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType); case isset($this->currentCert['tbsCertList']): return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType); } return false; } function getSubjectDNProp($propName, $withType = false) { switch (true) { case !empty($this->dn): return $this->getDNProp($propName, null, $withType); case !isset($this->currentCert) || !is_array($this->currentCert): break; case isset($this->currentCert['tbsCertificate']): return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType); case isset($this->currentCert['certificationRequestInfo']): return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType); } return false; } function getChain() { $chain = array($this->currentCert); if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { return false; } if (empty($this->CAs)) { return $chain; } while (true) { $currentCert = $chain[count($chain) - 1]; for ($i = 0; $i < count($this->CAs); $i++) { $ca = $this->CAs[$i]; if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) { $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert); $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); switch (true) { case !is_array($authorityKey): case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: if ($currentCert === $ca) { break 3; } $chain[] = $ca; break 2; } } } if ($i == count($this->CAs)) { break; } } foreach ($chain as $key => $value) { $chain[$key] = new X509(); $chain[$key]->loadX509($value); } return $chain; } function setPublicKey($key) { $key->setPublicKey(); $this->publicKey = $key; } function setPrivateKey($key) { $this->privateKey = $key; } function setChallenge($challenge) { $this->challenge = $challenge; } function getPublicKey() { if (isset($this->publicKey)) { return $this->publicKey; } if (isset($this->currentCert) && is_array($this->currentCert)) { foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) { $keyinfo = $this->_subArray($this->currentCert, $path); if (!empty($keyinfo)) { break; } } } if (empty($keyinfo)) { return false; } $key = $keyinfo['subjectPublicKey']; switch ($keyinfo['algorithm']['algorithm']) { case 'rsaEncryption': $publicKey = new RSA(); $publicKey->loadKey($key); $publicKey->setPublicKey(); break; default: return false; } return $publicKey; } function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($csr) && isset($csr['certificationRequestInfo'])) { unset($this->currentCert); unset($this->currentKeyIdentifier); unset($this->signatureSubject); $this->dn = $csr['certificationRequestInfo']['subject']; if (!isset($this->dn)) { return false; } $this->currentCert = $csr; return $csr; } $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { $newcsr = $this->_extractBER($csr); if ($mode == self::FORMAT_PEM && $csr == $newcsr) { return false; } $csr = $newcsr; } $orig = $csr; if ($csr === false) { $this->currentCert = false; return false; } $asn1->loadOIDs($this->oids); $decoded = $asn1->decodeBER($csr); if (empty($decoded)) { $this->currentCert = false; return false; } $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); if (!isset($csr) || $csr === false) { $this->currentCert = false; return false; } $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1); $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); $this->dn = $csr['certificationRequestInfo']['subject']; $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; $key = $this->_reformatKey($algorithm, $key); switch ($algorithm) { case 'rsaEncryption': $this->publicKey = new RSA(); $this->publicKey->loadKey($key); $this->publicKey->setPublicKey(); break; default: $this->publicKey = null; } $this->currentKeyIdentifier = null; $this->currentCert = $csr; return $csr; } function saveCSR($csr, $format = self::FORMAT_PEM) { if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { return false; } switch (true) { case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')): case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): break; default: switch ($algorithm) { case 'rsaEncryption': $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; $csr['signatureAlgorithm']['parameters'] = null; $csr['certificationRequestInfo']['signature']['parameters'] = null; } } $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $asn1->loadFilters($filters); $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1); $csr = $asn1->encodeDER($csr, $this->CertificationRequest); switch ($format) { case self::FORMAT_DER: return $csr; default: return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----'; } } function loadSPKAC($spkac) { if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) { unset($this->currentCert); unset($this->currentKeyIdentifier); unset($this->signatureSubject); $this->currentCert = $spkac; return $spkac; } $asn1 = new ASN1(); $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac); $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; if ($temp != false) { $spkac = $temp; } $orig = $spkac; if ($spkac === false) { $this->currentCert = false; return false; } $asn1->loadOIDs($this->oids); $decoded = $asn1->decodeBER($spkac); if (empty($decoded)) { $this->currentCert = false; return false; } $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge); if (!isset($spkac) || $spkac === false) { $this->currentCert = false; return false; } $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm']; $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']; $key = $this->_reformatKey($algorithm, $key); switch ($algorithm) { case 'rsaEncryption': $this->publicKey = new RSA(); $this->publicKey->loadKey($key); $this->publicKey->setPublicKey(); break; default: $this->publicKey = null; } $this->currentKeyIdentifier = null; $this->currentCert = $spkac; return $spkac; } function saveSPKAC($spkac, $format = self::FORMAT_PEM) { if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) { return false; } $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm'); switch (true) { case !$algorithm: case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']): break; default: switch ($algorithm) { case 'rsaEncryption': $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))); } } $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge); switch ($format) { case self::FORMAT_DER: return $spkac; default: return 'SPKAC=' . base64_encode($spkac); } } function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) { if (is_array($crl) && isset($crl['tbsCertList'])) { $this->currentCert = $crl; unset($this->signatureSubject); return $crl; } $asn1 = new ASN1(); if ($mode != self::FORMAT_DER) { $newcrl = $this->_extractBER($crl); if ($mode == self::FORMAT_PEM && $crl == $newcrl) { return false; } $crl = $newcrl; } $orig = $crl; if ($crl === false) { $this->currentCert = false; return false; } $asn1->loadOIDs($this->oids); $decoded = $asn1->decodeBER($crl); if (empty($decoded)) { $this->currentCert = false; return false; } $crl = $asn1->asn1map($decoded[0], $this->CertificateList); if (!isset($crl) || $crl === false) { $this->currentCert = false; return false; } $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) { $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); } if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) { $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates'); if ($rclist_ref) { $rclist = $crl['tbsCertList']['revokedCertificates']; foreach ($rclist as $i => $extension) { if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) { $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1); } } } } $this->currentKeyIdentifier = null; $this->currentCert = $crl; return $crl; } function saveCRL($crl, $format = self::FORMAT_PEM) { if (!is_array($crl) || !isset($crl['tbsCertList'])) { return false; } $asn1 = new ASN1(); $asn1->loadOIDs($this->oids); $filters = array(); $filters['tbsCertList']['issuer']['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); $filters['tbsCertList']['signature']['parameters'] = array('type' => ASN1::TYPE_UTF8_STRING); $filters['signatureAlgorithm']['parameters'] = array('type' => ASN1::TYPE_UTF8_STRING); if (empty($crl['tbsCertList']['signature']['parameters'])) { $filters['tbsCertList']['signature']['parameters'] = array('type' => ASN1::TYPE_NULL); } if (empty($crl['signatureAlgorithm']['parameters'])) { $filters['signatureAlgorithm']['parameters'] = array('type' => ASN1::TYPE_NULL); } $asn1->loadFilters($filters); $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1); $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates'); if (is_array($rclist)) { foreach ($rclist as $i => $extension) { $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1); } } $crl = $asn1->encodeDER($crl, $this->CertificateList); switch ($format) { case self::FORMAT_DER: return $crl; default: return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----'; } } function _timeField($date) { if ($date instanceof Element) { return $date; } $dateObj = new DateTime($date, new DateTimeZone('GMT')); $year = $dateObj->format('Y'); if ($year < 2050) { return array('utcTime' => $date); } else { return array('generalTime' => $date); } } function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') { if (!is_object($issuer->privateKey) || empty($issuer->dn)) { return false; } if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) { return false; } $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { $this->currentCert = $subject->currentCert; $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm; $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; if (!empty($this->startDate)) { $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate); } if (!empty($this->endDate)) { $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate); } if (!empty($this->serialNumber)) { $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber; } if (!empty($subject->dn)) { $this->currentCert['tbsCertificate']['subject'] = $subject->dn; } if (!empty($subject->publicKey)) { $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey; } $this->removeExtension('id-ce-authorityKeyIdentifier'); if (isset($subject->domains)) { $this->removeExtension('id-ce-subjectAltName'); } } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) { return false; } else { if (!isset($subject->publicKey)) { return false; } $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); $serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256); $this->currentCert = array( 'tbsCertificate' => array( 'version' => 'v3', 'serialNumber' => $serialNumber, 'signature' => array('algorithm' => $signatureAlgorithm), 'issuer' => false, 'validity' => array( 'notBefore' => $this->_timeField($startDate), 'notAfter' => $this->_timeField($endDate) ), 'subject' => $subject->dn, 'subjectPublicKeyInfo' => $subjectPublicKey ), 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 'signature' => false ); $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0); if (!empty($csrexts)) { $this->currentCert['tbsCertificate']['extensions'] = $csrexts; } } $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn; if (isset($issuer->currentKeyIdentifier)) { $this->setExtension('id-ce-authorityKeyIdentifier', array( 'keyIdentifier' => $issuer->currentKeyIdentifier )); } if (isset($subject->currentKeyIdentifier)) { $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier); } $altName = array(); if (isset($subject->domains) && count($subject->domains)) { $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains); } if (isset($subject->ipAddresses) && count($subject->ipAddresses)) { $ipAddresses = array(); foreach ($subject->ipAddresses as $ipAddress) { $encoded = $subject->_ipAddress($ipAddress); if ($encoded !== false) { $ipAddresses[] = $encoded; } } if (count($ipAddresses)) { $altName = array_merge($altName, $ipAddresses); } } if (!empty($altName)) { $this->setExtension('id-ce-subjectAltName', $altName); } if ($this->caFlag) { $keyUsage = $this->getExtension('id-ce-keyUsage'); if (!$keyUsage) { $keyUsage = array(); } $this->setExtension( 'id-ce-keyUsage', array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign')))) ); $basicConstraints = $this->getExtension('id-ce-basicConstraints'); if (!$basicConstraints) { $basicConstraints = array(); } $this->setExtension( 'id-ce-basicConstraints', array_unique(array_merge(array('cA' => true), $basicConstraints)), true ); if (!isset($subject->currentKeyIdentifier)) { $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false); } } $tbsCertificate = $this->currentCert['tbsCertificate']; $this->loadX509($this->saveX509($this->currentCert)); $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); $result['tbsCertificate'] = $tbsCertificate; $this->currentCert = $currentCert; $this->signatureSubject = $signatureSubject; return $result; } function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') { if (!is_object($this->privateKey) || empty($this->dn)) { return false; } $origPublicKey = $this->publicKey; $class = get_class($this->privateKey); $this->publicKey = new $class(); $this->publicKey->loadKey($this->privateKey->getPublicKey()); $this->publicKey->setPublicKey(); if (!($publicKey = $this->_formatSubjectPublicKey())) { return false; } $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; if (!empty($this->dn)) { $this->currentCert['certificationRequestInfo']['subject'] = $this->dn; } $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; } else { $this->currentCert = array( 'certificationRequestInfo' => array( 'version' => 'v1', 'subject' => $this->dn, 'subjectPKInfo' => $publicKey ), 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 'signature' => false ); } $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; $this->loadCSR($this->saveCSR($this->currentCert)); $result = $this->_sign($this->privateKey, $signatureAlgorithm); $result['certificationRequestInfo'] = $certificationRequestInfo; $this->currentCert = $currentCert; $this->signatureSubject = $signatureSubject; return $result; } function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') { if (!is_object($this->privateKey)) { return false; } $origPublicKey = $this->publicKey; $class = get_class($this->privateKey); $this->publicKey = new $class(); $this->publicKey->loadKey($this->privateKey->getPublicKey()); $this->publicKey->setPublicKey(); $publicKey = $this->_formatSubjectPublicKey(); if (!$publicKey) { return false; } $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) { $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey; if (!empty($this->challenge)) { $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge)); } } else { $this->currentCert = array( 'publicKeyAndChallenge' => array( 'spki' => $publicKey, 'challenge' => !empty($this->challenge) ? $this->challenge : '' ), 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 'signature' => false ); } $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge']; $this->loadSPKAC($this->saveSPKAC($this->currentCert)); $result = $this->_sign($this->privateKey, $signatureAlgorithm); $result['publicKeyAndChallenge'] = $publicKeyAndChallenge; $this->currentCert = $currentCert; $this->signatureSubject = $signatureSubject; return $result; } function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') { if (!is_object($issuer->privateKey) || empty($issuer->dn)) { return false; } $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { $this->currentCert = $crl->currentCert; $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm; $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; } else { $this->currentCert = array( 'tbsCertList' => array( 'version' => 'v2', 'signature' => array('algorithm' => $signatureAlgorithm), 'issuer' => false, 'thisUpdate' => $this->_timeField($thisUpdate) ), 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 'signature' => false ); } $tbsCertList = &$this->currentCert['tbsCertList']; $tbsCertList['issuer'] = $issuer->dn; $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate); if (!empty($this->endDate)) { $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); } else { unset($tbsCertList['nextUpdate']); } if (!empty($this->serialNumber)) { $crlNumber = $this->serialNumber; } else { $crlNumber = $this->getExtension('id-ce-cRLNumber'); $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null; } $this->removeExtension('id-ce-authorityKeyIdentifier'); $this->removeExtension('id-ce-issuerAltName'); $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0; if (!$version) { if (!empty($tbsCertList['crlExtensions'])) { $version = 1; } elseif (!empty($tbsCertList['revokedCertificates'])) { foreach ($tbsCertList['revokedCertificates'] as $cert) { if (!empty($cert['crlEntryExtensions'])) { $version = 1; } } } if ($version) { $tbsCertList['version'] = $version; } } if (!empty($tbsCertList['version'])) { if (!empty($crlNumber)) { $this->setExtension('id-ce-cRLNumber', $crlNumber); } if (isset($issuer->currentKeyIdentifier)) { $this->setExtension('id-ce-authorityKeyIdentifier', array( 'keyIdentifier' => $issuer->currentKeyIdentifier )); } $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert); if ($issuerAltName !== false) { $this->setExtension('id-ce-issuerAltName', $issuerAltName); } } if (empty($tbsCertList['revokedCertificates'])) { unset($tbsCertList['revokedCertificates']); } unset($tbsCertList); $tbsCertList = $this->currentCert['tbsCertList']; $this->loadCRL($this->saveCRL($this->currentCert)); $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); $result['tbsCertList'] = $tbsCertList; $this->currentCert = $currentCert; $this->signatureSubject = $signatureSubject; return $result; } function _sign($key, $signatureAlgorithm) { if ($key instanceof RSA) { switch ($signatureAlgorithm) { case 'md2WithRSAEncryption': case 'md5WithRSAEncryption': case 'sha1WithRSAEncryption': case 'sha224WithRSAEncryption': case 'sha256WithRSAEncryption': case 'sha384WithRSAEncryption': case 'sha512WithRSAEncryption': $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); $key->setSignatureMode(RSA::SIGNATURE_PKCS1); $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); return $this->currentCert; } } return false; } function setStartDate($date) { if (!is_object($date) || !is_a($date, 'DateTime')) { $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); } $this->startDate = $date->format('D, d M Y H:i:s O'); } function setEndDate($date) { if (strtolower($date) == 'lifetime') { $temp = '99991231235959Z'; $asn1 = new ASN1(); $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; $this->endDate = new Element($temp); } else { if (!is_object($date) || !is_a($date, 'DateTime')) { $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); } $this->endDate = $date->format('D, d M Y H:i:s O'); } } function setSerialNumber($serial, $base = -256) { $this->serialNumber = new BigInteger($serial, $base); } function makeCA() { $this->caFlag = true; } function _isSubArrayValid($root, $path) { if (!is_array($root)) { return false; } foreach (explode('/', $path) as $i) { if (!is_array($root)) { return false; } if (!isset($root[$i])) { return true; } $root = $root[$i]; } return true; } function &_subArrayUnchecked(&$root, $path, $create = false) { $false = false; foreach (explode('/', $path) as $i) { if (!isset($root[$i])) { if (!$create) { return $false; } $root[$i] = array(); } $root = &$root[$i]; } return $root; } function &_subArray(&$root, $path, $create = false) { $false = false; if (!is_array($root)) { return $false; } foreach (explode('/', $path) as $i) { if (!is_array($root)) { return $false; } if (!isset($root[$i])) { if (!$create) { return $false; } $root[$i] = array(); } $root = &$root[$i]; } return $root; } function &_extensions(&$root, $path = null, $create = false) { if (!isset($root)) { $root = $this->currentCert; } switch (true) { case !empty($path): case !is_array($root): break; case isset($root['tbsCertificate']): $path = 'tbsCertificate/extensions'; break; case isset($root['tbsCertList']): $path = 'tbsCertList/crlExtensions'; break; case isset($root['certificationRequestInfo']): $pth = 'certificationRequestInfo/attributes'; $attributes = &$this->_subArray($root, $pth, $create); if (is_array($attributes)) { foreach ($attributes as $key => $value) { if ($value['type'] == 'pkcs-9-at-extensionRequest') { $path = "$pth/$key/value/0"; break 2; } } if ($create) { $key = count($attributes); $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array()); $path = "$pth/$key/value/0"; } } break; } $extensions = &$this->_subArray($root, $path, $create); if (!is_array($extensions)) { $false = false; return $false; } return $extensions; } function _removeExtension($id, $path = null) { $extensions = &$this->_extensions($this->currentCert, $path); if (!is_array($extensions)) { return false; } $result = false; foreach ($extensions as $key => $value) { if ($value['extnId'] == $id) { unset($extensions[$key]); $result = true; } } $extensions = array_values($extensions); if (!isset($extensions[0])) { $extensions = array_splice($extensions, 0, 0); } return $result; } function _getExtension($id, $cert = null, $path = null) { $extensions = $this->_extensions($cert, $path); if (!is_array($extensions)) { return false; } foreach ($extensions as $key => $value) { if ($value['extnId'] == $id) { return $value['extnValue']; } } return false; } function _getExtensions($cert = null, $path = null) { $exts = $this->_extensions($cert, $path); $extensions = array(); if (is_array($exts)) { foreach ($exts as $extension) { $extensions[] = $extension['extnId']; } } return $extensions; } function _setExtension($id, $value, $critical = false, $replace = true, $path = null) { $extensions = &$this->_extensions($this->currentCert, $path, true); if (!is_array($extensions)) { return false; } $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value); foreach ($extensions as $key => $value) { if ($value['extnId'] == $id) { if (!$replace) { return false; } $extensions[$key] = $newext; return true; } } $extensions[] = $newext; return true; } function removeExtension($id) { return $this->_removeExtension($id); } function getExtension($id, $cert = null) { return $this->_getExtension($id, $cert); } function getExtensions($cert = null) { return $this->_getExtensions($cert); } function setExtension($id, $value, $critical = false, $replace = true) { return $this->_setExtension($id, $value, $critical, $replace); } function removeAttribute($id, $disposition = self::ATTR_ALL) { $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes'); if (!is_array($attributes)) { return false; } $result = false; foreach ($attributes as $key => $attribute) { if ($attribute['type'] == $id) { $n = count($attribute['value']); switch (true) { case $disposition == self::ATTR_APPEND: case $disposition == self::ATTR_REPLACE: return false; case $disposition >= $n: $disposition -= $n; break; case $disposition == self::ATTR_ALL: case $n == 1: unset($attributes[$key]); $result = true; break; default: unset($attributes[$key]['value'][$disposition]); $attributes[$key]['value'] = array_values($attributes[$key]['value']); $result = true; break; } if ($result && $disposition != self::ATTR_ALL) { break; } } } $attributes = array_values($attributes); return $result; } function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null) { if (empty($csr)) { $csr = $this->currentCert; } $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); if (!is_array($attributes)) { return false; } foreach ($attributes as $key => $attribute) { if ($attribute['type'] == $id) { $n = count($attribute['value']); switch (true) { case $disposition == self::ATTR_APPEND: case $disposition == self::ATTR_REPLACE: return false; case $disposition == self::ATTR_ALL: return $attribute['value']; case $disposition >= $n: $disposition -= $n; break; default: return $attribute['value'][$disposition]; } } } return false; } function getAttributes($csr = null) { if (empty($csr)) { $csr = $this->currentCert; } $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); $attrs = array(); if (is_array($attributes)) { foreach ($attributes as $attribute) { $attrs[] = $attribute['type']; } } return $attrs; } function setAttribute($id, $value, $disposition = self::ATTR_ALL) { $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true); if (!is_array($attributes)) { return false; } switch ($disposition) { case self::ATTR_REPLACE: $disposition = self::ATTR_APPEND; case self::ATTR_ALL: $this->removeAttribute($id); break; } foreach ($attributes as $key => $attribute) { if ($attribute['type'] == $id) { $n = count($attribute['value']); switch (true) { case $disposition == self::ATTR_APPEND: $last = $key; break; case $disposition >= $n: $disposition -= $n; break; default: $attributes[$key]['value'][$disposition] = $value; return true; } } } switch (true) { case $disposition >= 0: return false; case isset($last): $attributes[$last]['value'][] = $value; break; default: $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value)); break; } return true; } function setKeyIdentifier($value) { if (empty($value)) { unset($this->currentKeyIdentifier); } else { $this->currentKeyIdentifier = base64_encode($value); } } function computeKeyIdentifier($key = null, $method = 1) { if (is_null($key)) { $key = $this; } switch (true) { case is_string($key): break; case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method); case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method); case !is_object($key): return false; case $key instanceof Element: $asn1 = new ASN1(); $decoded = $asn1->decodeBER($key->element); if (empty($decoded)) { return false; } $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING)); if (empty($raw)) { return false; } $raw = base64_decode($raw); $key = new RSA(); if (!$key->loadKey($raw)) { return false; } if ($key->getPrivateKey() !== false) { return $this->computeKeyIdentifier($key, $method); } $key = $raw; break; case $key instanceof X509: if (isset($key->publicKey)) { return $this->computeKeyIdentifier($key->publicKey, $method); } if (isset($key->privateKey)) { return $this->computeKeyIdentifier($key->privateKey, $method); } if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) { return $this->computeKeyIdentifier($key->currentCert, $method); } return false; default: $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); break; } $key = $this->_extractBER($key); $hash = new Hash('sha1'); $hash = $hash->hash($key); if ($method == 2) { $hash = substr($hash, -8); $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40); } return $hash; } function _formatSubjectPublicKey() { if ($this->publicKey instanceof RSA) { return array( 'algorithm' => array('algorithm' => 'rsaEncryption'), 'subjectPublicKey' => $this->publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1) ); } return false; } function setDomain() { $this->domains = func_get_args(); $this->removeDNProp('id-at-commonName'); $this->setDNProp('id-at-commonName', $this->domains[0]); } function setIPAddress() { $this->ipAddresses = func_get_args(); } function _dnsName($domain) { return array('dNSName' => $domain); } function _iPAddress($address) { return array('iPAddress' => $address); } function _revokedCertificate(&$rclist, $serial, $create = false) { $serial = new BigInteger($serial); foreach ($rclist as $i => $rc) { if (!($serial->compare($rc['userCertificate']))) { return $i; } } if (!$create) { return false; } $i = count($rclist); $revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); $rclist[] = array('userCertificate' => $serial, 'revocationDate' => $this->_timeField($revocationDate->format('D, d M Y H:i:s O'))); return $i; } function revoke($serial, $date = null) { if (isset($this->currentCert['tbsCertList'])) { if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { if ($this->_revokedCertificate($rclist, $serial) === false) { if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { if (!empty($date)) { $rclist[$i]['revocationDate'] = $this->_timeField($date); } return true; } } } } return false; } function unrevoke($serial) { if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { unset($rclist[$i]); $rclist = array_values($rclist); return true; } } return false; } function getRevoked($serial) { if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { return $rclist[$i]; } } return false; } function listRevoked($crl = null) { if (!isset($crl)) { $crl = $this->currentCert; } if (!isset($crl['tbsCertList'])) { return false; } $result = array(); if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { foreach ($rclist as $rc) { $result[] = $rc['userCertificate']->toString(); } } return $result; } function removeRevokedCertificateExtension($serial, $id) { if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } return false; } function getRevokedCertificateExtension($serial, $id, $crl = null) { if (!isset($crl)) { $crl = $this->currentCert; } if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } return false; } function getRevokedCertificateExtensions($serial, $crl = null) { if (!isset($crl)) { $crl = $this->currentCert; } if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } return false; } function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true) { if (isset($this->currentCert['tbsCertList'])) { if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } } return false; } function _extractBER($str) { if (strlen($str) > ini_get('pcre.backtrack_limit')) { $temp = $str; } else { $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1); } $temp = str_replace(array("\r", "\n", ' '), '', $temp); $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp); $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; return $temp != false ? $temp : $str; } function getOID($name) { static $reverseMap; if (!isset($reverseMap)) { $reverseMap = array_flip($this->oids); } return isset($reverseMap[$name]) ? $reverseMap[$name] : $name; } } bold = false; $attr_cell->underline = false; $attr_cell->blink = false; $attr_cell->background = 'black'; $attr_cell->foreground = 'white'; $attr_cell->reverse = false; $this->base_attr_cell = clone $attr_cell; $this->attr_cell = clone $attr_cell; $this->setHistory(200); $this->setDimensions(80, 24); } function setDimensions($x, $y) { $this->max_x = $x - 1; $this->max_y = $y - 1; $this->x = $this->y = 0; $this->history = $this->history_attrs = array(); $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell); $this->screen = array_fill(0, $this->max_y + 1, ''); $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row); $this->ansi = ''; } function setHistory($history) { $this->max_history = $history; } function loadString($source) { $this->setDimensions($this->max_x + 1, $this->max_y + 1); $this->appendString($source); } function appendString($source) { $this->tokenization = array(''); for ($i = 0; $i < strlen($source); $i++) { if (strlen($this->ansi)) { $this->ansi.= $source[$i]; $chr = ord($source[$i]); switch (true) { case $this->ansi == "\x1B=": $this->ansi = ''; continue 2; case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['): case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126: break; default: continue 2; } $this->tokenization[] = $this->ansi; $this->tokenization[] = ''; switch ($this->ansi) { case "\x1B[H": $this->old_x = $this->x; $this->old_y = $this->y; $this->x = $this->y = 0; break; case "\x1B[J": $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y)); $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, '')); $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y)); $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row)); if (count($this->history) == $this->max_history) { array_shift($this->history); array_shift($this->history_attrs); } case "\x1B[K": $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x); array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - ($this->x - 1), $this->base_attr_cell)); break; case "\x1B[2K": $this->screen[$this->y] = str_repeat(' ', $this->x); $this->attrs[$this->y] = $this->attr_row; break; case "\x1B[?1h": case "\x1B[?25h": case "\x1B(B": break; case "\x1BE": $this->_newLine(); $this->x = 0; break; default: switch (true) { case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): $this->old_y = $this->y; $this->y+= $match[1]; break; case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): $this->old_x = $this->x; $this->old_y = $this->y; $this->x = $match[2] - 1; $this->y = $match[1] - 1; break; case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): $this->old_x = $this->x; $this->x+= $match[1]; break; case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): $this->old_x = $this->x; $this->x-= $match[1]; if ($this->x < 0) { $this->x = 0; } break; case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): break; case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): $attr_cell = &$this->attr_cell; $mods = explode(';', $match[1]); foreach ($mods as $mod) { switch ($mod) { case '': case '0': $attr_cell = clone $this->base_attr_cell; break; case '1': $attr_cell->bold = true; break; case '4': $attr_cell->underline = true; break; case '5': $attr_cell->blink = true; break; case '7': $attr_cell->reverse = !$attr_cell->reverse; $temp = $attr_cell->background; $attr_cell->background = $attr_cell->foreground; $attr_cell->foreground = $temp; break; default: $front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' }; $back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' }; switch ($mod) { case '30': $front = 'black'; break; case '31': $front = 'red'; break; case '32': $front = 'green'; break; case '33': $front = 'yellow'; break; case '34': $front = 'blue'; break; case '35': $front = 'magenta'; break; case '36': $front = 'cyan'; break; case '37': $front = 'white'; break; case '40': $back = 'black'; break; case '41': $back = 'red'; break; case '42': $back = 'green'; break; case '43': $back = 'yellow'; break; case '44': $back = 'blue'; break; case '45': $back = 'magenta'; break; case '46': $back = 'cyan'; break; case '47': $back = 'white'; break; default: $this->ansi = ''; break 2; } } } break; default: } } $this->ansi = ''; continue; } $this->tokenization[count($this->tokenization) - 1].= $source[$i]; switch ($source[$i]) { case "\r": $this->x = 0; break; case "\n": $this->_newLine(); break; case "\x08": if ($this->x) { $this->x--; $this->attrs[$this->y][$this->x] = clone $this->base_attr_cell; $this->screen[$this->y] = substr_replace( $this->screen[$this->y], $source[$i], $this->x, 1 ); } break; case "\x0F": break; case "\x1B": $this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1); $this->ansi.= "\x1B"; break; default: $this->attrs[$this->y][$this->x] = clone $this->attr_cell; if ($this->x > strlen($this->screen[$this->y])) { $this->screen[$this->y] = str_repeat(' ', $this->x); } $this->screen[$this->y] = substr_replace( $this->screen[$this->y], $source[$i], $this->x, 1 ); if ($this->x > $this->max_x) { $this->x = 0; $this->_newLine(); } else { $this->x++; } } } } function _newLine() { while ($this->y >= $this->max_y) { $this->history = array_merge($this->history, array(array_shift($this->screen))); $this->screen[] = ''; $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs))); $this->attrs[] = $this->attr_row; if (count($this->history) >= $this->max_history) { array_shift($this->history); array_shift($this->history_attrs); } $this->y--; } $this->y++; } function _processCoordinate($last_attr, $cur_attr, $char) { $output = ''; if ($last_attr != $cur_attr) { $close = $open = ''; if ($last_attr->foreground != $cur_attr->foreground) { if ($cur_attr->foreground != 'white') { $open.= ''; } if ($last_attr->foreground != 'white') { $close = '' . $close; } } if ($last_attr->background != $cur_attr->background) { if ($cur_attr->background != 'black') { $open.= ''; } if ($last_attr->background != 'black') { $close = '' . $close; } } if ($last_attr->bold != $cur_attr->bold) { if ($cur_attr->bold) { $open.= ''; } else { $close = '' . $close; } } if ($last_attr->underline != $cur_attr->underline) { if ($cur_attr->underline) { $open.= ''; } else { $close = '' . $close; } } if ($last_attr->blink != $cur_attr->blink) { if ($cur_attr->blink) { $open.= ''; } else { $close = '' . $close; } } $output.= $close . $open; } $output.= htmlspecialchars($char); return $output; } function _getScreen() { $output = ''; $last_attr = $this->base_attr_cell; for ($i = 0; $i <= $this->max_y; $i++) { for ($j = 0; $j <= $this->max_x; $j++) { $cur_attr = $this->attrs[$i][$j]; $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : ''); $last_attr = $this->attrs[$i][$j]; } $output.= "\r\n"; } $output = substr($output, 0, -2); $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, ''); return rtrim($output); } function getScreen() { return '
' . $this->_getScreen() . '
'; } function getHistory() { $scrollback = ''; $last_attr = $this->base_attr_cell; for ($i = 0; $i < count($this->history); $i++) { for ($j = 0; $j <= $this->max_x + 1; $j++) { $cur_attr = $this->history_attrs[$i][$j]; $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : ''); $last_attr = $this->history_attrs[$i][$j]; } $scrollback.= "\r\n"; } $base_attr_cell = $this->base_attr_cell; $this->base_attr_cell = $last_attr; $scrollback.= $this->_getScreen(); $this->base_attr_cell = $base_attr_cell; return '
' . $scrollback . '
'; } } element = $encoded; } } true, self::TYPE_INTEGER => true, self::TYPE_BIT_STRING => 'bitString', self::TYPE_OCTET_STRING => 'octetString', self::TYPE_NULL => 'null', self::TYPE_OBJECT_IDENTIFIER => 'objectIdentifier', self::TYPE_REAL => true, self::TYPE_ENUMERATED => 'enumerated', self::TYPE_UTF8_STRING => 'utf8String', self::TYPE_NUMERIC_STRING => 'numericString', self::TYPE_PRINTABLE_STRING => 'printableString', self::TYPE_TELETEX_STRING => 'teletexString', self::TYPE_VIDEOTEX_STRING => 'videotexString', self::TYPE_IA5_STRING => 'ia5String', self::TYPE_UTC_TIME => 'utcTime', self::TYPE_GENERALIZED_TIME => 'generalTime', self::TYPE_GRAPHIC_STRING => 'graphicString', self::TYPE_VISIBLE_STRING => 'visibleString', self::TYPE_GENERAL_STRING => 'generalString', self::TYPE_UNIVERSAL_STRING => 'universalString', self::TYPE_BMP_STRING => 'bmpString' ); var $stringTypeSize = array( self::TYPE_UTF8_STRING => 0, self::TYPE_BMP_STRING => 2, self::TYPE_UNIVERSAL_STRING => 4, self::TYPE_PRINTABLE_STRING => 1, self::TYPE_TELETEX_STRING => 1, self::TYPE_IA5_STRING => 1, self::TYPE_VISIBLE_STRING => 1, ); function decodeBER($encoded) { if ($encoded instanceof Element) { $encoded = $encoded->element; } $this->encoded = $encoded; return array($this->_decode_ber($encoded)); } function _decode_ber($encoded, $start = 0, $encoded_pos = 0) { $current = array('start' => $start); if (!isset($encoded[$encoded_pos])) { return false; } $type = ord($encoded[$encoded_pos++]); $startOffset = 1; $constructed = ($type >> 5) & 1; $tag = $type & 0x1F; if ($tag == 0x1F) { $tag = 0; do { if (!isset($encoded[$encoded_pos])) { return false; } $temp = ord($encoded[$encoded_pos++]); $startOffset++; $loop = $temp >> 7; $tag <<= 7; $temp &= 0x7F; if ($startOffset == 2 && $temp == 0) { return false; } $tag |= $temp; } while ($loop); } $start+= $startOffset; if (!isset($encoded[$encoded_pos])) { return false; } $length = ord($encoded[$encoded_pos++]); $start++; if ($length == 0x80) { $length = strlen($encoded) - $encoded_pos; } elseif ($length & 0x80) { $length&= 0x7F; $temp = substr($encoded, $encoded_pos, $length); $encoded_pos += $length; $current+= array('headerlength' => $length + 2); $start+= $length; extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))); } else { $current+= array('headerlength' => 2); } if ($length > (strlen($encoded) - $encoded_pos)) { return false; } $content = substr($encoded, $encoded_pos, $length); $content_pos = 0; $class = ($type >> 6) & 3; switch ($class) { case self::CLASS_APPLICATION: case self::CLASS_PRIVATE: case self::CLASS_CONTEXT_SPECIFIC: if (!$constructed) { return array( 'type' => $class, 'constant' => $tag, 'content' => $content, 'length' => $length + $start - $current['start'] ); } $newcontent = array(); $remainingLength = $length; while ($remainingLength > 0) { $temp = $this->_decode_ber($content, $start, $content_pos); if ($temp === false) { break; } $length = $temp['length']; if (substr($content, $content_pos + $length, 2) == "\0\0") { $length+= 2; $start+= $length; $newcontent[] = $temp; break; } $start+= $length; $remainingLength-= $length; $newcontent[] = $temp; $content_pos += $length; } return array( 'type' => $class, 'constant' => $tag, 'content' => $newcontent, 'length' => $start - $current['start'] ) + $current; } $current+= array('type' => $tag); switch ($tag) { case self::TYPE_BOOLEAN: if ($constructed || strlen($content) != 1) { return false; } $current['content'] = (bool) ord($content[$content_pos]); break; case self::TYPE_INTEGER: case self::TYPE_ENUMERATED: if ($constructed) { return false; } $current['content'] = new BigInteger(substr($content, $content_pos), -256); break; case self::TYPE_REAL: return false; case self::TYPE_BIT_STRING: if (!$constructed) { $current['content'] = substr($content, $content_pos); } else { $temp = $this->_decode_ber($content, $start, $content_pos); if ($temp === false) { return false; } $length-= (strlen($content) - $content_pos); $last = count($temp) - 1; for ($i = 0; $i < $last; $i++) { if ($temp[$i]['type'] != self::TYPE_BIT_STRING) { return false; } $current['content'].= substr($temp[$i]['content'], 1); } if ($temp[$last]['type'] != self::TYPE_BIT_STRING) { return false; } $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1); } break; case self::TYPE_OCTET_STRING: if (!$constructed) { $current['content'] = substr($content, $content_pos); } else { $current['content'] = ''; $length = 0; while (substr($content, $content_pos, 2) != "\0\0") { $temp = $this->_decode_ber($content, $length + $start, $content_pos); if ($temp === false) { return false; } $content_pos += $temp['length']; if ($temp['type'] != self::TYPE_OCTET_STRING) { return false; } $current['content'].= $temp['content']; $length+= $temp['length']; } if (substr($content, $content_pos, 2) == "\0\0") { $length+= 2; } } break; case self::TYPE_NULL: if ($constructed || strlen($content)) { return false; } break; case self::TYPE_SEQUENCE: case self::TYPE_SET: if (!$constructed) { return false; } $offset = 0; $current['content'] = array(); $content_len = strlen($content); while ($content_pos < $content_len) { if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") { $length = $offset + 2; break 2; } $temp = $this->_decode_ber($content, $start + $offset, $content_pos); if ($temp === false) { return false; } $content_pos += $temp['length']; $current['content'][] = $temp; $offset+= $temp['length']; } break; case self::TYPE_OBJECT_IDENTIFIER: if ($constructed) { return false; } $current['content'] = $this->_decodeOID(substr($content, $content_pos)); if ($current['content'] === false) { return false; } break; case self::TYPE_NUMERIC_STRING: case self::TYPE_PRINTABLE_STRING: case self::TYPE_TELETEX_STRING: case self::TYPE_VIDEOTEX_STRING: case self::TYPE_VISIBLE_STRING: case self::TYPE_IA5_STRING: case self::TYPE_GRAPHIC_STRING: case self::TYPE_GENERAL_STRING: case self::TYPE_UTF8_STRING: case self::TYPE_BMP_STRING: if ($constructed) { return false; } $current['content'] = substr($content, $content_pos); break; case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: if ($constructed) { return false; } $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag); break; default: return false; } $start+= $length; return $current + array('length' => $start - $current['start']); } function asn1map($decoded, $mapping, $special = array()) { if (!is_array($decoded)) { return false; } if (isset($mapping['explicit']) && is_array($decoded['content'])) { $decoded = $decoded['content'][0]; } switch (true) { case $mapping['type'] == self::TYPE_ANY: $intype = $decoded['type']; if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) { return new Element(substr($this->encoded, $decoded['start'], $decoded['length'])); } $inmap = $this->ANYmap[$intype]; if (is_string($inmap)) { return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special)); } break; case $mapping['type'] == self::TYPE_CHOICE: foreach ($mapping['children'] as $key => $option) { switch (true) { case isset($option['constant']) && $option['constant'] == $decoded['constant']: case !isset($option['constant']) && $option['type'] == $decoded['type']: $value = $this->asn1map($decoded, $option, $special); break; case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE: $v = $this->asn1map($decoded, $option, $special); if (isset($v)) { $value = $v; } } if (isset($value)) { if (isset($special[$key])) { $value = call_user_func($special[$key], $value); } return array($key => $value); } } return null; case isset($mapping['implicit']): case isset($mapping['explicit']): case $decoded['type'] == $mapping['type']: break; default: switch (true) { case $decoded['type'] < 18: case $decoded['type'] > 30: case $mapping['type'] < 18: case $mapping['type'] > 30: return null; } } if (isset($mapping['implicit'])) { $decoded['type'] = $mapping['type']; } switch ($decoded['type']) { case self::TYPE_SEQUENCE: $map = array(); if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { if (($map[] = $this->asn1map($content, $child, $special)) === null) { return null; } } return $map; } $n = count($decoded['content']); $i = 0; foreach ($mapping['children'] as $key => $child) { $maymatch = $i < $n; if ($maymatch) { $temp = $decoded['content'][$i]; if ($child['type'] != self::TYPE_CHOICE) { $childClass = $tempClass = self::CLASS_UNIVERSAL; $constant = null; if (isset($temp['constant'])) { $tempClass = $temp['type']; } if (isset($child['class'])) { $childClass = $child['class']; $constant = $child['cast']; } elseif (isset($child['constant'])) { $childClass = self::CLASS_CONTEXT_SPECIFIC; $constant = $child['constant']; } if (isset($constant) && isset($temp['constant'])) { $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; } } } if ($maymatch) { $candidate = $this->asn1map($temp, $child, $special); $maymatch = $candidate !== null; } if ($maymatch) { if (isset($special[$key])) { $candidate = call_user_func($special[$key], $candidate); } $map[$key] = $candidate; $i++; } elseif (isset($child['default'])) { $map[$key] = $child['default']; } elseif (!isset($child['optional'])) { return null; } } return $i < $n ? null: $map; case self::TYPE_SET: $map = array(); if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { if (($map[] = $this->asn1map($content, $child, $special)) === null) { return null; } } return $map; } for ($i = 0; $i < count($decoded['content']); $i++) { $temp = $decoded['content'][$i]; $tempClass = self::CLASS_UNIVERSAL; if (isset($temp['constant'])) { $tempClass = $temp['type']; } foreach ($mapping['children'] as $key => $child) { if (isset($map[$key])) { continue; } $maymatch = true; if ($child['type'] != self::TYPE_CHOICE) { $childClass = self::CLASS_UNIVERSAL; $constant = null; if (isset($child['class'])) { $childClass = $child['class']; $constant = $child['cast']; } elseif (isset($child['constant'])) { $childClass = self::CLASS_CONTEXT_SPECIFIC; $constant = $child['constant']; } if (isset($constant) && isset($temp['constant'])) { $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; } } if ($maymatch) { $candidate = $this->asn1map($temp, $child, $special); $maymatch = $candidate !== null; } if (!$maymatch) { break; } if (isset($special[$key])) { $candidate = call_user_func($special[$key], $candidate); } $map[$key] = $candidate; break; } } foreach ($mapping['children'] as $key => $child) { if (!isset($map[$key])) { if (isset($child['default'])) { $map[$key] = $child['default']; } elseif (!isset($child['optional'])) { return null; } } } return $map; case self::TYPE_OBJECT_IDENTIFIER: return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content']; case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: if (is_array($decoded['content'])) { $decoded['content'] = $decoded['content'][0]['content']; } if (!is_object($decoded['content'])) { $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); } return $decoded['content'] ? $decoded['content']->format($this->format) : false; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $offset = ord($decoded['content'][0]); $size = (strlen($decoded['content']) - 1) * 8 - $offset; $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false); for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) { $current = ord($decoded['content'][$i]); for ($j = $offset; $j < 8; $j++) { $bits[] = (bool) ($current & (1 << $j)); } $offset = 0; } $values = array(); $map = array_reverse($mapping['mapping']); foreach ($map as $i => $value) { if ($bits[$i]) { $values[] = $value; } } return $values; } case self::TYPE_OCTET_STRING: return base64_encode($decoded['content']); case self::TYPE_NULL: return ''; case self::TYPE_BOOLEAN: return $decoded['content']; case self::TYPE_NUMERIC_STRING: case self::TYPE_PRINTABLE_STRING: case self::TYPE_TELETEX_STRING: case self::TYPE_VIDEOTEX_STRING: case self::TYPE_IA5_STRING: case self::TYPE_GRAPHIC_STRING: case self::TYPE_VISIBLE_STRING: case self::TYPE_GENERAL_STRING: case self::TYPE_UNIVERSAL_STRING: case self::TYPE_UTF8_STRING: case self::TYPE_BMP_STRING: return $decoded['content']; case self::TYPE_INTEGER: case self::TYPE_ENUMERATED: $temp = $decoded['content']; if (isset($mapping['implicit'])) { $temp = new BigInteger($decoded['content'], -256); } if (isset($mapping['mapping'])) { $temp = (int) $temp->toString(); return isset($mapping['mapping'][$temp]) ? $mapping['mapping'][$temp] : false; } return $temp; } } function encodeDER($source, $mapping, $special = array()) { $this->location = array(); return $this->_encode_der($source, $mapping, null, $special); } function _encode_der($source, $mapping, $idx = null, $special = array()) { if ($source instanceof Element) { return $source->element; } if (isset($mapping['default']) && $source === $mapping['default']) { return ''; } if (isset($idx)) { if (isset($special[$idx])) { $source = call_user_func($special[$idx], $source); } $this->location[] = $idx; } $tag = $mapping['type']; switch ($tag) { case self::TYPE_SET: case self::TYPE_SEQUENCE: $tag|= 0x20; if (isset($mapping['min']) && isset($mapping['max'])) { $value = array(); $child = $mapping['children']; foreach ($source as $content) { $temp = $this->_encode_der($content, $child, null, $special); if ($temp === false) { return false; } $value[]= $temp; } if ($mapping['type'] == self::TYPE_SET) { sort($value); } $value = implode('', $value); break; } $value = ''; foreach ($mapping['children'] as $key => $child) { if (!array_key_exists($key, $source)) { if (!isset($child['optional'])) { return false; } continue; } $temp = $this->_encode_der($source[$key], $child, $key, $special); if ($temp === false) { return false; } if ($temp === '') { continue; } if (isset($child['constant'])) { if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; } else { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); $temp = $subtag . substr($temp, 1); } } $value.= $temp; } break; case self::TYPE_CHOICE: $temp = false; foreach ($mapping['children'] as $key => $child) { if (!isset($source[$key])) { continue; } $temp = $this->_encode_der($source[$key], $child, $key, $special); if ($temp === false) { return false; } if ($temp === '') { continue; } $tag = ord($temp[0]); if (isset($child['constant'])) { if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; } else { $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); $temp = $subtag . substr($temp, 1); } } } if (isset($idx)) { array_pop($this->location); } if ($temp && isset($mapping['cast'])) { $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']); } return $temp; case self::TYPE_INTEGER: case self::TYPE_ENUMERATED: if (!isset($mapping['mapping'])) { if (is_numeric($source)) { $source = new BigInteger($source); } $value = $source->toBytes(true); } else { $value = array_search($source, $mapping['mapping']); if ($value === false) { return false; } $value = new BigInteger($value); $value = $value->toBytes(true); } if (!strlen($value)) { $value = chr(0); } break; case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y'; $format.= 'mdHis'; $date = new DateTime($source, new DateTimeZone('GMT')); $date->setTimezone(new DateTimeZone('GMT')); $value = $date->format($format) . 'Z'; break; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $bits = array_fill(0, count($mapping['mapping']), 0); $size = 0; for ($i = 0; $i < count($mapping['mapping']); $i++) { if (in_array($mapping['mapping'][$i], $source)) { $bits[$i] = 1; $size = $i; } } if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) { $size = $mapping['min'] - 1; } $offset = 8 - (($size + 1) & 7); $offset = $offset !== 8 ? $offset : 0; $value = chr($offset); for ($i = $size + 1; $i < count($mapping['mapping']); $i++) { unset($bits[$i]); } $bits = implode('', array_pad($bits, $size + $offset + 1, 0)); $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' '))); foreach ($bytes as $byte) { $value.= chr(bindec($byte)); } break; } case self::TYPE_OCTET_STRING: $value = base64_decode($source); break; case self::TYPE_OBJECT_IDENTIFIER: $value = $this->_encodeOID($source); break; case self::TYPE_ANY: $loc = $this->location; if (isset($idx)) { array_pop($this->location); } switch (true) { case !isset($source): return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special); case is_int($source): case $source instanceof BigInteger: return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special); case is_float($source): return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special); case is_bool($source): return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special); case is_array($source) && count($source) == 1: $typename = implode('', array_keys($source)); $outtype = array_search($typename, $this->ANYmap, true); if ($outtype !== false) { return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special); } } $filters = $this->filters; foreach ($loc as $part) { if (!isset($filters[$part])) { $filters = false; break; } $filters = $filters[$part]; } if ($filters === false) { user_error('No filters defined for ' . implode('/', $loc)); return false; } return $this->_encode_der($source, $filters + $mapping, null, $special); case self::TYPE_NULL: $value = ''; break; case self::TYPE_NUMERIC_STRING: case self::TYPE_TELETEX_STRING: case self::TYPE_PRINTABLE_STRING: case self::TYPE_UNIVERSAL_STRING: case self::TYPE_UTF8_STRING: case self::TYPE_BMP_STRING: case self::TYPE_IA5_STRING: case self::TYPE_VISIBLE_STRING: case self::TYPE_VIDEOTEX_STRING: case self::TYPE_GRAPHIC_STRING: case self::TYPE_GENERAL_STRING: $value = $source; break; case self::TYPE_BOOLEAN: $value = $source ? "\xFF" : "\x00"; break; default: user_error('Mapping provides no type definition for ' . implode('/', $this->location)); return false; } if (isset($idx)) { array_pop($this->location); } if (isset($mapping['cast'])) { if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) { $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value; $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast']; } else { $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast']; } } return chr($tag) . $this->_encodeLength(strlen($value)) . $value; } function _encodeLength($length) { if ($length <= 0x7F) { return chr($length); } $temp = ltrim(pack('N', $length), chr(0)); return pack('Ca*', 0x80 | strlen($temp), $temp); } function _decodeOID($content) { static $eighty; if (!$eighty) { $eighty = new BigInteger(80); } $oid = array(); $pos = 0; $len = strlen($content); if (ord($content[$len - 1]) & 0x80) { return false; } $n = new BigInteger(); while ($pos < $len) { $temp = ord($content[$pos++]); $n = $n->bitwise_leftShift(7); $n = $n->bitwise_or(new BigInteger($temp & 0x7F)); if (~$temp & 0x80) { $oid[] = $n; $n = new BigInteger(); } } $part1 = array_shift($oid); $first = floor(ord($content[0]) / 40); if ($first <= 2) { array_unshift($oid, ord($content[0]) % 40); array_unshift($oid, $first); } else { array_unshift($oid, $part1->subtract($eighty)); array_unshift($oid, 2); } return implode('.', $oid); } function _encodeOID($source) { static $mask, $zero, $forty; if (!$mask) { $mask = new BigInteger(0x7F); $zero = new BigInteger(); $forty = new BigInteger(40); } $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); if ($oid === false) { user_error('Invalid OID'); return false; } $parts = explode('.', $oid); $part1 = array_shift($parts); $part2 = array_shift($parts); $first = new BigInteger($part1); $first = $first->multiply($forty); $first = $first->add(new BigInteger($part2)); array_unshift($parts, $first->toString()); $value = ''; foreach ($parts as $part) { if (!$part) { $temp = "\0"; } else { $temp = ''; $part = new BigInteger($part); while (!$part->equals($zero)) { $submask = $part->bitwise_and($mask); $submask->setPrecision(8); $temp = (chr(0x80) | $submask->toBytes()) . $temp; $part = $part->bitwise_rightShift(7); } $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); } $value.= $temp; } return $value; } function _decodeTime($content, $tag) { $format = 'YmdHis'; if ($tag == self::TYPE_UTC_TIME) { if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) { $content = $matches[1] . '00' . $matches[2]; } $prefix = substr($content, 0, 2) >= 50 ? '19' : '20'; $content = $prefix . $content; } elseif (strpos($content, '.') !== false) { $format.= '.u'; } if ($content[strlen($content) - 1] == 'Z') { $content = substr($content, 0, -1) . '+0000'; } if (strpos($content, '-') !== false || strpos($content, '+') !== false) { $format.= 'O'; } return @DateTime::createFromFormat($format, $content); } function setTimeFormat($format) { $this->format = $format; } function loadOIDs($oids) { $this->oids = $oids; } function loadFilters($filters) { $this->filters = $filters; } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING) { if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) { return false; } $insize = $this->stringTypeSize[$from]; $outsize = $this->stringTypeSize[$to]; $inlength = strlen($in); $out = ''; for ($i = 0; $i < $inlength;) { if ($inlength - $i < $insize) { return false; } $c = ord($in[$i++]); switch (true) { case $insize == 4: $c = ($c << 8) | ord($in[$i++]); $c = ($c << 8) | ord($in[$i++]); case $insize == 2: $c = ($c << 8) | ord($in[$i++]); case $insize == 1: break; case ($c & 0x80) == 0x00: break; case ($c & 0x40) == 0x00: return false; default: $bit = 6; do { if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) { return false; } $c = ($c << 6) | (ord($in[$i++]) & 0x3F); $bit += 5; $mask = 1 << $bit; } while ($c & $bit); $c &= $mask - 1; break; } $v = ''; switch (true) { case $outsize == 4: $v .= chr($c & 0xFF); $c >>= 8; $v .= chr($c & 0xFF); $c >>= 8; case $outsize == 2: $v .= chr($c & 0xFF); $c >>= 8; case $outsize == 1: $v .= chr($c & 0xFF); $c >>= 8; if ($c) { return false; } break; case ($c & 0x80000000) != 0: return false; case $c >= 0x04000000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x04000000; case $c >= 0x00200000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00200000; case $c >= 0x00010000: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00010000; case $c >= 0x00000800: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x00000800; case $c >= 0x00000080: $v .= chr(0x80 | ($c & 0x3F)); $c = ($c >> 6) | 0x000000C0; default: $v .= chr($c); break; } $out .= strrev($v); } return $out; } } fsock = fsockopen('unix://' . $address, 0, $errno, $errstr); if (!$this->fsock) { user_error("Unable to connect to ssh-agent (Error $errno: $errstr)"); } } function requestIdentities() { if (!$this->fsock) { return array(); } $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES); if (strlen($packet) != fputs($this->fsock, $packet)) { user_error('Connection closed while requesting identities'); return array(); } $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed while requesting identities'); return array(); } $length = current(unpack('N', $temp)); $type = ord(fread($this->fsock, 1)); if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) { user_error('Unable to request identities'); return array(); } $identities = array(); $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed while requesting identities'); return array(); } $keyCount = current(unpack('N', $temp)); for ($i = 0; $i < $keyCount; $i++) { $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed while requesting identities'); return array(); } $length = current(unpack('N', $temp)); $key_blob = fread($this->fsock, $length); if (strlen($key_blob) != $length) { user_error('Connection closed while requesting identities'); return array(); } $key_str = 'ssh-rsa ' . base64_encode($key_blob); $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed while requesting identities'); return array(); } $length = current(unpack('N', $temp)); if ($length) { $temp = fread($this->fsock, $length); if (strlen($temp) != $length) { user_error('Connection closed while requesting identities'); return array(); } $key_str.= ' ' . $temp; } $length = current(unpack('N', substr($key_blob, 0, 4))); $key_type = substr($key_blob, 4, $length); switch ($key_type) { case 'ssh-rsa': $key = new RSA(); $key->loadKey($key_str); break; case 'ssh-dss': break; } if (isset($key)) { $identity = new Identity($this->fsock); $identity->setPublicKey($key); $identity->setPublicKeyBlob($key_blob); $identities[] = $identity; unset($key); } } return $identities; } function startSSHForwarding() { if ($this->forward_status == self::FORWARD_NONE) { $this->forward_status = self::FORWARD_REQUEST; } } function _request_forwarding($ssh) { $request_channel = $ssh->_get_open_channel(); if ($request_channel === false) { return false; } $packet = pack( 'CNNa*C', NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[$request_channel], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1 ); $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST; if (!$ssh->_send_binary_packet($packet)) { return false; } $response = $ssh->_get_channel_packet($request_channel); if ($response === false) { return false; } $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN; $this->forward_status = self::FORWARD_ACTIVE; return true; } function _on_channel_open($ssh) { if ($this->forward_status == self::FORWARD_REQUEST) { $this->_request_forwarding($ssh); } } function _forward_data($data) { if ($this->expected_bytes > 0) { $this->socket_buffer.= $data; $this->expected_bytes -= strlen($data); } else { $agent_data_bytes = current(unpack('N', $data)); $current_data_bytes = strlen($data); $this->socket_buffer = $data; if ($current_data_bytes != $agent_data_bytes + 4) { $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes; return false; } } if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) { user_error('Connection closed attempting to forward data to SSH agent'); return false; } $this->socket_buffer = ''; $this->expected_bytes = 0; $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed while reading data response'); return false; } $agent_reply_bytes = current(unpack('N', $temp)); $agent_reply_data = fread($this->fsock, $agent_reply_bytes); if (strlen($agent_reply_data) != $agent_reply_bytes) { user_error('Connection closed while reading data response'); return false; } $agent_reply_data = current(unpack('a*', $agent_reply_data)); return pack('Na*', $agent_reply_bytes, $agent_reply_data); } } fsock = $fsock; } function setPublicKey($key) { $this->key = $key; $this->key->setPublicKey(); } function setPublicKeyBlob($key_blob) { $this->key_blob = $key_blob; } function getPublicKey($format = null) { return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format); } function setSignatureMode($mode) { } function setHash($hash) { $this->flags = 0; switch ($hash) { case 'sha1': break; case 'sha256': $this->flags = self::SSH_AGENT_RSA2_256; break; case 'sha512': $this->flags = self::SSH_AGENT_RSA2_512; break; default: user_error('The only supported hashes for RSA are sha1, sha256 and sha512'); } } function sign($message) { $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, $this->flags); $packet = pack('Na*', strlen($packet), $packet); if (strlen($packet) != fputs($this->fsock, $packet)) { user_error('Connection closed during signing'); return false; } $temp = fread($this->fsock, 4); if (strlen($temp) != 4) { user_error('Connection closed during signing'); return false; } $length = current(unpack('N', $temp)); $type = ord(fread($this->fsock, 1)); if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) { user_error('Unable to retrieve signature'); return false; } $signature_blob = fread($this->fsock, $length - 1); if (strlen($signature_blob) != $length - 1) { user_error('Connection closed during signing'); return false; } $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); if ($length != strlen($signature_blob)) { user_error('Malformed signature blob'); } $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); if ($length > strlen($signature_blob) + 4) { user_error('Malformed signature blob'); } $type = $this->_string_shift($signature_blob, $length); $this->_string_shift($signature_blob, 4); return $signature_blob; } function _string_shift(&$string, $index = 1) { $substr = substr($string, 0, $index); $string = substr($string, $index); return $substr; } } 0)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a positive integer. Got: %s', static::valueToString($value) )); } } public static function float($value, $message = '') { if (!\is_float($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a float. Got: %s', static::typeToString($value) )); } } public static function numeric($value, $message = '') { if (!\is_numeric($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a numeric. Got: %s', static::typeToString($value) )); } } public static function natural($value, $message = '') { if (!\is_int($value) || $value < 0) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a non-negative integer. Got: %s', static::valueToString($value) )); } } public static function boolean($value, $message = '') { if (!\is_bool($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a boolean. Got: %s', static::typeToString($value) )); } } public static function scalar($value, $message = '') { if (!\is_scalar($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a scalar. Got: %s', static::typeToString($value) )); } } public static function object($value, $message = '') { if (!\is_object($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an object. Got: %s', static::typeToString($value) )); } } public static function resource($value, $type = null, $message = '') { if (!\is_resource($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a resource. Got: %s', static::typeToString($value) )); } if ($type && $type !== \get_resource_type($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a resource of type %2$s. Got: %s', static::typeToString($value), $type )); } } public static function isCallable($value, $message = '') { if (!\is_callable($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a callable. Got: %s', static::typeToString($value) )); } } public static function isArray($value, $message = '') { if (!\is_array($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array. Got: %s', static::typeToString($value) )); } } public static function isTraversable($value, $message = '') { @\trigger_error( \sprintf( 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', __METHOD__ ), \E_USER_DEPRECATED ); if (!\is_array($value) && !($value instanceof Traversable)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a traversable. Got: %s', static::typeToString($value) )); } } public static function isArrayAccessible($value, $message = '') { if (!\is_array($value) && !($value instanceof ArrayAccess)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array accessible. Got: %s', static::typeToString($value) )); } } public static function isCountable($value, $message = '') { if ( !\is_array($value) && !($value instanceof Countable) && !($value instanceof ResourceBundle) && !($value instanceof SimpleXMLElement) ) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a countable. Got: %s', static::typeToString($value) )); } } public static function isIterable($value, $message = '') { if (!\is_array($value) && !($value instanceof Traversable)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an iterable. Got: %s', static::typeToString($value) )); } } public static function isInstanceOf($value, $class, $message = '') { if (!($value instanceof $class)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an instance of %2$s. Got: %s', static::typeToString($value), $class )); } } public static function notInstanceOf($value, $class, $message = '') { if ($value instanceof $class) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an instance other than %2$s. Got: %s', static::typeToString($value), $class )); } } public static function isInstanceOfAny($value, array $classes, $message = '') { foreach ($classes as $class) { if ($value instanceof $class) { return; } } static::reportInvalidArgument(\sprintf( $message ?: 'Expected an instance of any of %2$s. Got: %s', static::typeToString($value), \implode(', ', \array_map(array(static::class, 'valueToString'), $classes)) )); } public static function isAOf($value, $class, $message = '') { static::string($class, 'Expected class as a string. Got: %s'); if (!\is_a($value, $class, \is_string($value))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s', static::valueToString($value), $class )); } } public static function isNotA($value, $class, $message = '') { static::string($class, 'Expected class as a string. Got: %s'); if (\is_a($value, $class, \is_string($value))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s', static::valueToString($value), $class )); } } public static function isAnyOf($value, array $classes, $message = '') { foreach ($classes as $class) { static::string($class, 'Expected class as a string. Got: %s'); if (\is_a($value, $class, \is_string($value))) { return; } } static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s', static::valueToString($value), \implode(', ', $classes) )); } public static function isEmpty($value, $message = '') { if (!empty($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an empty value. Got: %s', static::valueToString($value) )); } } public static function notEmpty($value, $message = '') { if (empty($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a non-empty value. Got: %s', static::valueToString($value) )); } } public static function null($value, $message = '') { if (null !== $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected null. Got: %s', static::valueToString($value) )); } } public static function notNull($value, $message = '') { if (null === $value) { static::reportInvalidArgument( $message ?: 'Expected a value other than null.' ); } } public static function true($value, $message = '') { if (true !== $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be true. Got: %s', static::valueToString($value) )); } } public static function false($value, $message = '') { if (false !== $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be false. Got: %s', static::valueToString($value) )); } } public static function notFalse($value, $message = '') { if (false === $value) { static::reportInvalidArgument( $message ?: 'Expected a value other than false.' ); } } public static function ip($value, $message = '') { if (false === \filter_var($value, \FILTER_VALIDATE_IP)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be an IP. Got: %s', static::valueToString($value) )); } } public static function ipv4($value, $message = '') { if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be an IPv4. Got: %s', static::valueToString($value) )); } } public static function ipv6($value, $message = '') { if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be an IPv6. Got: %s', static::valueToString($value) )); } } public static function email($value, $message = '') { if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to be a valid e-mail address. Got: %s', static::valueToString($value) )); } } public static function uniqueValues(array $values, $message = '') { $allValues = \count($values); $uniqueValues = \count(\array_unique($values)); if ($allValues !== $uniqueValues) { $difference = $allValues - $uniqueValues; static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array of unique values, but %s of them %s duplicated', $difference, (1 === $difference ? 'is' : 'are') )); } } public static function eq($value, $expect, $message = '') { if ($expect != $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect) )); } } public static function notEq($value, $expect, $message = '') { if ($expect == $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a different value than %s.', static::valueToString($expect) )); } } public static function same($value, $expect, $message = '') { if ($expect !== $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value identical to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect) )); } } public static function notSame($value, $expect, $message = '') { if ($expect === $value) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value not identical to %s.', static::valueToString($expect) )); } } public static function greaterThan($value, $limit, $message = '') { if ($value <= $limit) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value greater than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function greaterThanEq($value, $limit, $message = '') { if ($value < $limit) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function lessThan($value, $limit, $message = '') { if ($value >= $limit) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value less than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function lessThanEq($value, $limit, $message = '') { if ($value > $limit) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value less than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function range($value, $min, $max, $message = '') { if ($value < $min || $value > $max) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value between %2$s and %3$s. Got: %s', static::valueToString($value), static::valueToString($min), static::valueToString($max) )); } } public static function oneOf($value, array $values, $message = '') { static::inArray($value, $values, $message); } public static function inArray($value, array $values, $message = '') { if (!\in_array($value, $values, true)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected one of: %2$s. Got: %s', static::valueToString($value), \implode(', ', \array_map(array(static::class, 'valueToString'), $values)) )); } } public static function contains($value, $subString, $message = '') { if (false === \strpos($value, $subString)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain %2$s. Got: %s', static::valueToString($value), static::valueToString($subString) )); } } public static function notContains($value, $subString, $message = '') { if (false !== \strpos($value, $subString)) { static::reportInvalidArgument(\sprintf( $message ?: '%2$s was not expected to be contained in a value. Got: %s', static::valueToString($value), static::valueToString($subString) )); } } public static function notWhitespaceOnly($value, $message = '') { if (\preg_match('/^\s*$/', $value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a non-whitespace string. Got: %s', static::valueToString($value) )); } } public static function startsWith($value, $prefix, $message = '') { if (0 !== \strpos($value, $prefix)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix) )); } } public static function notStartsWith($value, $prefix, $message = '') { if (0 === \strpos($value, $prefix)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value not to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix) )); } } public static function startsWithLetter($value, $message = '') { static::string($value); $valid = isset($value[0]); if ($valid) { $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = \ctype_alpha($value[0]); \setlocale(LC_CTYPE, $locale); } if (!$valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to start with a letter. Got: %s', static::valueToString($value) )); } } public static function endsWith($value, $suffix, $message = '') { if ($suffix !== \substr($value, -\strlen($suffix))) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix) )); } } public static function notEndsWith($value, $suffix, $message = '') { if ($suffix === \substr($value, -\strlen($suffix))) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value not to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix) )); } } public static function regex($value, $pattern, $message = '') { if (!\preg_match($pattern, $value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The value %s does not match the expected pattern.', static::valueToString($value) )); } } public static function notRegex($value, $pattern, $message = '') { if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { static::reportInvalidArgument(\sprintf( $message ?: 'The value %s matches the pattern %s (at offset %d).', static::valueToString($value), static::valueToString($pattern), $matches[0][1] )); } } public static function unicodeLetters($value, $message = '') { static::string($value); if (!\preg_match('/^\p{L}+$/u', $value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain only Unicode letters. Got: %s', static::valueToString($value) )); } } public static function alpha($value, $message = '') { static::string($value); $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = !\ctype_alpha($value); \setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain only letters. Got: %s', static::valueToString($value) )); } } public static function digits($value, $message = '') { $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = !\ctype_digit($value); \setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain digits only. Got: %s', static::valueToString($value) )); } } public static function alnum($value, $message = '') { $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = !\ctype_alnum($value); \setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain letters and digits only. Got: %s', static::valueToString($value) )); } } public static function lower($value, $message = '') { $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = !\ctype_lower($value); \setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain lowercase characters only. Got: %s', static::valueToString($value) )); } } public static function upper($value, $message = '') { $locale = \setlocale(LC_CTYPE, 0); \setlocale(LC_CTYPE, 'C'); $valid = !\ctype_upper($value); \setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain uppercase characters only. Got: %s', static::valueToString($value) )); } } public static function length($value, $length, $message = '') { if ($length !== static::strlen($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain %2$s characters. Got: %s', static::valueToString($value), $length )); } } public static function minLength($value, $min, $message = '') { if (static::strlen($value) < $min) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', static::valueToString($value), $min )); } } public static function maxLength($value, $max, $message = '') { if (static::strlen($value) > $max) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', static::valueToString($value), $max )); } } public static function lengthBetween($value, $min, $max, $message = '') { $length = static::strlen($value); if ($length < $min || $length > $max) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', static::valueToString($value), $min, $max )); } } public static function fileExists($value, $message = '') { static::string($value); if (!\file_exists($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The file %s does not exist.', static::valueToString($value) )); } } public static function file($value, $message = '') { static::fileExists($value, $message); if (!\is_file($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The path %s is not a file.', static::valueToString($value) )); } } public static function directory($value, $message = '') { static::fileExists($value, $message); if (!\is_dir($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The path %s is no directory.', static::valueToString($value) )); } } public static function readable($value, $message = '') { if (!\is_readable($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The path %s is not readable.', static::valueToString($value) )); } } public static function writable($value, $message = '') { if (!\is_writable($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'The path %s is not writable.', static::valueToString($value) )); } } public static function classExists($value, $message = '') { if (!\class_exists($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an existing class name. Got: %s', static::valueToString($value) )); } } public static function subclassOf($value, $class, $message = '') { if (!\is_subclass_of($value, $class)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected a sub-class of %2$s. Got: %s', static::valueToString($value), static::valueToString($class) )); } } public static function interfaceExists($value, $message = '') { if (!\interface_exists($value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an existing interface name. got %s', static::valueToString($value) )); } } public static function implementsInterface($value, $interface, $message = '') { if (!\in_array($interface, \class_implements($value))) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an implementation of %2$s. Got: %s', static::valueToString($value), static::valueToString($interface) )); } } public static function propertyExists($classOrObject, $property, $message = '') { if (!\property_exists($classOrObject, $property)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the property %s to exist.', static::valueToString($property) )); } } public static function propertyNotExists($classOrObject, $property, $message = '') { if (\property_exists($classOrObject, $property)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the property %s to not exist.', static::valueToString($property) )); } } public static function methodExists($classOrObject, $method, $message = '') { if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the method %s to exist.', static::valueToString($method) )); } } public static function methodNotExists($classOrObject, $method, $message = '') { if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the method %s to not exist.', static::valueToString($method) )); } } public static function keyExists($array, $key, $message = '') { if (!(isset($array[$key]) || \array_key_exists($key, $array))) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the key %s to exist.', static::valueToString($key) )); } } public static function keyNotExists($array, $key, $message = '') { if (isset($array[$key]) || \array_key_exists($key, $array)) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected the key %s to not exist.', static::valueToString($key) )); } } public static function validArrayKey($value, $message = '') { if (!(\is_int($value) || \is_string($value))) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected string or integer. Got: %s', static::typeToString($value) )); } } public static function count($array, $number, $message = '') { static::eq( \count($array), $number, \sprintf( $message ?: 'Expected an array to contain %d elements. Got: %d.', $number, \count($array) ) ); } public static function minCount($array, $min, $message = '') { if (\count($array) < $min) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', \count($array), $min )); } } public static function maxCount($array, $max, $message = '') { if (\count($array) > $max) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', \count($array), $max )); } } public static function countBetween($array, $min, $max, $message = '') { $count = \count($array); if ($count < $min || $count > $max) { static::reportInvalidArgument(\sprintf( $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', $count, $min, $max )); } } public static function isList($array, $message = '') { if (!\is_array($array)) { static::reportInvalidArgument( $message ?: 'Expected list - non-associative array.' ); } if ($array === \array_values($array)) { return; } $nextKey = -1; foreach ($array as $k => $v) { if ($k !== ++$nextKey) { static::reportInvalidArgument( $message ?: 'Expected list - non-associative array.' ); } } } public static function isNonEmptyList($array, $message = '') { static::isList($array, $message); static::notEmpty($array, $message); } public static function isMap($array, $message = '') { if ( !\is_array($array) || \array_keys($array) !== \array_filter(\array_keys($array), '\is_string') ) { static::reportInvalidArgument( $message ?: 'Expected map - associative array with string keys.' ); } } public static function isNonEmptyMap($array, $message = '') { static::isMap($array, $message); static::notEmpty($array, $message); } public static function uuid($value, $message = '') { $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); if ('00000000-0000-0000-0000-000000000000' === $value) { return; } if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { static::reportInvalidArgument(\sprintf( $message ?: 'Value %s is not a valid UUID.', static::valueToString($value) )); } } public static function throws(Closure $expression, $class = 'Exception', $message = '') { static::string($class); $actual = 'none'; try { $expression(); } catch (Exception $e) { $actual = \get_class($e); if ($e instanceof $class) { return; } } catch (Throwable $e) { $actual = \get_class($e); if ($e instanceof $class) { return; } } static::reportInvalidArgument($message ?: \sprintf( 'Expected to throw "%s", got "%s"', $class, $actual )); } public static function __callStatic($name, $arguments) { if ('nullOr' === \substr($name, 0, 6)) { if (null !== $arguments[0]) { $method = \lcfirst(\substr($name, 6)); \call_user_func_array(array(static::class, $method), $arguments); } return; } if ('all' === \substr($name, 0, 3)) { static::isIterable($arguments[0]); $method = \lcfirst(\substr($name, 3)); $args = $arguments; foreach ($arguments[0] as $entry) { $args[0] = $entry; \call_user_func_array(array(static::class, $method), $args); } return; } throw new BadMethodCallException('No such method: '.$name); } protected static function valueToString($value) { if (null === $value) { return 'null'; } if (true === $value) { return 'true'; } if (false === $value) { return 'false'; } if (\is_array($value)) { return 'array'; } if (\is_object($value)) { if (\method_exists($value, '__toString')) { return \get_class($value).': '.self::valueToString($value->__toString()); } if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { return \get_class($value).': '.self::valueToString($value->format('c')); } return \get_class($value); } if (\is_resource($value)) { return 'resource'; } if (\is_string($value)) { return '"'.$value.'"'; } return (string) $value; } protected static function typeToString($value) { return \is_object($value) ? \get_class($value) : \gettype($value); } protected static function strlen($value) { if (!\function_exists('mb_detect_encoding')) { return \strlen($value); } if (false === $encoding = \mb_detect_encoding($value)) { return \strlen($value); } return \mb_strlen($value, $encoding); } protected static function reportInvalidArgument($message) { throw new InvalidArgumentException($message); } private function __construct() { } } mechanism = $mechanism; $this->value = $value; } public function getMechanism() { return $this->mechanism; } public function getValue() { return $this->value; } public static function className($class_name) { return new static('class name', $class_name); } public static function cssSelector($css_selector) { return new static('css selector', $css_selector); } public static function id($id) { return new static('id', $id); } public static function name($name) { return new static('name', $name); } public static function linkText($link_text) { return new static('link text', $link_text); } public static function partialLinkText($partial_link_text) { return new static('partial link text', $partial_link_text); } public static function tagName($tag_name) { return new static('tag name', $tag_name); } public static function xpath($xpath) { return new static('xpath', $xpath); } } executor = $executor; } public function accept() { $this->executor->execute(DriverCommand::ACCEPT_ALERT); return $this; } public function dismiss() { $this->executor->execute(DriverCommand::DISMISS_ALERT); return $this; } public function getText() { return $this->executor->execute(DriverCommand::GET_ALERT_TEXT); } public function sendKeys($value) { $this->executor->execute( DriverCommand::SET_ALERT_VALUE, ['text' => $value] ); return $this; } } getTagName(); if ($tagName !== 'input') { throw new UnexpectedTagNameException('input', $tagName); } $this->name = $element->getAttribute('name'); if ($this->name === null) { throw new WebDriverException('The input does not have a "name" attribute.'); } $this->element = $element; } public function getOptions() { return $this->getRelatedElements(); } public function getAllSelectedOptions() { $selectedElement = []; foreach ($this->getRelatedElements() as $element) { if ($element->isSelected()) { $selectedElement[] = $element; if (!$this->isMultiple()) { return $selectedElement; } } } return $selectedElement; } public function getFirstSelectedOption() { foreach ($this->getRelatedElements() as $element) { if ($element->isSelected()) { return $element; } } throw new NoSuchElementException( sprintf('No %s are selected', $this->type === 'radio' ? 'radio buttons' : 'checkboxes') ); } public function selectByIndex($index) { $this->byIndex($index); } public function selectByValue($value) { $this->byValue($value); } public function selectByVisibleText($text) { $this->byVisibleText($text); } public function selectByVisiblePartialText($text) { $this->byVisibleText($text, true); } protected function byValue($value, $select = true) { $matched = false; foreach ($this->getRelatedElements($value) as $element) { $select ? $this->selectOption($element) : $this->deselectOption($element); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate %s with value: %s', $this->type, $value) ); } } protected function byIndex($index, $select = true) { $elements = $this->getRelatedElements(); if (!isset($elements[$index])) { throw new NoSuchElementException(sprintf('Cannot locate %s with index: %d', $this->type, $index)); } $select ? $this->selectOption($elements[$index]) : $this->deselectOption($elements[$index]); } protected function byVisibleText($text, $partial = false, $select = true) { foreach ($this->getRelatedElements() as $element) { $normalizeFilter = sprintf( $partial ? 'contains(normalize-space(.), %s)' : 'normalize-space(.) = %s', XPathEscaper::escapeQuotes($text) ); $xpath = 'ancestor::label'; $xpathNormalize = sprintf('%s[%s]', $xpath, $normalizeFilter); $id = $element->getAttribute('id'); if ($id !== null) { $idFilter = sprintf('@for = %s', XPathEscaper::escapeQuotes($id)); $xpath .= sprintf(' | //label[%s]', $idFilter); $xpathNormalize .= sprintf(' | //label[%s and %s]', $idFilter, $normalizeFilter); } try { $element->findElement(WebDriverBy::xpath($xpathNormalize)); } catch (NoSuchElementException $e) { if ($partial) { continue; } try { if ($text !== $element->findElement(WebDriverBy::xpath($xpath))->getText()) { continue; } } catch (NoSuchElementException $e) { continue; } } $select ? $this->selectOption($element) : $this->deselectOption($element); if (!$this->isMultiple()) { return; } } } protected function getRelatedElements($value = null) { $valueSelector = $value ? sprintf(' and @value = %s', XPathEscaper::escapeQuotes($value)) : ''; $formId = $this->element->getAttribute('form'); if ($formId === null) { $form = $this->element->findElement(WebDriverBy::xpath('ancestor::form')); $formId = $form->getAttribute('id'); if ($formId === '' || $formId === null) { return $form->findElements(WebDriverBy::xpath( sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector) )); } } return $this->element->findElements( WebDriverBy::xpath(sprintf( '//form[@id = %1$s]//input[@name = %2$s%3$s' . ' and ((boolean(@form) = true() and @form = %1$s) or boolean(@form) = false())]' . ' | //input[@form = %1$s and @name = %2$s%3$s]', XPathEscaper::escapeQuotes($formId), XPathEscaper::escapeQuotes($this->name), $valueSelector )) ); } protected function selectOption(WebDriverElement $element) { if (!$element->isSelected()) { $element->click(); } } protected function deselectOption(WebDriverElement $element) { if ($element->isSelected()) { $element->click(); } } } validateCookieName($name); $this->validateCookieValue($value); $this->cookie['name'] = $name; $this->cookie['value'] = $value; } public static function createFromArray(array $cookieArray) { if (!isset($cookieArray['name'])) { throw new InvalidArgumentException('Cookie name should be set'); } if (!isset($cookieArray['value'])) { throw new InvalidArgumentException('Cookie value should be set'); } $cookie = new self($cookieArray['name'], $cookieArray['value']); if (isset($cookieArray['path'])) { $cookie->setPath($cookieArray['path']); } if (isset($cookieArray['domain'])) { $cookie->setDomain($cookieArray['domain']); } if (isset($cookieArray['expiry'])) { $cookie->setExpiry($cookieArray['expiry']); } if (isset($cookieArray['secure'])) { $cookie->setSecure($cookieArray['secure']); } if (isset($cookieArray['httpOnly'])) { $cookie->setHttpOnly($cookieArray['httpOnly']); } if (isset($cookieArray['sameSite'])) { $cookie->setSameSite($cookieArray['sameSite']); } return $cookie; } public function getName() { return $this->offsetGet('name'); } public function getValue() { return $this->offsetGet('value'); } public function setPath($path) { $this->offsetSet('path', $path); } public function getPath() { return $this->offsetGet('path'); } public function setDomain($domain) { if (mb_strpos($domain, ':') !== false) { throw new InvalidArgumentException(sprintf('Cookie domain "%s" should not contain a port', $domain)); } $this->offsetSet('domain', $domain); } public function getDomain() { return $this->offsetGet('domain'); } public function setExpiry($expiry) { $this->offsetSet('expiry', (int) $expiry); } public function getExpiry() { return $this->offsetGet('expiry'); } public function setSecure($secure) { $this->offsetSet('secure', $secure); } public function isSecure() { return $this->offsetGet('secure'); } public function setHttpOnly($httpOnly) { $this->offsetSet('httpOnly', $httpOnly); } public function isHttpOnly() { return $this->offsetGet('httpOnly'); } public function setSameSite($sameSite) { $this->offsetSet('sameSite', $sameSite); } public function getSameSite() { return $this->offsetGet('sameSite'); } public function toArray() { $cookie = $this->cookie; if (!isset($cookie['secure'])) { $cookie['secure'] = false; } return $cookie; } #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookie[$offset]); } #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->cookie[$offset] : null; } #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($value === null) { unset($this->cookie[$offset]); } else { $this->cookie[$offset] = $value; } } #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->cookie[$offset]); } protected function validateCookieName($name) { if ($name === null || $name === '') { throw new InvalidArgumentException('Cookie name should be non-empty'); } if (mb_strpos($name, ';') !== false) { throw new InvalidArgumentException('Cookie name should not contain a ";"'); } } protected function validateCookieValue($value) { if ($value === null) { throw new InvalidArgumentException('Cookie value is required when setting a cookie'); } } } microtime(true)) { if ($this->getHTTPResponseCode($url) === 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeoutException(sprintf( 'Timed out waiting for %s to become available after %d ms.', $url, $timeout_in_ms )); } public function waitUntilUnavailable($timeout_in_ms, $url) { $end = microtime(true) + $timeout_in_ms / 1000; while ($end > microtime(true)) { if ($this->getHTTPResponseCode($url) !== 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeoutException(sprintf( 'Timed out waiting for %s to become unavailable after %d ms.', $url, $timeout_in_ms )); } private function getHTTPResponseCode($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { define('CURLOPT_CONNECTTIMEOUT_MS', 156); } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, self::CONNECT_TIMEOUT_MS); $code = null; try { curl_exec($ch); $info = curl_getinfo($ch); $code = $info['http_code']; } catch (Exception $e) { } curl_close($ch); return $code; } } type = $element->getAttribute('type'); if ($this->type !== 'radio') { throw new WebDriverException('The input must be of type "radio".'); } } public function isMultiple() { return false; } public function deselectAll() { throw new UnsupportedOperationException('You cannot deselect radio buttons'); } public function deselectByIndex($index) { throw new UnsupportedOperationException('You cannot deselect radio buttons'); } public function deselectByValue($value) { throw new UnsupportedOperationException('You cannot deselect radio buttons'); } public function deselectByVisibleText($text) { throw new UnsupportedOperationException('You cannot deselect radio buttons'); } public function deselectByVisiblePartialText($text) { throw new UnsupportedOperationException('You cannot deselect radio buttons'); } } getTagName(); if ($tag_name !== 'select') { throw new UnexpectedTagNameException('select', $tag_name); } $this->element = $element; $value = $element->getAttribute('multiple'); $this->isMulti = $value === 'true'; } public function isMultiple() { return $this->isMulti; } public function getOptions() { return $this->element->findElements(WebDriverBy::tagName('option')); } public function getAllSelectedOptions() { $selected_options = []; foreach ($this->getOptions() as $option) { if ($option->isSelected()) { $selected_options[] = $option; if (!$this->isMultiple()) { return $selected_options; } } } return $selected_options; } public function getFirstSelectedOption() { foreach ($this->getOptions() as $option) { if ($option->isSelected()) { return $option; } } throw new NoSuchElementException('No options are selected'); } public function selectByIndex($index) { foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->selectOption($option); return; } } throw new NoSuchElementException(sprintf('Cannot locate option with index: %d', $index)); } public function selectByValue($value) { $matched = false; $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with value: %s', $value) ); } } public function selectByVisibleText($text) { $matched = false; $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { foreach ($this->getOptions() as $option) { if ($option->getText() === $text) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } } } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function selectByVisiblePartialText($text) { $matched = false; $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function deselectAll() { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect all options of a multi-select'); } foreach ($this->getOptions() as $option) { $this->deselectOption($option); } } public function deselectByIndex($index) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->deselectOption($option); return; } } } public function deselectByValue($value) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisibleText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisiblePartialText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } protected function selectOption(WebDriverElement $option) { if (!$option->isSelected()) { $option->click(); } } protected function deselectOption(WebDriverElement $option) { if ($option->isSelected()) { $option->click(); } } } executor = $executor; } public function back() { $this->executor->execute(DriverCommand::GO_BACK); return $this; } public function forward() { $this->executor->execute(DriverCommand::GO_FORWARD); return $this; } public function refresh() { $this->executor->execute(DriverCommand::REFRESH); return $this; } public function to($url) { $params = ['url' => (string) $url]; $this->executor->execute(DriverCommand::GET, $params); return $this; } } executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } public function addCookie($cookie) { if (is_array($cookie)) { $cookie = Cookie::createFromArray($cookie); } if (!$cookie instanceof Cookie) { throw new InvalidArgumentException('Cookie must be set from instance of Cookie class or from array.'); } $this->executor->execute( DriverCommand::ADD_COOKIE, ['cookie' => $cookie->toArray()] ); return $this; } public function deleteAllCookies() { $this->executor->execute(DriverCommand::DELETE_ALL_COOKIES); return $this; } public function deleteCookieNamed($name) { $this->executor->execute( DriverCommand::DELETE_COOKIE, [':name' => $name] ); return $this; } public function getCookieNamed($name) { if ($this->isW3cCompliant) { $cookieArray = $this->executor->execute( DriverCommand::GET_NAMED_COOKIE, [':name' => $name] ); if (!is_array($cookieArray)) { throw new NoSuchCookieException('no such cookie'); } return Cookie::createFromArray($cookieArray); } $cookies = $this->getCookies(); foreach ($cookies as $cookie) { if ($cookie['name'] === $name) { return $cookie; } } return null; } public function getCookies() { $cookieArrays = $this->executor->execute(DriverCommand::GET_ALL_COOKIES); if (!is_array($cookieArrays)) { return []; } $cookies = []; foreach ($cookieArrays as $cookieArray) { $cookies[] = Cookie::createFromArray($cookieArray); } return $cookies; } public function timeouts() { return new WebDriverTimeouts($this->executor, $this->isW3cCompliant); } public function window() { return new WebDriverWindow($this->executor, $this->isW3cCompliant); } public function getLog($log_type) { return $this->executor->execute( DriverCommand::GET_LOG, ['type' => $log_type] ); } public function getAvailableLogTypes() { return $this->executor->execute(DriverCommand::GET_AVAILABLE_LOG_TYPES); } } x = $x; $this->y = $y; } public function getX() { return (int) $this->x; } public function getY() { return (int) $this->y; } public function move($new_x, $new_y) { $this->x = $new_x; $this->y = $new_y; return $this; } public function moveBy($x_offset, $y_offset) { $this->x += $x_offset; $this->y += $y_offset; return $this; } public function equals(self $point) { return $this->x === $point->getX() && $this->y === $point->getY(); } } driver = $driver; $this->timeout = isset($timeout_in_second) ? $timeout_in_second : 30; $this->interval = $interval_in_millisecond ?: 250; } public function until($func_or_ec, $message = '') { $end = microtime(true) + $this->timeout; $last_exception = null; while ($end > microtime(true)) { try { if ($func_or_ec instanceof WebDriverExpectedCondition) { $ret_val = call_user_func($func_or_ec->getApply(), $this->driver); } else { $ret_val = call_user_func($func_or_ec, $this->driver); } if ($ret_val) { return $ret_val; } } catch (NoSuchElementException $e) { $last_exception = $e; } usleep($this->interval * 1000); } if ($last_exception) { throw $last_exception; } throw new TimeoutException($message); } } executor = $executor; $this->id = $id; $this->fileDetector = new UselessFileDetector(); $this->isW3cCompliant = $isW3cCompliant; } public function clear() { $this->executor->execute( DriverCommand::CLEAR_ELEMENT, [':id' => $this->id] ); return $this; } public function click() { try { $this->executor->execute( DriverCommand::CLICK_ELEMENT, [':id' => $this->id] ); } catch (ElementNotInteractableException $e) { $this->clickChildElement($e); } return $this; } public function findElement(WebDriverBy $by) { $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_element = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENT, $params ); return $this->newElement(JsonWireCompat::getElement($raw_element)); } public function findElements(WebDriverBy $by) { $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_elements = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENTS, $params ); $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; } public function getAttribute($attribute_name) { $params = [ ':name' => $attribute_name, ':id' => $this->id, ]; if ($this->isW3cCompliant && ($attribute_name === 'value' || $attribute_name === 'index')) { $value = $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } if ($value !== null) { return (string) $value; } } return $this->executor->execute(DriverCommand::GET_ELEMENT_ATTRIBUTE, $params); } public function getDomProperty($propertyName) { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('This method is only supported in W3C mode'); } $params = [ ':name' => $propertyName, ':id' => $this->id, ]; return $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); } public function getCSSValue($css_property_name) { $params = [ ':propertyName' => $css_property_name, ':id' => $this->id, ]; return $this->executor->execute( DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY, $params ); } public function getLocation() { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION, [':id' => $this->id] ); return new WebDriverPoint($location['x'], $location['y']); } public function getLocationOnScreenOnceScrolledIntoView() { if ($this->isW3cCompliant) { $script = <<executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $script, 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], ]); $location = ['x' => $result['x'], 'y' => $result['y']]; } else { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, [':id' => $this->id] ); } return new WebDriverPoint($location['x'], $location['y']); } public function getCoordinates() { $element = $this; $on_screen = null; $in_view_port = static function () use ($element) { return $element->getLocationOnScreenOnceScrolledIntoView(); }; $on_page = static function () use ($element) { return $element->getLocation(); }; $auxiliary = $this->getID(); return new WebDriverCoordinates( $on_screen, $in_view_port, $on_page, $auxiliary ); } public function getSize() { $size = $this->executor->execute( DriverCommand::GET_ELEMENT_SIZE, [':id' => $this->id] ); return new WebDriverDimension($size['width'], $size['height']); } public function getTagName() { return mb_strtolower($this->executor->execute( DriverCommand::GET_ELEMENT_TAG_NAME, [':id' => $this->id] )); } public function getText() { return $this->executor->execute( DriverCommand::GET_ELEMENT_TEXT, [':id' => $this->id] ); } public function isDisplayed() { return $this->executor->execute( DriverCommand::IS_ELEMENT_DISPLAYED, [':id' => $this->id] ); } public function isEnabled() { return $this->executor->execute( DriverCommand::IS_ELEMENT_ENABLED, [':id' => $this->id] ); } public function isSelected() { return $this->executor->execute( DriverCommand::IS_ELEMENT_SELECTED, [':id' => $this->id] ); } public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); $params = []; if ($local_file === null) { if ($this->isW3cCompliant) { $encodedValues = explode(WebDriverKeys::NULL, WebDriverKeys::encode($value, true)); foreach ($encodedValues as $encodedValue) { $params[] = [ 'text' => $encodedValue, ':id' => $this->id, ]; } } else { $params[] = [ 'value' => WebDriverKeys::encode($value), ':id' => $this->id, ]; } } else { if ($this->isW3cCompliant) { try { $fileName = $this->upload($local_file); } catch (WebDriverException $e) { $fileName = $local_file; } $params[] = [ 'text' => $fileName, ':id' => $this->id, ]; } else { $params[] = [ 'value' => WebDriverKeys::encode($this->upload($local_file)), ':id' => $this->id, ]; } } foreach ($params as $param) { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $param); } return $this; } public function setFileDetector(FileDetector $detector) { $this->fileDetector = $detector; return $this; } public function submit() { if ($this->isW3cCompliant) { $submitPolyfill = $script = <<executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $submitPolyfill, 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], ]); return $this; } $this->executor->execute( DriverCommand::SUBMIT_ELEMENT, [':id' => $this->id] ); return $this; } public function getID() { return $this->id; } public function takeElementScreenshot($save_as = null) { $screenshot = base64_decode( $this->executor->execute( DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $this->id] ), true ); if ($save_as !== null) { $directoryPath = dirname($save_as); if (!file_exists($directoryPath)) { mkdir($directoryPath, 0777, true); } file_put_contents($save_as, $screenshot); } return $screenshot; } public function equals(WebDriverElement $other) { if ($this->isW3cCompliant) { return $this->getID() === $other->getID(); } return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [ ':id' => $this->id, ':other' => $other->getID(), ]); } protected function clickChildElement(ElementNotInteractableException $originalException) { $children = $this->findElements(WebDriverBy::xpath('./*')); foreach ($children as $child) { try { $this->executor->execute( DriverCommand::CLICK_ELEMENT, [':id' => $child->id] ); return; } catch (ElementNotInteractableException $e) { } } throw $originalException; } protected function newElement($id) { return new static($this->executor, $id, $this->isW3cCompliant); } protected function upload($local_file) { if (!is_file($local_file)) { throw new WebDriverException('You may only upload files: ' . $local_file); } $temp_zip_path = $this->createTemporaryZipArchive($local_file); $remote_path = $this->executor->execute( DriverCommand::UPLOAD_FILE, ['file' => base64_encode(file_get_contents($temp_zip_path))] ); unlink($temp_zip_path); return $remote_path; } protected function createTemporaryZipArchive($fileToZip) { $tempZipPath = sys_get_temp_dir() . '/' . uniqid('WebDriverZip', false); $zip = new ZipArchive(); if (($errorCode = $zip->open($tempZipPath, ZipArchive::CREATE)) !== true) { throw new WebDriverException(sprintf('Error creating zip archive: %s', $errorCode)); } $info = pathinfo($fileToZip); $file_name = $info['basename']; $zip->addFile($fileToZip, $file_name); $zip->close(); return $tempZipPath; } } executor = $executor; } public function tap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_SINGLE_TAP, ['element' => $element->getID()] ); return $this; } public function doubleTap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_DOUBLE_TAP, ['element' => $element->getID()] ); return $this; } public function down($x, $y) { $this->executor->execute(DriverCommand::TOUCH_DOWN, [ 'x' => $x, 'y' => $y, ]); return $this; } public function flick($xspeed, $yspeed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xspeed' => $xspeed, 'yspeed' => $yspeed, ]); return $this; } public function flickFromElement(WebDriverElement $element, $xoffset, $yoffset, $speed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, 'element' => $element->getID(), 'speed' => $speed, ]); return $this; } public function longPress(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_LONG_PRESS, ['element' => $element->getID()] ); return $this; } public function move($x, $y) { $this->executor->execute(DriverCommand::TOUCH_MOVE, [ 'x' => $x, 'y' => $y, ]); return $this; } public function scroll($xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } public function scrollFromElement(WebDriverElement $element, $xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'element' => $element->getID(), 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } public function up($x, $y) { $this->executor->execute(DriverCommand::TOUCH_UP, [ 'x' => $x, 'y' => $y, ]); return $this; } } executor = $executor; $this->driver = $driver; $this->isW3cCompliant = $isW3cCompliant; } public function sendKeys($keys) { if ($this->isW3cCompliant) { $activeElement = $this->driver->switchTo()->activeElement(); $activeElement->sendKeys($keys); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => WebDriverKeys::encode($keys), ]); } return $this; } public function pressKey($key) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'key', 'id' => 'keyboard', 'actions' => [['type' => 'keyDown', 'value' => $key]], ], ], ]); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); } return $this; } public function releaseKey($key) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'key', 'id' => 'keyboard', 'actions' => [['type' => 'keyUp', 'value' => $key]], ], ], ]); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); } return $this; } } driver = $driver; } public function execute($command_name, array $parameters = []) { return $this->driver->execute($command_name, $parameters); } } executor = $executor; $this->driver = $driver; $this->isW3cCompliant = $isW3cCompliant; } public function defaultContent() { $params = ['id' => null]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } public function frame($frame) { if ($this->isW3cCompliant) { if ($frame instanceof WebDriverElement) { $id = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $frame->getID()]; } elseif ($frame === null) { $id = null; } elseif (is_int($frame)) { $id = $frame; } else { throw new \InvalidArgumentException( 'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null' ); } } else { if ($frame instanceof WebDriverElement) { $id = ['ELEMENT' => $frame->getID()]; } elseif ($frame === null) { $id = null; } elseif (is_int($frame)) { $id = $frame; } else { $id = (string) $frame; } } $params = ['id' => $id]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } public function parent() { $this->executor->execute(DriverCommand::SWITCH_TO_PARENT_FRAME, []); return $this->driver; } public function window($handle) { if ($this->isW3cCompliant) { $params = ['handle' => (string) $handle]; } else { $params = ['name' => (string) $handle]; } $this->executor->execute(DriverCommand::SWITCH_TO_WINDOW, $params); return $this->driver; } public function newWindow($windowType = self::WINDOW_TYPE_TAB) { if ($windowType !== self::WINDOW_TYPE_TAB && $windowType !== self::WINDOW_TYPE_WINDOW) { throw new \InvalidArgumentException('Window type must by either "tab" or "window"'); } if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('New window is only supported in W3C mode'); } $response = $this->executor->execute(DriverCommand::NEW_WINDOW, ['type' => $windowType]); $this->window($response['handle']); return $this->driver; } public function alert() { return new WebDriverAlert($this->executor); } public function activeElement() { $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); return new RemoteWebElement($method, JsonWireCompat::getElement($response), $this->isW3cCompliant); } } setCustomRequestParameters($url, $method); parent::__construct($session_id, DriverCommand::CUSTOM_COMMAND, $parameters); } public function getCustomUrl() { if ($this->customUrl === null) { throw new WebDriverException('URL of custom command is not set'); } return $this->customUrl; } public function getCustomMethod() { if ($this->customMethod === null) { throw new WebDriverException('Method of custom command is not set'); } return $this->customMethod; } protected function setCustomRequestParameters($custom_url, $custom_method) { $allowedMethods = [static::METHOD_GET, static::METHOD_POST]; if (!in_array($custom_method, $allowedMethods, true)) { throw new WebDriverException( sprintf( 'Invalid custom method "%s", must be one of [%s]', $custom_method, implode(', ', $allowedMethods) ) ); } $this->customMethod = $custom_method; if (mb_strpos($custom_url, '/') !== 0) { throw new WebDriverException( sprintf('URL of custom command has to start with / but is "%s"', $custom_url) ); } $this->customUrl = $custom_url; } } getURL()); $this->service = $service; } public function execute(WebDriverCommand $command) { if ($command->getName() === DriverCommand::NEW_SESSION) { $this->service->start(); } try { $value = parent::execute($command); if ($command->getName() === DriverCommand::QUIT) { $this->service->stop(); } return $value; } catch (\Exception $e) { if (!$this->service->isRunning()) { throw new DriverServerDiedException($e); } throw $e; } } } setExecutable($executable); $this->url = sprintf('http://localhost:%d', $port); $this->args = $args; $this->environment = $environment ?: $_ENV; } public function getURL() { return $this->url; } public function start() { if ($this->process !== null) { return $this; } $this->process = $this->createProcess(); $this->process->start(); $this->checkWasStarted($this->process); $checker = new URLChecker(); $checker->waitUntilAvailable(20 * 1000, $this->url . '/status'); return $this; } public function stop() { if ($this->process === null) { return $this; } $this->process->stop(); $this->process = null; $checker = new URLChecker(); $checker->waitUntilUnavailable(3 * 1000, $this->url . '/shutdown'); return $this; } public function isRunning() { if ($this->process === null) { return false; } return $this->process->isRunning(); } protected static function checkExecutable($executable) { return $executable; } protected function setExecutable($executable) { if ($this->isExecutable($executable)) { $this->executable = $executable; return; } throw new Exception( sprintf( '"%s" is not executable. Make sure the path is correct or use environment variable to specify' . ' location of the executable.', $executable ) ); } protected function checkWasStarted($process) { usleep(10000); if (!$process->isRunning()) { throw new Exception( sprintf( 'Error starting driver executable "%s": %s', $process->getCommandLine(), $process->getErrorOutput() ) ); } } private function createProcess() { if (class_exists(ProcessBuilder::class) && mb_strpos('@deprecated', (new \ReflectionClass(ProcessBuilder::class))->getDocComment()) === false ) { $processBuilder = (new ProcessBuilder()) ->setPrefix($this->executable) ->setArguments($this->args) ->addEnvironmentVariables($this->environment); return $processBuilder->getProcess(); } $commandLine = array_merge([$this->executable], $this->args); return new Process($commandLine, null, $this->environment); } private function isExecutable($filename) { if (is_executable($filename)) { return true; } if ($filename !== basename($filename)) { return false; } $paths = explode(PATH_SEPARATOR, getenv('PATH')); foreach ($paths as $path) { if (is_executable($path . DIRECTORY_SEPARATOR . $filename)) { return true; } } return false; } } sessionID = $session_id; $this->name = $name; $this->parameters = $parameters; } public static function newSession(array $parameters) { return new self(null, DriverCommand::NEW_SESSION, $parameters); } public function getName() { return $this->name; } public function getSessionID() { return $this->sessionID; } public function getParameters() { return $this->parameters; } } ['method' => 'POST', 'url' => '/session/:sessionId/accept_alert'], DriverCommand::ADD_COOKIE => ['method' => 'POST', 'url' => '/session/:sessionId/cookie'], DriverCommand::CLEAR_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/clear'], DriverCommand::CLICK_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/click'], DriverCommand::CLOSE => ['method' => 'DELETE', 'url' => '/session/:sessionId/window'], DriverCommand::DELETE_ALL_COOKIES => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie'], DriverCommand::DELETE_COOKIE => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie/:name'], DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/dismiss_alert'], DriverCommand::ELEMENT_EQUALS => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/equals/:other'], DriverCommand::FIND_CHILD_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/element'], DriverCommand::FIND_CHILD_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/elements'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute_async'], DriverCommand::FIND_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element'], DriverCommand::FIND_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/elements'], DriverCommand::SWITCH_TO_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame'], DriverCommand::SWITCH_TO_PARENT_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame/parent'], DriverCommand::SWITCH_TO_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window'], DriverCommand::GET => ['method' => 'POST', 'url' => '/session/:sessionId/url'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert_text'], DriverCommand::GET_ALL_COOKIES => ['method' => 'GET', 'url' => '/session/:sessionId/cookie'], DriverCommand::GET_NAMED_COOKIE => ['method' => 'GET', 'url' => '/session/:sessionId/cookie/:name'], DriverCommand::GET_ALL_SESSIONS => ['method' => 'GET', 'url' => '/sessions'], DriverCommand::GET_AVAILABLE_LOG_TYPES => ['method' => 'GET', 'url' => '/session/:sessionId/log/types'], DriverCommand::GET_CURRENT_URL => ['method' => 'GET', 'url' => '/session/:sessionId/url'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window_handle'], DriverCommand::GET_ELEMENT_ATTRIBUTE => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/attribute/:name', ], DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/css/:propertyName', ], DriverCommand::GET_ELEMENT_LOCATION => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location', ], DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location_in_view', ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/size'], DriverCommand::GET_ELEMENT_TAG_NAME => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/name'], DriverCommand::GET_ELEMENT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/text'], DriverCommand::GET_LOG => ['method' => 'POST', 'url' => '/session/:sessionId/log'], DriverCommand::GET_PAGE_SOURCE => ['method' => 'GET', 'url' => '/session/:sessionId/source'], DriverCommand::GET_SCREEN_ORIENTATION => ['method' => 'GET', 'url' => '/session/:sessionId/orientation'], DriverCommand::GET_CAPABILITIES => ['method' => 'GET', 'url' => '/session/:sessionId'], DriverCommand::GET_TITLE => ['method' => 'GET', 'url' => '/session/:sessionId/title'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window_handles'], DriverCommand::GET_WINDOW_POSITION => [ 'method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/size'], DriverCommand::GO_BACK => ['method' => 'POST', 'url' => '/session/:sessionId/back'], DriverCommand::GO_FORWARD => ['method' => 'POST', 'url' => '/session/:sessionId/forward'], DriverCommand::IS_ELEMENT_DISPLAYED => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/displayed', ], DriverCommand::IS_ELEMENT_ENABLED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/enabled'], DriverCommand::IS_ELEMENT_SELECTED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/selected'], DriverCommand::MAXIMIZE_WINDOW => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/maximize', ], DriverCommand::MOUSE_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/buttondown'], DriverCommand::MOUSE_UP => ['method' => 'POST', 'url' => '/session/:sessionId/buttonup'], DriverCommand::CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/click'], DriverCommand::DOUBLE_CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/doubleclick'], DriverCommand::MOVE_TO => ['method' => 'POST', 'url' => '/session/:sessionId/moveto'], DriverCommand::NEW_SESSION => ['method' => 'POST', 'url' => '/session'], DriverCommand::QUIT => ['method' => 'DELETE', 'url' => '/session/:sessionId'], DriverCommand::REFRESH => ['method' => 'POST', 'url' => '/session/:sessionId/refresh'], DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/file'], DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/keys'], DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert_text'], DriverCommand::SEND_KEYS_TO_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/value'], DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/implicit_wait'], DriverCommand::SET_SCREEN_ORIENTATION => ['method' => 'POST', 'url' => '/session/:sessionId/orientation'], DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/async_script'], DriverCommand::SET_WINDOW_POSITION => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::SET_WINDOW_SIZE => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/size', ], DriverCommand::STATUS => ['method' => 'GET', 'url' => '/status'], DriverCommand::SUBMIT_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/submit'], DriverCommand::SCREENSHOT => ['method' => 'GET', 'url' => '/session/:sessionId/screenshot'], DriverCommand::TAKE_ELEMENT_SCREENSHOT => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/screenshot', ], DriverCommand::TOUCH_SINGLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/click'], DriverCommand::TOUCH_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/touch/down'], DriverCommand::TOUCH_DOUBLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/doubleclick'], DriverCommand::TOUCH_FLICK => ['method' => 'POST', 'url' => '/session/:sessionId/touch/flick'], DriverCommand::TOUCH_LONG_PRESS => ['method' => 'POST', 'url' => '/session/:sessionId/touch/longclick'], DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'], DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], DriverCommand::CUSTOM_COMMAND => [], ]; protected static $w3cCompliantCommands = [ DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/accept'], DriverCommand::ACTIONS => ['method' => 'POST', 'url' => '/session/:sessionId/actions'], DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], DriverCommand::FULLSCREEN_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/fullscreen'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'GET', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window'], DriverCommand::GET_ELEMENT_LOCATION => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_ELEMENT_PROPERTY => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/property/:name', ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'], DriverCommand::GET_WINDOW_POSITION => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::MAXIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/maximize'], DriverCommand::MINIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/minimize'], DriverCommand::NEW_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/new'], DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert/text'], DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_WINDOW_SIZE => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], DriverCommand::SET_WINDOW_POSITION => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], ]; protected $url; protected $curl; protected $isW3cCompliant = true; public function __construct($url, $http_proxy = null, $http_proxy_port = null) { self::$w3cCompliantCommands = array_merge(self::$commands, self::$w3cCompliantCommands); $this->url = $url; $this->curl = curl_init(); if (!empty($http_proxy)) { curl_setopt($this->curl, CURLOPT_PROXY, $http_proxy); if ($http_proxy_port !== null) { curl_setopt($this->curl, CURLOPT_PROXYPORT, $http_proxy_port); } } $matches = null; if (preg_match("/^(https?:\/\/)(.*):(.*)@(.*?)/U", $url, $matches)) { $this->url = $matches[1] . $matches[4]; $auth_creds = $matches[2] . ':' . $matches[3]; curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($this->curl, CURLOPT_USERPWD, $auth_creds); } curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); $this->setRequestTimeout(30000); $this->setConnectionTimeout(30000); } public function disableW3cCompliance() { $this->isW3cCompliant = false; } public function setConnectionTimeout($timeout_in_ms) { curl_setopt( $this->curl, 156, $timeout_in_ms ); return $this; } public function setRequestTimeout($timeout_in_ms) { curl_setopt( $this->curl, 155, $timeout_in_ms ); return $this; } public function execute(WebDriverCommand $command) { $http_options = $this->getCommandHttpOptions($command); $http_method = $http_options['method']; $url = $http_options['url']; $sessionID = $command->getSessionID(); $url = str_replace(':sessionId', $sessionID === null ? '' : $sessionID, $url); $params = $command->getParameters(); foreach ($params as $name => $value) { if ($name[0] === ':') { $url = str_replace($name, $value, $url); unset($params[$name]); } } if (is_array($params) && !empty($params) && $http_method !== 'POST') { throw new BadMethodCallException(sprintf( 'The http method called for %s is %s but it has to be POST' . ' if you want to pass the JSON params %s', $url, $http_method, json_encode($params) )); } curl_setopt($this->curl, CURLOPT_URL, $this->url . $url); if ($command->getName() === DriverCommand::NEW_SESSION) { curl_setopt($this->curl, CURLOPT_POST, 1); } else { curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); } if (in_array($http_method, ['POST', 'PUT'], true)) { curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_merge(static::DEFAULT_HTTP_HEADERS, ['Expect:'])); } else { curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); } $encoded_params = null; if ($http_method === 'POST') { if (is_array($params) && !empty($params)) { $encoded_params = json_encode($params); } elseif ($this->isW3cCompliant) { $encoded_params = '{}'; } } curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params); $raw_results = trim(curl_exec($this->curl)); if ($error = curl_error($this->curl)) { $msg = sprintf( 'Curl error thrown for http %s to %s', $http_method, $url ); if (is_array($params) && !empty($params)) { $msg .= sprintf(' with params: %s', json_encode($params, JSON_UNESCAPED_SLASHES)); } throw new WebDriverCurlException($msg . "\n\n" . $error); } $results = json_decode($raw_results, true); if ($results === null && json_last_error() !== JSON_ERROR_NONE) { throw new WebDriverException( sprintf( "JSON decoding of remote response failed.\n" . "Error code: %d\n" . "The response: '%s'\n", json_last_error(), $raw_results ) ); } $value = null; if (is_array($results) && array_key_exists('value', $results)) { $value = $results['value']; } $message = null; if (is_array($value) && array_key_exists('message', $value)) { $message = $value['message']; } $sessionId = null; if (is_array($value) && array_key_exists('sessionId', $value)) { $sessionId = $value['sessionId']; } elseif (is_array($results) && array_key_exists('sessionId', $results)) { $sessionId = $results['sessionId']; } if (isset($value['error'])) { WebDriverException::throwException($value['error'], $message, $results); } $status = isset($results['status']) ? $results['status'] : 0; if ($status !== 0) { WebDriverException::throwException($status, $message, $results); } $response = new WebDriverResponse($sessionId); return $response ->setStatus($status) ->setValue($value); } public function getAddressOfRemoteServer() { return $this->url; } protected function getCommandHttpOptions(WebDriverCommand $command) { $commandName = $command->getName(); if (!isset(self::$commands[$commandName])) { if ($this->isW3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); } } if ($this->isW3cCompliant) { $raw = self::$w3cCompliantCommands[$command->getName()]; } else { $raw = self::$commands[$command->getName()]; } if ($command instanceof CustomWebDriverCommand) { $url = $command->getCustomUrl(); $method = $command->getCustomMethod(); } else { $url = $raw['url']; $method = $raw['method']; } return [ 'url' => $url, 'method' => $method, ]; } } executor = $commandExecutor; $this->sessionID = $sessionId; $this->isW3cCompliant = $isW3cCompliant; if ($capabilities !== null) { $this->capabilities = $capabilities; } } public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, $connection_timeout_in_ms = null, $request_timeout_in_ms = null, $http_proxy = null, $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url); $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } if ($request_timeout_in_ms !== null) { $executor->setRequestTimeout($request_timeout_in_ms); } $parameters = [ 'capabilities' => [ 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], ], ]; if ($required_capabilities !== null && !empty($required_capabilities->toArray())) { $parameters['capabilities']['alwaysMatch'] = (object) $required_capabilities->toW3cCompatibleArray(); } if ($required_capabilities !== null) { $desired_capabilities->setCapability('requiredCapabilities', (object) $required_capabilities->toArray()); } $parameters['desiredCapabilities'] = (object) $desired_capabilities->toArray(); $command = WebDriverCommand::newSession($parameters); $response = $executor->execute($command); return static::createFromResponse($response, $executor); } public static function createBySessionID( $session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', $connection_timeout_in_ms = null, $request_timeout_in_ms = null ) { $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : true; $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } if ($request_timeout_in_ms !== null) { $executor->setRequestTimeout($request_timeout_in_ms); } if (!$isW3cCompliant) { $executor->disableW3cCompliance(); } return new static($executor, $session_id, null, $isW3cCompliant); } public function close() { $this->execute(DriverCommand::CLOSE, []); return $this; } public function newWindow() { return $this->switchTo()->newWindow(); } public function findElement(WebDriverBy $by) { $raw_element = $this->execute( DriverCommand::FIND_ELEMENT, JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); return $this->newElement(JsonWireCompat::getElement($raw_element)); } public function findElements(WebDriverBy $by) { $raw_elements = $this->execute( DriverCommand::FIND_ELEMENTS, JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; } public function get($url) { $params = ['url' => (string) $url]; $this->execute(DriverCommand::GET, $params); return $this; } public function getCurrentURL() { return $this->execute(DriverCommand::GET_CURRENT_URL); } public function getPageSource() { return $this->execute(DriverCommand::GET_PAGE_SOURCE); } public function getTitle() { return $this->execute(DriverCommand::GET_TITLE); } public function getWindowHandle() { return $this->execute( DriverCommand::GET_CURRENT_WINDOW_HANDLE, [] ); } public function getWindowHandles() { return $this->execute(DriverCommand::GET_WINDOW_HANDLES, []); } public function quit() { $this->execute(DriverCommand::QUIT); $this->executor = null; } public function executeScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params); } public function executeAsyncScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute( DriverCommand::EXECUTE_ASYNC_SCRIPT, $params ); } public function takeScreenshot($save_as = null) { $screenshot = base64_decode($this->execute(DriverCommand::SCREENSHOT), true); if ($save_as !== null) { $directoryPath = dirname($save_as); if (!file_exists($directoryPath)) { mkdir($directoryPath, 0777, true); } file_put_contents($save_as, $screenshot); } return $screenshot; } public function getStatus() { $response = $this->execute(DriverCommand::STATUS); return RemoteStatus::createFromResponse($response); } public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { return new WebDriverWait( $this, $timeout_in_second, $interval_in_millisecond ); } public function manage() { return new WebDriverOptions($this->getExecuteMethod(), $this->isW3cCompliant); } public function navigate() { return new WebDriverNavigation($this->getExecuteMethod()); } public function switchTo() { return new RemoteTargetLocator($this->getExecuteMethod(), $this, $this->isW3cCompliant); } public function getMouse() { if (!$this->mouse) { $this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->isW3cCompliant); } return $this->mouse; } public function getKeyboard() { if (!$this->keyboard) { $this->keyboard = new RemoteKeyboard($this->getExecuteMethod(), $this, $this->isW3cCompliant); } return $this->keyboard; } public function getTouch() { if (!$this->touch) { $this->touch = new RemoteTouchScreen($this->getExecuteMethod()); } return $this->touch; } public function action() { return new WebDriverActions($this); } public function setCommandExecutor(WebDriverCommandExecutor $executor) { $this->executor = $executor; return $this; } public function getCommandExecutor() { return $this->executor; } public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } public function getSessionID() { return $this->sessionID; } public function getCapabilities() { return $this->capabilities; } public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000) { $executor = new HttpCommandExecutor($selenium_server_url, null, null); $executor->setConnectionTimeout($timeout_in_ms); $command = new WebDriverCommand( null, DriverCommand::GET_ALL_SESSIONS, [] ); return $executor->execute($command)->getValue(); } public function execute($command_name, $params = []) { $command = new WebDriverCommand( $this->sessionID, $command_name, $params ); if ($this->executor) { $response = $this->executor->execute($command); return $response->getValue(); } return null; } public function executeCustomCommand($endpointUrl, $method = 'GET', $params = []) { $command = new CustomWebDriverCommand( $this->sessionID, $endpointUrl, $method, $params ); if ($this->executor) { $response = $this->executor->execute($command); return $response->getValue(); } return null; } public function isW3cCompliant() { return $this->isW3cCompliant; } protected static function createFromResponse(WebDriverResponse $response, HttpCommandExecutor $commandExecutor) { $responseValue = $response->getValue(); if (!$isW3cCompliant = isset($responseValue['capabilities'])) { $commandExecutor->disableW3cCompliance(); } if ($isW3cCompliant) { $returnedCapabilities = DesiredCapabilities::createFromW3cCapabilities($responseValue['capabilities']); } else { $returnedCapabilities = new DesiredCapabilities($responseValue); } return new static($commandExecutor, $response->getSessionID(), $returnedCapabilities, $isW3cCompliant); } protected function prepareScriptArguments(array $arguments) { $args = []; foreach ($arguments as $key => $value) { if ($value instanceof WebDriverElement) { $args[$key] = [ $this->isW3cCompliant ? JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER : 'ELEMENT' => $value->getID(), ]; } else { if (is_array($value)) { $value = $this->prepareScriptArguments($value); } $args[$key] = $value; } } return $args; } protected function getExecuteMethod() { if (!$this->executeMethod) { $this->executeMethod = new RemoteExecuteMethod($this); } return $this->executeMethod; } protected function newElement($id) { return new RemoteWebElement($this->getExecuteMethod(), $id, $this->isW3cCompliant); } protected static function castToDesiredCapabilitiesObject($desired_capabilities = null) { if ($desired_capabilities === null) { return new DesiredCapabilities(); } if (is_array($desired_capabilities)) { return new DesiredCapabilities($desired_capabilities); } return $desired_capabilities; } } executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } public function click(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, $this->createClickActions()), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => self::BUTTON_LEFT, ]); return $this; } public function contextClick(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerDown', 'button' => self::BUTTON_RIGHT, ], [ 'type' => 'pointerUp', 'button' => self::BUTTON_RIGHT, ], ]), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => self::BUTTON_RIGHT, ]); return $this; } public function doubleClick(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $clickActions = $this->createClickActions(); $moveAction = $where === null ? [] : [$this->createMoveAction($where)]; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, $clickActions, $clickActions), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::DOUBLE_CLICK); return $this; } public function mouseDown(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => [ $this->createMoveAction($where), [ 'type' => 'pointerDown', 'button' => self::BUTTON_LEFT, ], ], ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_DOWN); return $this; } public function mouseMove( WebDriverCoordinates $where = null, $x_offset = null, $y_offset = null ) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => [$this->createMoveAction($where, $x_offset, $y_offset)], ], ], ]); return $this; } $params = []; if ($where !== null) { $params['element'] = $where->getAuxiliary(); } if ($x_offset !== null) { $params['xoffset'] = $x_offset; } if ($y_offset !== null) { $params['yoffset'] = $y_offset; } $this->executor->execute(DriverCommand::MOVE_TO, $params); return $this; } public function mouseUp(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerUp', 'button' => self::BUTTON_LEFT, ], ]), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_UP); return $this; } protected function moveIfNeeded(WebDriverCoordinates $where = null) { if ($where) { $this->mouseMove($where); } } private function createMoveAction( WebDriverCoordinates $where = null, $x_offset = null, $y_offset = null ) { $move_action = [ 'type' => 'pointerMove', 'duration' => 100, 'x' => $x_offset === null ? 0 : $x_offset, 'y' => $y_offset === null ? 0 : $y_offset, ]; if ($where !== null) { $move_action['origin'] = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $where->getAuxiliary()]; } else { $move_action['origin'] = 'pointer'; } return $move_action; } private function createClickActions() { return [ [ 'type' => 'pointerDown', 'button' => self::BUTTON_LEFT, ], [ 'type' => 'pointerUp', 'button' => self::BUTTON_LEFT, ], ]; } } isReady = (bool) $isReady; $this->message = (string) $message; $this->setMeta($meta); } public static function createFromResponse(array $responseBody) { $object = new static($responseBody['ready'], $responseBody['message'], $responseBody); return $object; } public function isReady() { return $this->isReady; } public function getMessage() { return $this->message; } public function getMeta() { return $this->meta; } protected function setMeta(array $meta) { unset($meta['ready'], $meta['message']); $this->meta = $meta; } } getMechanism(); $value = $by->getValue(); if ($isW3cCompliant) { switch ($mechanism) { case 'class name': $mechanism = 'css selector'; $value = sprintf('.%s', self::escapeSelector($value)); break; case 'id': $mechanism = 'css selector'; $value = sprintf('#%s', self::escapeSelector($value)); break; case 'name': $mechanism = 'css selector'; $value = sprintf('[name=\'%s\']', self::escapeSelector($value)); break; } } return ['using' => $mechanism, 'value' => $value]; } private static function escapeSelector($selector) { return preg_replace_callback('/[^a-z0-9]/iSu', function ($matches) { $chr = $matches[0]; if (mb_strlen($chr) === 1) { $ord = ord($chr); } else { $chr = mb_convert_encoding($chr, 'UTF-32BE', 'UTF-8'); $ord = hexdec(bin2hex($chr)); } return sprintf('\\%X ', $ord); }, $selector); } } sessionID = $session_id; } public function getStatus() { return $this->status; } public function setStatus($status) { $this->status = $status; return $this; } public function getValue() { return $this->value; } public function setValue($value) { $this->value = $value; return $this; } public function getSessionID() { return $this->sessionID; } public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } } 'platformName', WebDriverCapabilityType::VERSION => 'browserVersion', WebDriverCapabilityType::ACCEPT_SSL_CERTS => 'acceptInsecureCerts', ChromeOptions::CAPABILITY => ChromeOptions::CAPABILITY_W3C, ]; public function __construct(array $capabilities = []) { $this->capabilities = $capabilities; } public static function createFromW3cCapabilities(array $capabilities = []) { $w3cToOss = array_flip(self::$ossToW3c); foreach ($w3cToOss as $w3cCapability => $ossCapability) { if (array_key_exists($w3cCapability, $capabilities)) { $capabilities[$ossCapability] = $capabilities[$w3cCapability]; } } return new self($capabilities); } public function getBrowserName() { return $this->get(WebDriverCapabilityType::BROWSER_NAME, ''); } public function setBrowserName($browser_name) { $this->set(WebDriverCapabilityType::BROWSER_NAME, $browser_name); return $this; } public function getVersion() { return $this->get(WebDriverCapabilityType::VERSION, ''); } public function setVersion($version) { $this->set(WebDriverCapabilityType::VERSION, $version); return $this; } public function getCapability($name) { return $this->get($name); } public function setCapability($name, $value) { if ($name === FirefoxOptions::CAPABILITY && is_array($value)) { $defaultOptions = (new FirefoxOptions())->toArray(); $value = array_merge($defaultOptions, $value); } $this->set($name, $value); return $this; } public function getPlatform() { return $this->get(WebDriverCapabilityType::PLATFORM, ''); } public function setPlatform($platform) { $this->set(WebDriverCapabilityType::PLATFORM, $platform); return $this; } public function is($capability_name) { return (bool) $this->get($capability_name); } public function isJavascriptEnabled() { return $this->get(WebDriverCapabilityType::JAVASCRIPT_ENABLED, false); } public function setJavascriptEnabled($enabled) { $browser = $this->getBrowserName(); if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { throw new Exception( 'isJavascriptEnabled() is a htmlunit-only option. ' . 'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.' ); } $this->set(WebDriverCapabilityType::JAVASCRIPT_ENABLED, $enabled); return $this; } public function toArray() { if (isset($this->capabilities[ChromeOptions::CAPABILITY]) && $this->capabilities[ChromeOptions::CAPABILITY] instanceof ChromeOptions ) { $this->capabilities[ChromeOptions::CAPABILITY] = $this->capabilities[ChromeOptions::CAPABILITY]->toArray(); } if (isset($this->capabilities[FirefoxOptions::CAPABILITY]) && $this->capabilities[FirefoxOptions::CAPABILITY] instanceof FirefoxOptions ) { $this->capabilities[FirefoxOptions::CAPABILITY] = $this->capabilities[FirefoxOptions::CAPABILITY]->toArray(); } if (isset($this->capabilities[FirefoxDriver::PROFILE]) && $this->capabilities[FirefoxDriver::PROFILE] instanceof FirefoxProfile ) { $this->capabilities[FirefoxDriver::PROFILE] = $this->capabilities[FirefoxDriver::PROFILE]->encode(); } return $this->capabilities; } public function toW3cCompatibleArray() { $allowedW3cCapabilities = [ 'browserName', 'browserVersion', 'platformName', 'acceptInsecureCerts', 'pageLoadStrategy', 'proxy', 'setWindowRect', 'timeouts', 'strictFileInteractability', 'unhandledPromptBehavior', ]; $ossCapabilities = $this->toArray(); $w3cCapabilities = []; foreach ($ossCapabilities as $capabilityKey => $capabilityValue) { if (in_array($capabilityKey, $allowedW3cCapabilities, true)) { $w3cCapabilities[$capabilityKey] = $capabilityValue; } if (array_key_exists($capabilityKey, self::$ossToW3c)) { if ($capabilityKey === WebDriverCapabilityType::PLATFORM) { $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue); if ($w3cCapabilities[self::$ossToW3c[$capabilityKey]] === 'any') { unset($w3cCapabilities[self::$ossToW3c[$capabilityKey]]); } } else { $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = $capabilityValue; } } if (mb_strpos($capabilityKey, ':') !== false) { $w3cCapabilities[$capabilityKey] = $capabilityValue; } } if (array_key_exists(ChromeOptions::CAPABILITY, $ossCapabilities)) { if (array_key_exists(ChromeOptions::CAPABILITY_W3C, $ossCapabilities)) { $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = new \ArrayObject( array_merge_recursive( (array) $ossCapabilities[ChromeOptions::CAPABILITY], (array) $ossCapabilities[ChromeOptions::CAPABILITY_W3C] ) ); } else { $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = $ossCapabilities[ChromeOptions::CAPABILITY]; } } if (array_key_exists(FirefoxDriver::PROFILE, $ossCapabilities)) { if (!array_key_exists(FirefoxOptions::CAPABILITY, $ossCapabilities) || !array_key_exists('profile', $ossCapabilities[FirefoxOptions::CAPABILITY])) { $w3cCapabilities[FirefoxOptions::CAPABILITY]['profile'] = $ossCapabilities[FirefoxDriver::PROFILE]; } } return $w3cCapabilities; } public static function android() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::ANDROID, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANDROID, ]); } public static function chrome() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function firefox() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::FIREFOX, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); $caps->setCapability(FirefoxOptions::CAPABILITY, new FirefoxOptions()); return $caps; } public static function htmlUnit() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function htmlUnitWithJS() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); return $caps->setJavascriptEnabled(true); } public static function internetExplorer() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } public static function microsoftEdge() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::MICROSOFT_EDGE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } public static function iphone() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPHONE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } public static function ipad() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPAD, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } public static function opera() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::OPERA, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function safari() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::SAFARI, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function phantomjs() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::PHANTOMJS, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } private function set($key, $value) { $this->capabilities[$key] = $value; return $this; } private function get($key, $default = null) { return isset($this->capabilities[$key]) ? $this->capabilities[$key] : $default; } } apply = $apply; } public function getApply() { return $this->apply; } public static function titleIs($title) { return new static( function (WebDriver $driver) use ($title) { return $title === $driver->getTitle(); } ); } public static function titleContains($title) { return new static( function (WebDriver $driver) use ($title) { return mb_strpos($driver->getTitle(), $title) !== false; } ); } public static function titleMatches($titleRegexp) { return new static( function (WebDriver $driver) use ($titleRegexp) { return (bool) preg_match($titleRegexp, $driver->getTitle()); } ); } public static function urlIs($url) { return new static( function (WebDriver $driver) use ($url) { return $url === $driver->getCurrentURL(); } ); } public static function urlContains($url) { return new static( function (WebDriver $driver) use ($url) { return mb_strpos($driver->getCurrentURL(), $url) !== false; } ); } public static function urlMatches($urlRegexp) { return new static( function (WebDriver $driver) use ($urlRegexp) { return (bool) preg_match($urlRegexp, $driver->getCurrentURL()); } ); } public static function presenceOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { return $driver->findElement($by); } catch (NoSuchElementException $e) { return false; } } ); } public static function presenceOfAllElementsLocatedBy(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { $elements = $driver->findElements($by); return count($elements) > 0 ? $elements : null; } ); } public static function visibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { $element = $driver->findElement($by); return $element->isDisplayed() ? $element : null; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function visibilityOfAnyElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { $elements = $driver->findElements($by); $visibleElements = []; foreach ($elements as $element) { try { if ($element->isDisplayed()) { $visibleElements[] = $element; } } catch (StaleElementReferenceException $e) { } } return count($visibleElements) > 0 ? $visibleElements : null; } ); } public static function visibilityOf(WebDriverElement $element) { return new static( function () use ($element) { return $element->isDisplayed() ? $element : null; } ); } public static function textToBePresentInElement(WebDriverBy $by, $text) { return self::elementTextContains($by, $text); } public static function elementTextContains(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { $element_text = $driver->findElement($by)->getText(); return mb_strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementTextIs(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return $driver->findElement($by)->getText() == $text; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementTextMatches(WebDriverBy $by, $regexp) { return new static( function (WebDriver $driver) use ($by, $regexp) { try { return (bool) preg_match($regexp, $driver->findElement($by)->getText()); } catch (StaleElementReferenceException $e) { return null; } } ); } public static function textToBePresentInElementValue(WebDriverBy $by, $text) { return self::elementValueContains($by, $text); } public static function elementValueContains(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { $element_text = $driver->findElement($by)->getAttribute('value'); return mb_strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function frameToBeAvailableAndSwitchToIt($frame_locator) { return new static( function (WebDriver $driver) use ($frame_locator) { try { return $driver->switchTo()->frame($frame_locator); } catch (NoSuchFrameException $e) { return false; } } ); } public static function invisibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { return !$driver->findElement($by)->isDisplayed(); } catch (NoSuchElementException $e) { return true; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function invisibilityOfElementWithText(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return !($driver->findElement($by)->getText() === $text); } catch (NoSuchElementException $e) { return true; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function elementToBeClickable(WebDriverBy $by) { $visibility_of_element_located = self::visibilityOfElementLocated($by); return new static( function (WebDriver $driver) use ($visibility_of_element_located) { $element = call_user_func( $visibility_of_element_located->getApply(), $driver ); try { if ($element !== null && $element->isEnabled()) { return $element; } return null; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function stalenessOf(WebDriverElement $element) { return new static( function () use ($element) { try { $element->isEnabled(); return false; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function refreshed(self $condition) { return new static( function (WebDriver $driver) use ($condition) { try { return call_user_func($condition->getApply(), $driver); } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementToBeSelected($element_or_by) { return self::elementSelectionStateToBe( $element_or_by, true ); } public static function elementSelectionStateToBe($element_or_by, $selected) { if ($element_or_by instanceof WebDriverElement) { return new static( function () use ($element_or_by, $selected) { return $element_or_by->isSelected() === $selected; } ); } if ($element_or_by instanceof WebDriverBy) { return new static( function (WebDriver $driver) use ($element_or_by, $selected) { try { $element = $driver->findElement($element_or_by); return $element->isSelected() === $selected; } catch (StaleElementReferenceException $e) { return null; } } ); } throw new \InvalidArgumentException('Instance of either WebDriverElement or WebDriverBy must be given'); } public static function alertIsPresent() { return new static( function (WebDriver $driver) { try { $alert = $driver->switchTo()->alert(); $alert->getText(); return $alert; } catch (NoSuchAlertException $e) { return null; } } ); } public static function numberOfWindowsToBe($expectedNumberOfWindows) { return new static( function (WebDriver $driver) use ($expectedNumberOfWindows) { return count($driver->getWindowHandles()) == $expectedNumberOfWindows; } ); } public static function not(self $condition) { return new static( function (WebDriver $driver) use ($condition) { $result = call_user_func($condition->getApply(), $driver); return !$result; } ); } } executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } public function getPosition() { $position = $this->executor->execute( DriverCommand::GET_WINDOW_POSITION, [':windowHandle' => 'current'] ); return new WebDriverPoint( $position['x'], $position['y'] ); } public function getSize() { $size = $this->executor->execute( DriverCommand::GET_WINDOW_SIZE, [':windowHandle' => 'current'] ); return new WebDriverDimension( $size['width'], $size['height'] ); } public function minimize() { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('Minimize window is only supported in W3C mode'); } $this->executor->execute(DriverCommand::MINIMIZE_WINDOW, []); return $this; } public function maximize() { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::MAXIMIZE_WINDOW, []); } else { $this->executor->execute( DriverCommand::MAXIMIZE_WINDOW, [':windowHandle' => 'current'] ); } return $this; } public function fullscreen() { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('The Fullscreen window command is only supported in W3C mode'); } $this->executor->execute(DriverCommand::FULLSCREEN_WINDOW, []); return $this; } public function setSize(WebDriverDimension $size) { $params = [ 'width' => $size->getWidth(), 'height' => $size->getHeight(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_SIZE, $params); return $this; } public function setPosition(WebDriverPoint $position) { $params = [ 'x' => $position->getX(), 'y' => $position->getY(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_POSITION, $params); return $this; } public function getScreenOrientation() { return $this->executor->execute(DriverCommand::GET_SCREEN_ORIENTATION); } public function setScreenOrientation($orientation) { $orientation = mb_strtoupper($orientation); if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'], true)) { throw new IndexOutOfBoundsException( 'Orientation must be either PORTRAIT, or LANDSCAPE' ); } $this->executor->execute( DriverCommand::SET_SCREEN_ORIENTATION, ['orientation' => $orientation] ); return $this; } } toArray(); } public function setBinary($path) { $this->binary = $path; return $this; } public function addArguments(array $arguments) { $this->arguments = array_merge($this->arguments, $arguments); return $this; } public function addExtensions(array $paths) { foreach ($paths as $path) { $this->addExtension($path); } return $this; } public function addEncodedExtensions(array $encoded_extensions) { foreach ($encoded_extensions as $encoded_extension) { $this->addEncodedExtension($encoded_extension); } return $this; } public function setExperimentalOption($name, $value) { $this->experimentalOptions[$name] = $value; return $this; } public function toCapabilities() { $capabilities = DesiredCapabilities::chrome(); $capabilities->setCapability(self::CAPABILITY, $this); return $capabilities; } public function toArray() { $options = new \ArrayObject($this->experimentalOptions); if (!empty($this->binary)) { $options['binary'] = $this->binary; } if (!empty($this->arguments)) { $options['args'] = $this->arguments; } if (!empty($this->extensions)) { $options['extensions'] = $this->extensions; } return $options; } private function addExtension($path) { $this->addEncodedExtension(base64_encode(file_get_contents($path))); return $this; } private function addEncodedExtension($encoded_extension) { $this->extensions[] = $encoded_extension; return $this; } } 'POST', 'url' => '/session/:sessionId/goog/cdp/execute', ]; private $driver; public function __construct(RemoteWebDriver $driver) { $this->driver = $driver; } public function execute($command, array $parameters = []) { $params = ['cmd' => $command, 'params' => (object) $parameters]; return $this->driver->executeCustomCommand( self::SEND_COMMAND['url'], self::SEND_COMMAND['method'], $params ); } } [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], ], 'desiredCapabilities' => (object) $capabilities->toArray(), ] ); $response = $executor->execute($newSessionCommand); return static::createFromResponse($response, $executor); } public function startSession(DesiredCapabilities $desired_capabilities) { $command = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], ], 'desiredCapabilities' => (object) $desired_capabilities->toArray(), ] ); $response = $this->executor->execute($command); $value = $response->getValue(); if (!$this->isW3cCompliant = isset($value['capabilities'])) { $this->executor->disableW3cCompliance(); } $this->sessionID = $response->getSessionID(); } public function getDevTools() { if ($this->devTools === null) { $this->devTools = new ChromeDevToolsDriver($this); } return $this->devTools; } } dispatcher = $dispatcher ?: new WebDriverDispatcher(); if (!$this->dispatcher->getDefaultDriver()) { $this->dispatcher->setDefaultDriver($this); } $this->driver = $driver; } public function getDispatcher() { return $this->dispatcher; } public function getWebDriver() { return $this->driver; } public function get($url) { $this->dispatch('beforeNavigateTo', $url, $this); try { $this->driver->get($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterNavigateTo', $url, $this); return $this; } public function findElements(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); $elements = []; try { foreach ($this->driver->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $elements; } public function findElement(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); try { $element = $this->newElement($this->driver->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $element; } public function executeScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } public function executeAsyncScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeAsyncScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } public function close() { try { $this->driver->close(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCurrentURL() { try { return $this->driver->getCurrentURL(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getPageSource() { try { return $this->driver->getPageSource(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTitle() { try { return $this->driver->getTitle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getWindowHandle() { try { return $this->driver->getWindowHandle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getWindowHandles() { try { return $this->driver->getWindowHandles(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function quit() { try { $this->driver->quit(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function takeScreenshot($save_as = null) { try { return $this->driver->takeScreenshot($save_as); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { try { return $this->driver->wait($timeout_in_second, $interval_in_millisecond); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function manage() { try { return $this->driver->manage(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function navigate() { try { return new EventFiringWebDriverNavigation( $this->driver->navigate(), $this->getDispatcher() ); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function switchTo() { try { return $this->driver->switchTo(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTouch() { try { return $this->driver->getTouch(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function execute($name, $params) { try { return $this->driver->execute($name, $params); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } protected function newElement(WebDriverElement $element) { return new EventFiringWebElement($element, $this->getDispatcher()); } protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch('onException', $exception, $this); } } element = $element; $this->dispatcher = $dispatcher; } public function getDispatcher() { return $this->dispatcher; } public function getElement() { return $this->element; } public function sendKeys($value) { $this->dispatch('beforeChangeValueOf', $this); try { $this->element->sendKeys($value); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterChangeValueOf', $this); return $this; } public function click() { $this->dispatch('beforeClickOn', $this); try { $this->element->click(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterClickOn', $this); return $this; } public function findElement(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $element = $this->newElement($this->element->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $element; } public function findElements(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $elements = []; foreach ($this->element->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $elements; } public function clear() { try { $this->element->clear(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getAttribute($attribute_name) { try { return $this->element->getAttribute($attribute_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCSSValue($css_property_name) { try { return $this->element->getCSSValue($css_property_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getLocation() { try { return $this->element->getLocation(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getLocationOnScreenOnceScrolledIntoView() { try { return $this->element->getLocationOnScreenOnceScrolledIntoView(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCoordinates() { try { return $this->element->getCoordinates(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getSize() { try { return $this->element->getSize(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTagName() { try { return $this->element->getTagName(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getText() { try { return $this->element->getText(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isDisplayed() { try { return $this->element->isDisplayed(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isEnabled() { try { return $this->element->isEnabled(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isSelected() { try { return $this->element->isSelected(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function submit() { try { $this->element->submit(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getID() { try { return $this->element->getID(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function equals(WebDriverElement $other) { try { return $this->element->equals($other); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch( 'onException', $exception, $this->dispatcher->getDefaultDriver() ); } protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } protected function newElement(WebDriverElement $element) { return new static($element, $this->getDispatcher()); } } navigator = $navigator; $this->dispatcher = $dispatcher; } public function getDispatcher() { return $this->dispatcher; } public function getNavigator() { return $this->navigator; } public function back() { $this->dispatch( 'beforeNavigateBack', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->back(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateBack', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function forward() { $this->dispatch( 'beforeNavigateForward', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->forward(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateForward', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function refresh() { try { $this->navigator->refresh(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function to($url) { $this->dispatch( 'beforeNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->to($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); return $this; } protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch('onException', $exception); } } executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } public function implicitlyWait($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['implicit' => $seconds * 1000] ); return $this; } $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['ms' => $seconds * 1000] ); return $this; } public function setScriptTimeout($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['script' => $seconds * 1000] ); return $this; } $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['ms' => $seconds * 1000] ); return $this; } public function pageLoadTimeout($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['pageLoad' => $seconds * 1000] ); return $this; } $this->executor->execute(DriverCommand::SET_TIMEOUT, [ 'type' => 'page load', 'ms' => $seconds * 1000, ]); return $this; } } width = $width; $this->height = $height; } public function getHeight() { return (int) $this->height; } public function getWidth() { return (int) $this->width; } public function equals(self $dimension) { return $this->height === $dimension->getHeight() && $this->width === $dimension->getWidth(); } } driver = $driver; return $this; } public function getDefaultDriver() { return $this->driver; } public function register(WebDriverEventListener $listener) { $this->listeners[] = $listener; return $this; } public function unregister(WebDriverEventListener $listener) { $key = array_search($listener, $this->listeners, true); if ($key !== false) { unset($this->listeners[$key]); } return $this; } public function dispatch($method, $arguments) { foreach ($this->listeners as $listener) { call_user_func_array([$listener, $method], $arguments); } return $this; } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->up($this->x, $this->y); } } getMessage(), $this->getCode(), $previous); } } results = $results; } public function getResults() { return $this->results; } public static function throwException($status_code, $message, $results) { if (is_string($status_code)) { switch ($status_code) { case 'element click intercepted': throw new ElementClickInterceptedException($message, $results); case 'element not interactable': throw new ElementNotInteractableException($message, $results); case 'insecure certificate': throw new InsecureCertificateException($message, $results); case 'invalid argument': throw new InvalidArgumentException($message, $results); case 'invalid cookie domain': throw new InvalidCookieDomainException($message, $results); case 'invalid element state': throw new InvalidElementStateException($message, $results); case 'invalid selector': throw new InvalidSelectorException($message, $results); case 'invalid session id': throw new InvalidSessionIdException($message, $results); case 'javascript error': throw new JavascriptErrorException($message, $results); case 'move target out of bounds': throw new MoveTargetOutOfBoundsException($message, $results); case 'no such alert': throw new NoSuchAlertException($message, $results); case 'no such cookie': throw new NoSuchCookieException($message, $results); case 'no such element': throw new NoSuchElementException($message, $results); case 'no such frame': throw new NoSuchFrameException($message, $results); case 'no such window': throw new NoSuchWindowException($message, $results); case 'script timeout': throw new ScriptTimeoutException($message, $results); case 'session not created': throw new SessionNotCreatedException($message, $results); case 'stale element reference': throw new StaleElementReferenceException($message, $results); case 'timeout': throw new TimeoutException($message, $results); case 'unable to set cookie': throw new UnableToSetCookieException($message, $results); case 'unable to capture screen': throw new UnableToCaptureScreenException($message, $results); case 'unexpected alert open': throw new UnexpectedAlertOpenException($message, $results); case 'unknown command': throw new UnknownCommandException($message, $results); case 'unknown error': throw new UnknownErrorException($message, $results); case 'unknown method': throw new UnknownMethodException($message, $results); case 'unsupported operation': throw new UnsupportedOperationException($message, $results); default: throw new UnrecognizedExceptionException($message, $results); } } switch ($status_code) { case 1: throw new IndexOutOfBoundsException($message, $results); case 2: throw new NoCollectionException($message, $results); case 3: throw new NoStringException($message, $results); case 4: throw new NoStringLengthException($message, $results); case 5: throw new NoStringWrapperException($message, $results); case 6: throw new NoSuchDriverException($message, $results); case 7: throw new NoSuchElementException($message, $results); case 8: throw new NoSuchFrameException($message, $results); case 9: throw new UnknownCommandException($message, $results); case 10: throw new StaleElementReferenceException($message, $results); case 11: throw new ElementNotVisibleException($message, $results); case 12: throw new InvalidElementStateException($message, $results); case 13: throw new UnknownServerException($message, $results); case 14: throw new ExpectedException($message, $results); case 15: throw new ElementNotSelectableException($message, $results); case 16: throw new NoSuchDocumentException($message, $results); case 17: throw new UnexpectedJavascriptException($message, $results); case 18: throw new NoScriptResultException($message, $results); case 19: throw new XPathLookupException($message, $results); case 20: throw new NoSuchCollectionException($message, $results); case 21: throw new TimeoutException($message, $results); case 22: throw new NullPointerException($message, $results); case 23: throw new NoSuchWindowException($message, $results); case 24: throw new InvalidCookieDomainException($message, $results); case 25: throw new UnableToSetCookieException($message, $results); case 26: throw new UnexpectedAlertOpenException($message, $results); case 27: throw new NoAlertOpenException($message, $results); case 28: throw new ScriptTimeoutException($message, $results); case 29: throw new InvalidCoordinatesException($message, $results); case 30: throw new IMENotAvailableException($message, $results); case 31: throw new IMEEngineActivationFailedException($message, $results); case 32: throw new InvalidSelectorException($message, $results); case 33: throw new SessionNotCreatedException($message, $results); case 34: throw new MoveTargetOutOfBoundsException($message, $results); default: throw new UnrecognizedExceptionException($message, $results); } } } type = $element->getAttribute('type'); if ($this->type !== 'checkbox') { throw new WebDriverException('The input must be of type "checkbox".'); } } public function isMultiple() { return true; } public function deselectAll() { foreach ($this->getRelatedElements() as $checkbox) { $this->deselectOption($checkbox); } } public function deselectByIndex($index) { $this->byIndex($index, false); } public function deselectByValue($value) { $this->byValue($value, false); } public function deselectByVisibleText($text) { $this->byVisibleText($text, false, false); } public function deselectByVisiblePartialText($text) { $this->byVisibleText($text, true, false); } } [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], ], ] ); $response = $executor->execute($newSessionCommand); $returnedCapabilities = DesiredCapabilities::createFromW3cCapabilities($response->getValue()['capabilities']); $sessionId = $response->getSessionID(); return new static($executor, $sessionId, $returnedCapabilities, true); } } setPreference(FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED, false); $this->setPreference(FirefoxPreferences::DEVTOOLS_JSONVIEW, false); } public function setOption($name, $value) { if ($name === self::OPTION_PREFS) { throw new \InvalidArgumentException('Use setPreference() method to set Firefox preferences'); } if ($name === self::OPTION_ARGS) { throw new \InvalidArgumentException('Use addArguments() method to add Firefox arguments'); } $this->options[$name] = $value; return $this; } public function addArguments(array $arguments) { $this->arguments = array_merge($this->arguments, $arguments); return $this; } public function setPreference($name, $value) { $this->preferences[$name] = $value; return $this; } public function toArray() { $array = $this->options; if (!empty($this->arguments)) { $array[self::OPTION_ARGS] = $this->arguments; } if (!empty($this->preferences)) { $array[self::OPTION_PREFS] = $this->preferences; } return $array; } #[ReturnTypeWillChange] public function jsonSerialize() { return new \ArrayObject($this->toArray()); } } extensions[] = $extension; return $this; } public function addExtensionDatas($extension_datas) { if (!is_dir($extension_datas)) { return null; } $this->extensions_datas[basename($extension_datas)] = $extension_datas; return $this; } public function setRdfFile($rdf_file) { if (!is_file($rdf_file)) { return null; } $this->rdf_file = $rdf_file; return $this; } public function setPreference($key, $value) { if (is_string($value)) { $value = sprintf('"%s"', $value); } else { if (is_int($value)) { $value = sprintf('%d', $value); } else { if (is_bool($value)) { $value = $value ? 'true' : 'false'; } else { throw new WebDriverException( 'The value of the preference should be either a string, int or bool.' ); } } } $this->preferences[$key] = $value; return $this; } public function getPreference($key) { if (array_key_exists($key, $this->preferences)) { return $this->preferences[$key]; } return null; } public function encode() { $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfile'); if (isset($this->rdf_file)) { copy($this->rdf_file, $temp_dir . DIRECTORY_SEPARATOR . 'mimeTypes.rdf'); } foreach ($this->extensions as $extension) { $this->installExtension($extension, $temp_dir); } foreach ($this->extensions_datas as $dirname => $extension_datas) { mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($extension_datas, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { $target_dir = $temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ($item->isDir()) { mkdir($target_dir); } else { copy($item, $target_dir); } } } $content = ''; foreach ($this->preferences as $key => $value) { $content .= sprintf("user_pref(\"%s\", %s);\n", $key, $value); } file_put_contents($temp_dir . '/user.js', $content); $temp_zip = sys_get_temp_dir() . '/' . uniqid('WebDriverFirefoxProfileZip', false); $zip = new ZipArchive(); $zip->open($temp_zip, ZipArchive::CREATE); $dir = new RecursiveDirectoryIterator($temp_dir); $files = new RecursiveIteratorIterator($dir); $dir_prefix = preg_replace( '#\\\\#', '\\\\\\\\', $temp_dir . DIRECTORY_SEPARATOR ); foreach ($files as $name => $object) { if (is_dir($name)) { continue; } $path = preg_replace("#^{$dir_prefix}#", '', $name); $zip->addFile($name, $path); } $zip->close(); $profile = base64_encode(file_get_contents($temp_zip)); $this->deleteDirectory($temp_dir); unlink($temp_zip); return $profile; } private function installExtension($extension, $profile_dir) { $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfileExtension'); $this->extractTo($extension, $temp_dir); $install_rdf_path = $temp_dir . '/install.rdf'; $xml = simplexml_load_file($install_rdf_path); $ns = $xml->getDocNamespaces(); $prefix = ''; if (!empty($ns)) { foreach ($ns as $key => $value) { if (mb_strpos($value, '//www.mozilla.org/2004/em-rdf') > 0) { if ($key != '') { $prefix = $key . ':'; } break; } } } $matches = []; preg_match('#<' . $prefix . 'id>([^<]+)#', $xml->asXML(), $matches); if (isset($matches[1])) { $ext_dir = $profile_dir . '/extensions/' . $matches[1]; mkdir($ext_dir, 0777, true); $this->extractTo($extension, $ext_dir); } else { $this->deleteDirectory($temp_dir); throw new WebDriverException('Cannot get the extension id from the install manifest.'); } $this->deleteDirectory($temp_dir); return $ext_dir; } private function createTempDirectory($prefix = '') { $temp_dir = tempnam(sys_get_temp_dir(), $prefix); if (file_exists($temp_dir)) { unlink($temp_dir); mkdir($temp_dir); if (!is_dir($temp_dir)) { throw new WebDriverException('Cannot create firefox profile.'); } } return $temp_dir; } private function deleteDirectory($directory) { $dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); $paths = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST); foreach ($paths as $path) { if ($path->isDir() && !$path->isLink()) { rmdir($path->getPathname()); } else { unlink($path->getPathname()); } } rmdir($directory); } private function extractTo($xpi, $target_dir) { $zip = new ZipArchive(); if (file_exists($xpi)) { if ($zip->open($xpi)) { $zip->extractTo($target_dir); $zip->close(); } else { throw new \Exception("Failed to open the firefox extension. '$xpi'"); } } else { throw new \Exception("Firefox extension doesn't exist. '$xpi'"); } return $this; } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->scroll($this->x, $this->y); } } touchScreen->doubleTap($this->locationProvider); } } touchScreen->tap($this->locationProvider); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->down($this->x, $this->y); } } touchScreen->longPress($this->locationProvider); } } x = $x; $this->y = $y; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->scrollFromElement( $this->locationProvider, $this->x, $this->y ); } } x = $x; $this->y = $y; $this->speed = $speed; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->flickFromElement( $this->locationProvider, $this->x, $this->y, $this->speed ); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->flick($this->x, $this->y); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->move($this->x, $this->y); } } touchScreen = $touch_screen; $this->locationProvider = $location_provider; } protected function getActionLocation() { return $this->locationProvider !== null ? $this->locationProvider->getCoordinates() : null; } } driver = $driver; $this->keyboard = $driver->getKeyboard(); $this->mouse = $driver->getMouse(); $this->action = new WebDriverCompositeAction(); } public function perform() { $this->action->perform(); } public function click(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAction($this->mouse, $element) ); return $this; } public function clickAndHold(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $element) ); return $this; } public function contextClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverContextClickAction($this->mouse, $element) ); return $this; } public function doubleClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverDoubleClickAction($this->mouse, $element) ); return $this; } public function dragAndDrop(WebDriverElement $source, WebDriverElement $target) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMouseMoveAction($this->mouse, $target) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $target) ); return $this; } public function dragAndDropBy(WebDriverElement $source, $x_offset, $y_offset) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, null) ); return $this; } public function moveByOffset($x_offset, $y_offset) { $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); return $this; } public function moveToElement(WebDriverElement $element, $x_offset = null, $y_offset = null) { $this->action->addAction(new WebDriverMoveToOffsetAction( $this->mouse, $element, $x_offset, $y_offset )); return $this; } public function release(WebDriverElement $element = null) { $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $element) ); return $this; } public function keyDown(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyDownAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } public function keyUp(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyUpAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } public function sendKeys(WebDriverElement $element = null, $keys = null) { $this->action->addAction( new WebDriverSendKeysAction( $this->keyboard, $this->mouse, $element, $keys ) ); return $this; } } touchScreen = $driver->getTouch(); } public function tap(WebDriverElement $element) { $this->action->addAction( new WebDriverTapAction($this->touchScreen, $element) ); return $this; } public function down($x, $y) { $this->action->addAction( new WebDriverDownAction($this->touchScreen, $x, $y) ); return $this; } public function up($x, $y) { $this->action->addAction( new WebDriverUpAction($this->touchScreen, $x, $y) ); return $this; } public function move($x, $y) { $this->action->addAction( new WebDriverMoveAction($this->touchScreen, $x, $y) ); return $this; } public function scroll($x, $y) { $this->action->addAction( new WebDriverScrollAction($this->touchScreen, $x, $y) ); return $this; } public function scrollFromElement(WebDriverElement $element, $x, $y) { $this->action->addAction( new WebDriverScrollFromElementAction($this->touchScreen, $element, $x, $y) ); return $this; } public function doubleTap(WebDriverElement $element) { $this->action->addAction( new WebDriverDoubleTapAction($this->touchScreen, $element) ); return $this; } public function longPress(WebDriverElement $element) { $this->action->addAction( new WebDriverLongPressAction($this->touchScreen, $element) ); return $this; } public function flick($x, $y) { $this->action->addAction( new WebDriverFlickAction($this->touchScreen, $x, $y) ); return $this; } public function flickFromElement(WebDriverElement $element, $x, $y, $speed) { $this->action->addAction( new WebDriverFlickFromElementAction( $this->touchScreen, $element, $x, $y, $speed ) ); return $this; } } focusOnElement(); $this->keyboard->releaseKey($this->key); } } keys = $keys; } public function perform() { $this->focusOnElement(); $this->keyboard->sendKeys($this->keys); } } mouse->mouseDown($this->getActionLocation()); } } mouse->click($this->getActionLocation()); } } xOffset = $x_offset; $this->yOffset = $y_offset; } public function perform() { $this->mouse->mouseMove( $this->getActionLocation(), $this->xOffset, $this->yOffset ); } } mouse->doubleClick($this->getActionLocation()); } } mouse->mouseMove($this->getActionLocation()); } } keyboard = $keyboard; $this->mouse = $mouse; $this->locationProvider = $location_provider; } protected function focusOnElement() { if ($this->locationProvider) { $this->mouse->click($this->locationProvider->getCoordinates()); } } } mouse->contextClick($this->getActionLocation()); } } mouse = $mouse; $this->locationProvider = $location_provider; } protected function getActionLocation() { if ($this->locationProvider !== null) { return $this->locationProvider->getCoordinates(); } return null; } protected function moveToLocation() { $this->mouse->mouseMove($this->locationProvider); } } focusOnElement(); $this->keyboard->pressKey($this->key); } } mouse->mouseUp($this->getActionLocation()); } } key = $key; } } onScreen = $on_screen; $this->inViewPort = $in_view_port; $this->onPage = $on_page; $this->auxiliary = $auxiliary; } public function onScreen() { throw new UnsupportedOperationException( 'onScreen is planned but not yet supported by Selenium' ); } public function inViewPort() { return call_user_func($this->inViewPort); } public function onPage() { return call_user_func($this->onPage); } public function getAuxiliary() { return $this->auxiliary; } } actions[] = $action; return $this; } public function getNumberOfActions() { return count($this->actions); } public function perform() { foreach ($this->actions as $action) { $action->perform(); } } } options = $this->createOptions($options ?: array()); $this->connection = $this->createConnection($parameters ?: array()); $this->profile = $this->options->profile; } protected function createOptions($options) { if (is_array($options)) { return new Options($options); } if ($options instanceof OptionsInterface) { return $options; } throw new \InvalidArgumentException('Invalid type for client options.'); } protected function createConnection($parameters) { if ($parameters instanceof ConnectionInterface) { return $parameters; } if ($parameters instanceof ParametersInterface || is_string($parameters)) { return $this->options->connections->create($parameters); } if (is_array($parameters)) { if (!isset($parameters[0])) { return $this->options->connections->create($parameters); } $options = $this->options; if ($options->defined('aggregate')) { $initializer = $this->getConnectionInitializerWrapper($options->aggregate); $connection = $initializer($parameters, $options); } elseif ($options->defined('replication')) { $replication = $options->replication; if ($replication instanceof AggregateConnectionInterface) { $connection = $replication; $options->connections->aggregate($connection, $parameters); } else { $initializer = $this->getConnectionInitializerWrapper($replication); $connection = $initializer($parameters, $options); } } else { $connection = $options->cluster; $options->connections->aggregate($connection, $parameters); } return $connection; } if (is_callable($parameters)) { $initializer = $this->getConnectionInitializerWrapper($parameters); $connection = $initializer($this->options); return $connection; } throw new \InvalidArgumentException('Invalid type for connection parameters.'); } protected function getConnectionInitializerWrapper($callable) { return function () use ($callable) { $connection = call_user_func_array($callable, func_get_args()); if (!$connection instanceof ConnectionInterface) { throw new \UnexpectedValueException( 'The callable connection initializer returned an invalid type.' ); } return $connection; }; } public function getProfile() { return $this->profile; } public function getOptions() { return $this->options; } public function getClientFor($connectionID) { if (!$connection = $this->getConnectionById($connectionID)) { throw new \InvalidArgumentException("Invalid connection ID: $connectionID."); } return new static($connection, $this->options); } public function connect() { $this->connection->connect(); } public function disconnect() { $this->connection->disconnect(); } public function quit() { $this->disconnect(); } public function isConnected() { return $this->connection->isConnected(); } public function getConnection() { return $this->connection; } public function getConnectionById($connectionID) { if (!$this->connection instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Retrieving connections by ID is supported only by aggregate connections.' ); } return $this->connection->getConnectionById($connectionID); } public function executeRaw(array $arguments, &$error = null) { $error = false; $response = $this->connection->executeCommand( new RawCommand($arguments) ); if ($response instanceof ResponseInterface) { if ($response instanceof ErrorResponseInterface) { $error = true; } return (string) $response; } return $response; } public function __call($commandID, $arguments) { return $this->executeCommand( $this->createCommand($commandID, $arguments) ); } public function createCommand($commandID, $arguments = array()) { return $this->profile->createCommand($commandID, $arguments); } public function executeCommand(CommandInterface $command) { $response = $this->connection->executeCommand($command); if ($response instanceof ResponseInterface) { if ($response instanceof ErrorResponseInterface) { $response = $this->onErrorResponse($command, $response); } return $response; } return $command->parseResponse($response); } protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response) { if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') { $eval = $this->createCommand('EVAL'); $eval->setRawArguments($command->getEvalArguments()); $response = $this->executeCommand($eval); if (!$response instanceof ResponseInterface) { $response = $command->parseResponse($response); } return $response; } if ($this->options->exceptions) { throw new ServerException($response->getMessage()); } return $response; } private function sharedContextFactory($initializer, $argv = null) { switch (count($argv)) { case 0: return $this->$initializer(); case 1: return is_array($argv[0]) ? $this->$initializer($argv[0]) : $this->$initializer(null, $argv[0]); case 2: list($arg0, $arg1) = $argv; return $this->$initializer($arg0, $arg1); default: return $this->$initializer($this, $argv); } } public function pipeline() { return $this->sharedContextFactory('createPipeline', func_get_args()); } protected function createPipeline(array $options = null, $callable = null) { if (isset($options['atomic']) && $options['atomic']) { $class = 'Predis\Pipeline\Atomic'; } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { $class = 'Predis\Pipeline\FireAndForget'; } else { $class = 'Predis\Pipeline\Pipeline'; } $pipeline = new $class($this); if (isset($callable)) { return $pipeline->execute($callable); } return $pipeline; } public function transaction() { return $this->sharedContextFactory('createTransaction', func_get_args()); } protected function createTransaction(array $options = null, $callable = null) { $transaction = new MultiExecTransaction($this, $options); if (isset($callable)) { return $transaction->execute($callable); } return $transaction; } public function pubSubLoop() { return $this->sharedContextFactory('createPubSub', func_get_args()); } protected function createPubSub(array $options = null, $callable = null) { $pubsub = new PubSubConsumer($this, $options); if (!isset($callable)) { return $pubsub; } foreach ($pubsub as $message) { if (call_user_func($callable, $pubsub, $message) === false) { $pubsub->stop(); } } } public function monitor() { return new MonitorConsumer($this); } #[\ReturnTypeWillChange] public function getIterator() { $clients = array(); $connection = $this->getConnection(); if (!$connection instanceof \Traversable) { return new \ArrayIterator(array( (string) $connection => new static($connection, $this->getOptions()) )); } foreach ($connection as $node) { $clients[(string) $node] = new static($node, $this->getOptions()); } return new \ArrayIterator($clients); } } disallowed = $this->getDisallowedOperations(); $this->readonly = $this->getReadOnlyOperations(); $this->readonlySHA1 = array(); } public function isReadOperation(CommandInterface $command) { if (isset($this->disallowed[$id = $command->getId()])) { throw new NotSupportedException( "The command '$id' is not allowed in replication mode." ); } if (isset($this->readonly[$id])) { if (true === $readonly = $this->readonly[$id]) { return true; } return call_user_func($readonly, $command); } if (($eval = $id === 'EVAL') || $id === 'EVALSHA') { $argument = $command->getArgument(0); $sha1 = $eval ? sha1(strval($argument)) : $argument; if (isset($this->readonlySHA1[$sha1])) { if (true === $readonly = $this->readonlySHA1[$sha1]) { return true; } return call_user_func($readonly, $command); } } return false; } public function isDisallowedOperation(CommandInterface $command) { return isset($this->disallowed[$command->getId()]); } protected function isBitfieldReadOnly(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); if ($argc >= 2) { for ($i = 1; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'SET' || $argument === 'INCRBY') { return false; } } } return true; } protected function isGeoradiusReadOnly(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if ($argc > $startIndex) { for ($i = $startIndex; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'STORE' || $argument === 'STOREDIST') { return false; } } } return true; } public function setCommandReadOnly($commandID, $readonly = true) { $commandID = strtoupper($commandID); if ($readonly) { $this->readonly[$commandID] = $readonly; } else { unset($this->readonly[$commandID]); } } public function setScriptReadOnly($script, $readonly = true) { $sha1 = sha1($script); if ($readonly) { $this->readonlySHA1[$sha1] = $readonly; } else { unset($this->readonlySHA1[$sha1]); } } protected function getDisallowedOperations() { return array( 'SHUTDOWN' => true, 'INFO' => true, 'DBSIZE' => true, 'LASTSAVE' => true, 'CONFIG' => true, 'MONITOR' => true, 'SLAVEOF' => true, 'SAVE' => true, 'BGSAVE' => true, 'BGREWRITEAOF' => true, 'SLOWLOG' => true, ); } protected function getReadOnlyOperations() { return array( 'EXISTS' => true, 'TYPE' => true, 'KEYS' => true, 'SCAN' => true, 'RANDOMKEY' => true, 'TTL' => true, 'GET' => true, 'MGET' => true, 'SUBSTR' => true, 'STRLEN' => true, 'GETRANGE' => true, 'GETBIT' => true, 'LLEN' => true, 'LRANGE' => true, 'LINDEX' => true, 'SCARD' => true, 'SISMEMBER' => true, 'SINTER' => true, 'SUNION' => true, 'SDIFF' => true, 'SMEMBERS' => true, 'SSCAN' => true, 'SRANDMEMBER' => true, 'ZRANGE' => true, 'ZREVRANGE' => true, 'ZRANGEBYSCORE' => true, 'ZREVRANGEBYSCORE' => true, 'ZCARD' => true, 'ZSCORE' => true, 'ZCOUNT' => true, 'ZRANK' => true, 'ZREVRANK' => true, 'ZSCAN' => true, 'ZLEXCOUNT' => true, 'ZRANGEBYLEX' => true, 'ZREVRANGEBYLEX' => true, 'HGET' => true, 'HMGET' => true, 'HEXISTS' => true, 'HLEN' => true, 'HKEYS' => true, 'HVALS' => true, 'HGETALL' => true, 'HSCAN' => true, 'HSTRLEN' => true, 'PING' => true, 'AUTH' => true, 'SELECT' => true, 'ECHO' => true, 'QUIT' => true, 'OBJECT' => true, 'BITCOUNT' => true, 'BITPOS' => true, 'TIME' => true, 'PFCOUNT' => true, 'BITFIELD' => array($this, 'isBitfieldReadOnly'), 'GEOHASH' => true, 'GEOPOS' => true, 'GEODIST' => true, 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'), 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'), ); } } client = $client; if (isset($options['gc_maxlifetime'])) { $this->ttl = (int) $options['gc_maxlifetime']; } else { $this->ttl = ini_get('session.gc_maxlifetime'); } } public function register() { if (PHP_VERSION_ID >= 50400) { session_set_save_handler($this, true); } else { session_set_save_handler( array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc') ); } } #[\ReturnTypeWillChange] public function open($save_path, $session_id) { return true; } #[\ReturnTypeWillChange] public function close() { return true; } #[\ReturnTypeWillChange] public function gc($maxlifetime) { return true; } #[\ReturnTypeWillChange] public function read($session_id) { if ($data = $this->client->get($session_id)) { return $data; } return ''; } #[\ReturnTypeWillChange] public function write($session_id, $session_data) { $this->client->setex($session_id, $this->ttl, $session_data); return true; } #[\ReturnTypeWillChange] public function destroy($session_id) { $this->client->del($session_id); return true; } public function getClient() { return $this->client; } public function getMaxLifeTime() { return $this->ttl; } } assertClient($client); $this->client = $client; $this->state = new MultiExecState(); $this->configure($client, $options ?: array()); $this->reset(); } private function assertClient(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.' ); } if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) { throw new NotSupportedException( 'The current profile does not support MULTI, EXEC and DISCARD.' ); } } protected function configure(ClientInterface $client, array $options) { if (isset($options['exceptions'])) { $this->exceptions = (bool) $options['exceptions']; } else { $this->exceptions = $client->getOptions()->exceptions; } if (isset($options['cas'])) { $this->modeCAS = (bool) $options['cas']; } if (isset($options['watch']) && $keys = $options['watch']) { $this->watchKeys = $keys; } if (isset($options['retry'])) { $this->attempts = (int) $options['retry']; } } protected function reset() { $this->state->reset(); $this->commands = new \SplQueue(); } protected function initialize() { if ($this->state->isInitialized()) { return; } if ($this->modeCAS) { $this->state->flag(MultiExecState::CAS); } if ($this->watchKeys) { $this->watch($this->watchKeys); } $cas = $this->state->isCAS(); $discarded = $this->state->isDiscarded(); if (!$cas || ($cas && $discarded)) { $this->call('MULTI'); if ($discarded) { $this->state->unflag(MultiExecState::CAS); } } $this->state->unflag(MultiExecState::DISCARDED); $this->state->flag(MultiExecState::INITIALIZED); } public function __call($method, $arguments) { return $this->executeCommand( $this->client->createCommand($method, $arguments) ); } protected function call($commandID, array $arguments = array()) { $response = $this->client->executeCommand( $this->client->createCommand($commandID, $arguments) ); if ($response instanceof ErrorResponseInterface) { throw new ServerException($response->getMessage()); } return $response; } public function executeCommand(CommandInterface $command) { $this->initialize(); if ($this->state->isCAS()) { return $this->client->executeCommand($command); } $response = $this->client->getConnection()->executeCommand($command); if ($response instanceof StatusResponse && $response == 'QUEUED') { $this->commands->enqueue($command); } elseif ($response instanceof ErrorResponseInterface) { throw new AbortedMultiExecException($this, $response->getMessage()); } else { $this->onProtocolError('The server did not return a +QUEUED status response.'); } return $this; } public function watch($keys) { if (!$this->client->getProfile()->supportsCommand('WATCH')) { throw new NotSupportedException('WATCH is not supported by the current profile.'); } if ($this->state->isWatchAllowed()) { throw new ClientException('Sending WATCH after MULTI is not allowed.'); } $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys)); $this->state->flag(MultiExecState::WATCH); return $response; } public function multi() { if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) { $this->state->unflag(MultiExecState::CAS); $this->call('MULTI'); } else { $this->initialize(); } return $this; } public function unwatch() { if (!$this->client->getProfile()->supportsCommand('UNWATCH')) { throw new NotSupportedException( 'UNWATCH is not supported by the current profile.' ); } $this->state->unflag(MultiExecState::WATCH); $this->__call('UNWATCH', array()); return $this; } public function discard() { if ($this->state->isInitialized()) { $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD'); $this->reset(); $this->state->flag(MultiExecState::DISCARDED); } return $this; } public function exec() { return $this->execute(); } private function checkBeforeExecution($callable) { if ($this->state->isExecuting()) { throw new ClientException( 'Cannot invoke "execute" or "exec" inside an active transaction context.' ); } if ($callable) { if (!is_callable($callable)) { throw new \InvalidArgumentException('The argument must be a callable object.'); } if (!$this->commands->isEmpty()) { $this->discard(); throw new ClientException( 'Cannot execute a transaction block after using fluent interface.' ); } } elseif ($this->attempts) { $this->discard(); throw new ClientException( 'Automatic retries are supported only when a callable block is provided.' ); } } public function execute($callable = null) { $this->checkBeforeExecution($callable); $execResponse = null; $attempts = $this->attempts; do { if ($callable) { $this->executeTransactionBlock($callable); } if ($this->commands->isEmpty()) { if ($this->state->isWatching()) { $this->discard(); } return; } $execResponse = $this->call('EXEC'); if ($execResponse === null) { if ($attempts === 0) { throw new AbortedMultiExecException( $this, 'The current transaction has been aborted by the server.' ); } $this->reset(); continue; } break; } while ($attempts-- > 0); $response = array(); $commands = $this->commands; $size = count($execResponse); if ($size !== count($commands)) { $this->onProtocolError('EXEC returned an unexpected number of response items.'); } for ($i = 0; $i < $size; ++$i) { $cmdResponse = $execResponse[$i]; if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) { throw new ServerException($cmdResponse->getMessage()); } $response[$i] = $commands->dequeue()->parseResponse($cmdResponse); } return $response; } protected function executeTransactionBlock($callable) { $exception = null; $this->state->flag(MultiExecState::INSIDEBLOCK); try { call_user_func($callable, $this); } catch (CommunicationException $exception) { } catch (ServerException $exception) { } catch (\Exception $exception) { $this->discard(); } $this->state->unflag(MultiExecState::INSIDEBLOCK); if ($exception) { throw $exception; } } private function onProtocolError($message) { CommunicationException::handle(new ProtocolException( $this->client->getConnection(), $message )); } } transaction = $transaction; } public function getTransaction() { return $this->transaction; } } flags = 0; } public function set($flags) { $this->flags = $flags; } public function get() { return $this->flags; } public function flag($flags) { $this->flags |= $flags; } public function unflag($flags) { $this->flags &= ~$flags; } public function check($flags) { return ($this->flags & $flags) === $flags; } public function reset() { $this->flags = 0; } public function isReset() { return $this->flags === 0; } public function isInitialized() { return $this->check(self::INITIALIZED); } public function isExecuting() { return $this->check(self::INSIDEBLOCK); } public function isCAS() { return $this->check(self::CAS); } public function isWatchAllowed() { return $this->check(self::INITIALIZED) && !$this->check(self::CAS); } public function isWatching() { return $this->check(self::WATCH); } public function isDiscarded() { return $this->check(self::DISCARDED); } } input = $options; $this->options = array(); $this->handlers = $this->getHandlers(); } protected function getHandlers() { return array( 'cluster' => 'Predis\Configuration\ClusterOption', 'connections' => 'Predis\Configuration\ConnectionFactoryOption', 'exceptions' => 'Predis\Configuration\ExceptionsOption', 'prefix' => 'Predis\Configuration\PrefixOption', 'profile' => 'Predis\Configuration\ProfileOption', 'replication' => 'Predis\Configuration\ReplicationOption', ); } public function getDefault($option) { if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; $handler = new $handler(); return $handler->getDefault($this); } } public function defined($option) { return array_key_exists($option, $this->options) || array_key_exists($option, $this->input) ; } public function __isset($option) { return ( array_key_exists($option, $this->options) || array_key_exists($option, $this->input) ) && $this->__get($option) !== null; } public function __get($option) { if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { return $this->options[$option]; } if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { $value = $this->input[$option]; unset($this->input[$option]); if (is_object($value) && method_exists($value, '__invoke')) { $value = $value($this, $option); } if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; $handler = new $handler(); $value = $handler->filter($this, $value); } return $this->options[$option] = $value; } if (isset($this->handlers[$option])) { return $this->options[$option] = $this->getDefault($option); } return; } } connections); default: return; } } public function filter(OptionsInterface $options, $value) { if (is_string($value)) { $value = $this->createByDescription($options, $value); } if (!$value instanceof ClusterInterface) { throw new \InvalidArgumentException( "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected." ); } return $value; } public function getDefault(OptionsInterface $options) { return new PredisCluster(); } } service, $sentinels, $options->connections); }; } if ( !is_object($value) && null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ) { if (true === $asbool) { return $this->getDefault($options); } else { throw new \InvalidArgumentException( "Values evaluating to FALSE are not accepted for `replication`" ); } } throw new \InvalidArgumentException( "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected." ); } public function getDefault(OptionsInterface $options) { $replication = new MasterSlaveReplication(); if ($options->autodiscovery) { $replication->setConnectionFactory($options->connections); $replication->setAutoDiscovery(true); } return $replication; } } prefix) && $profile instanceof RedisProfile) { $profile->setProcessor($options->__get('prefix')); } } public function filter(OptionsInterface $options, $value) { if (is_string($value)) { $value = Factory::get($value); $this->setProcessors($options, $value); } elseif (!$value instanceof ProfileInterface) { throw new \InvalidArgumentException('Invalid value for the profile option.'); } return $value; } public function getDefault(OptionsInterface $options) { $profile = Factory::getDefault(); $this->setProcessors($options, $profile); return $profile; } } getDefault($options); foreach ($value as $scheme => $initializer) { $factory->define($scheme, $initializer); } return $factory; } else { throw new \InvalidArgumentException( 'Invalid value provided for the connections option.' ); } } public function getDefault(OptionsInterface $options) { $factory = new Factory(); if ($options->defined('parameters')) { $factory->setDefaultParameters($options->parameters); } return $factory; } } getClient()->getConnection(); } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { if ($connection instanceof NodeConnectionInterface) { return $this->executeSingleNode($connection, $commands); } elseif ($connection instanceof ClusterInterface) { return $this->executeCluster($connection, $commands); } else { $class = get_class($connection); throw new NotSupportedException("The connection class '$class' is not supported."); } } protected function executeSingleNode(NodeConnectionInterface $connection, \SplQueue $commands) { $responses = array(); $sizeOfPipe = count($commands); foreach ($commands as $command) { try { $connection->writeRequest($command); } catch (CommunicationException $exception) { return array_fill(0, $sizeOfPipe, $exception); } } for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); try { $responses[$i] = $connection->readResponse($command); } catch (CommunicationException $exception) { $add = count($commands) - count($responses); $responses = array_merge($responses, array_fill(0, $add, $exception)); break; } } return $responses; } protected function executeCluster(ClusterInterface $connection, \SplQueue $commands) { $responses = array(); $sizeOfPipe = count($commands); $exceptions = array(); foreach ($commands as $command) { $cmdConnection = $connection->getConnection($command); if (isset($exceptions[spl_object_hash($cmdConnection)])) { continue; } try { $cmdConnection->writeRequest($command); } catch (CommunicationException $exception) { $exceptions[spl_object_hash($cmdConnection)] = $exception; } } for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); $cmdConnection = $connection->getConnection($command); $connectionHash = spl_object_hash($cmdConnection); if (isset($exceptions[$connectionHash])) { $responses[$i] = $exceptions[$connectionHash]; continue; } try { $responses[$i] = $cmdConnection->readResponse($command); } catch (CommunicationException $exception) { $responses[$i] = $exception; $exceptions[$connectionHash] = $exception; } } return $responses; } } getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) { throw new ClientException( "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'." ); } parent::__construct($client); } protected function getConnection() { $connection = $this->getClient()->getConnection(); if (!$connection instanceof NodeConnectionInterface) { $class = __CLASS__; throw new ClientException("The class '$class' does not support aggregate connections."); } return $connection; } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { $profile = $this->getClient()->getProfile(); $connection->executeCommand($profile->createCommand('multi')); foreach ($commands as $command) { $connection->writeRequest($command); } foreach ($commands as $command) { $response = $connection->readResponse($command); if ($response instanceof ErrorResponseInterface) { $connection->executeCommand($profile->createCommand('discard')); throw new ServerException($response->getMessage()); } } $executed = $connection->executeCommand($profile->createCommand('exec')); if (!isset($executed)) { throw new ClientException( 'The underlying transaction has been aborted by the server.' ); } if (count($executed) !== count($commands)) { $expected = count($commands); $received = count($executed); throw new ClientException( "Invalid number of responses [expected $expected, received $received]." ); } $responses = array(); $sizeOfPipe = count($commands); $exceptions = $this->throwServerExceptions(); for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); $response = $executed[$i]; if (!$response instanceof ResponseInterface) { $responses[] = $command->parseResponse($response); } elseif ($response instanceof ErrorResponseInterface && $exceptions) { $this->exception($connection, $response); } else { $responses[] = $response; } unset($executed[$i]); } return $responses; } } isEmpty()) { $connection->writeRequest($commands->dequeue()); } $connection->disconnect(); return array(); } } client = $client; $this->pipeline = new \SplQueue(); } public function __call($method, $arguments) { $command = $this->client->createCommand($method, $arguments); $this->recordCommand($command); return $this; } protected function recordCommand(CommandInterface $command) { $this->pipeline->enqueue($command); } public function executeCommand(CommandInterface $command) { $this->recordCommand($command); return $this; } protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response) { $connection->disconnect(); $message = $response->getMessage(); throw new ServerException($message); } protected function getConnection() { $connection = $this->getClient()->getConnection(); if ($connection instanceof ReplicationInterface) { $connection->switchTo('master'); } return $connection; } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { foreach ($commands as $command) { $connection->writeRequest($command); } $responses = array(); $exceptions = $this->throwServerExceptions(); while (!$commands->isEmpty()) { $command = $commands->dequeue(); $response = $connection->readResponse($command); if (!$response instanceof ResponseInterface) { $responses[] = $command->parseResponse($response); } elseif ($response instanceof ErrorResponseInterface && $exceptions) { $this->exception($connection, $response); } else { $responses[] = $response; } } return $responses; } public function flushPipeline($send = true) { if ($send && !$this->pipeline->isEmpty()) { $responses = $this->executePipeline($this->getConnection(), $this->pipeline); $this->responses = array_merge($this->responses, $responses); } else { $this->pipeline = new \SplQueue(); } return $this; } private function setRunning($bool) { if ($bool && $this->running) { throw new ClientException('The current pipeline context is already being executed.'); } $this->running = $bool; } public function execute($callable = null) { if ($callable && !is_callable($callable)) { throw new \InvalidArgumentException('The argument must be a callable object.'); } $exception = null; $this->setRunning(true); try { if ($callable) { call_user_func($callable, $this); } $this->flushPipeline(); } catch (\Exception $exception) { } $this->setRunning(false); if ($exception) { throw $exception; } return $this->responses; } protected function throwServerExceptions() { return (bool) $this->client->getOptions()->exceptions; } public function getClient() { return $this->client; } } assertClient($client); $this->client = $client; $this->start(); } public function __destruct() { $this->stop(); } private function assertClient(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a monitor consumer over aggregate connections.' ); } if ($client->getProfile()->supportsCommand('MONITOR') === false) { throw new NotSupportedException("The current profile does not support 'MONITOR'."); } } protected function start() { $this->client->executeCommand( $this->client->createCommand('MONITOR') ); $this->valid = true; } public function stop() { $this->client->disconnect(); $this->valid = false; } #[\ReturnTypeWillChange] public function rewind() { } #[\ReturnTypeWillChange] public function current() { return $this->getValue(); } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function next() { ++$this->position; } #[\ReturnTypeWillChange] public function valid() { return $this->valid; } private function getValue() { $database = 0; $client = null; $event = $this->client->getConnection()->read(); $callback = function ($matches) use (&$database, &$client) { if (2 === $count = count($matches)) { $database = (int) $matches[1]; } if (4 === $count) { $database = (int) $matches[2]; $client = $matches[3]; } return ' '; }; $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1); @list($timestamp, $command, $arguments) = explode(' ', $event, 3); return (object) array( 'timestamp' => (float) $timestamp, 'database' => $database, 'client' => $client, 'command' => substr($command, 1, -1), 'arguments' => $arguments, ); } } checkCapabilities($client); $this->options = $options ?: array(); $this->client = $client; $this->genericSubscribeInit('subscribe'); $this->genericSubscribeInit('psubscribe'); } public function getClient() { return $this->client; } private function checkCapabilities(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a PUB/SUB consumer over aggregate connections.' ); } $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe'); if ($client->getProfile()->supportsCommands($commands) === false) { throw new NotSupportedException( 'The current profile does not support PUB/SUB related commands.' ); } } private function genericSubscribeInit($subscribeAction) { if (isset($this->options[$subscribeAction])) { $this->$subscribeAction($this->options[$subscribeAction]); } } protected function writeRequest($method, $arguments) { $this->client->getConnection()->writeRequest( $this->client->createCommand($method, Command::normalizeArguments($arguments) ) ); } protected function disconnect() { $this->client->disconnect(); } protected function getValue() { $response = $this->client->getConnection()->read(); switch ($response[0]) { case self::SUBSCRIBE: case self::UNSUBSCRIBE: case self::PSUBSCRIBE: case self::PUNSUBSCRIBE: if ($response[2] === 0) { $this->invalidate(); } case self::MESSAGE: return (object) array( 'kind' => $response[0], 'channel' => $response[1], 'payload' => $response[2], ); case self::PMESSAGE: return (object) array( 'kind' => $response[0], 'pattern' => $response[1], 'channel' => $response[2], 'payload' => $response[3], ); case self::PONG: return (object) array( 'kind' => $response[0], 'payload' => $response[1], ); default: throw new ClientException( "Unknown message type '{$response[0]}' received in the PUB/SUB context." ); } } } stop(true); } protected function isFlagSet($value) { return ($this->statusFlags & $value) === $value; } public function subscribe($channel ) { $this->writeRequest(self::SUBSCRIBE, func_get_args()); $this->statusFlags |= self::STATUS_SUBSCRIBED; } public function unsubscribe() { $this->writeRequest(self::UNSUBSCRIBE, func_get_args()); } public function psubscribe($pattern ) { $this->writeRequest(self::PSUBSCRIBE, func_get_args()); $this->statusFlags |= self::STATUS_PSUBSCRIBED; } public function punsubscribe() { $this->writeRequest(self::PUNSUBSCRIBE, func_get_args()); } public function ping($payload = null) { $this->writeRequest('PING', array($payload)); } public function stop($drop = false) { if (!$this->valid()) { return false; } if ($drop) { $this->invalidate(); $this->disconnect(); } else { if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) { $this->unsubscribe(); } if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) { $this->punsubscribe(); } } return !$drop; } abstract protected function disconnect(); abstract protected function writeRequest($method, $arguments); #[\ReturnTypeWillChange] public function rewind() { } #[\ReturnTypeWillChange] public function current() { return $this->getValue(); } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function next() { if ($this->valid()) { ++$this->position; } return $this->position; } #[\ReturnTypeWillChange] public function valid() { $isValid = $this->isFlagSet(self::STATUS_VALID); $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED; $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0; return $isValid && $hasSubscriptions; } protected function invalidate() { $this->statusFlags = 0; } abstract protected function getValue(); } callbacks = array(); $this->pubsub = $pubsub; } protected function assertCallback($callable) { if (!is_callable($callable)) { throw new \InvalidArgumentException('The given argument must be a callable object.'); } } public function getPubSubConsumer() { return $this->pubsub; } public function subscriptionCallback($callable = null) { if (isset($callable)) { $this->assertCallback($callable); } $this->subscriptionCallback = $callable; } public function defaultCallback($callable = null) { if (isset($callable)) { $this->assertCallback($callable); } $this->subscriptionCallback = $callable; } public function attachCallback($channel, $callback) { $callbackName = $this->getPrefixKeys().$channel; $this->assertCallback($callback); $this->callbacks[$callbackName] = $callback; $this->pubsub->subscribe($channel); } public function detachCallback($channel) { $callbackName = $this->getPrefixKeys().$channel; if (isset($this->callbacks[$callbackName])) { unset($this->callbacks[$callbackName]); $this->pubsub->unsubscribe($channel); } } public function run() { foreach ($this->pubsub as $message) { $kind = $message->kind; if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) { if (isset($this->subscriptionCallback)) { $callback = $this->subscriptionCallback; call_user_func($callback, $message); } continue; } if (isset($this->callbacks[$message->channel])) { $callback = $this->callbacks[$message->channel]; call_user_func($callback, $message->payload); } elseif (isset($this->defaultCallback)) { $callback = $this->defaultCallback; call_user_func($callback, $message); } } } public function stop() { $this->pubsub->stop(); } protected function getPrefixKeys() { $options = $this->pubsub->getClient()->getOptions(); if (isset($options->prefix)) { return $options->prefix->getPrefix(); } return ''; } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', 'HSTRLEN' => 'Predis\Command\HashStringLength', 'BITFIELD' => 'Predis\Command\StringBitField', 'GEOADD' => 'Predis\Command\GeospatialGeoAdd', 'GEOHASH' => 'Predis\Command\GeospatialGeoHash', 'GEOPOS' => 'Predis\Command\GeospatialGeoPos', 'GEODIST' => 'Predis\Command\GeospatialGeoDist', 'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius', 'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember', ); } } commands = $this->getSupportedCommands(); } abstract protected function getSupportedCommands(); public function supportsCommand($commandID) { return isset($this->commands[strtoupper($commandID)]); } public function supportsCommands(array $commandIDs) { foreach ($commandIDs as $commandID) { if (!$this->supportsCommand($commandID)) { return false; } } return true; } public function getCommandClass($commandID) { if (isset($this->commands[$commandID = strtoupper($commandID)])) { return $this->commands[$commandID]; } } public function createCommand($commandID, array $arguments = array()) { $commandID = strtoupper($commandID); if (!isset($this->commands[$commandID])) { throw new ClientException("Command '$commandID' is not a registered Redis command."); } $commandClass = $this->commands[$commandID]; $command = new $commandClass(); $command->setArguments($arguments); if (isset($this->processor)) { $this->processor->process($command); } return $command; } public function defineCommand($commandID, $class) { $reflection = new \ReflectionClass($class); if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) { throw new \InvalidArgumentException("The class '$class' is not a valid command class."); } $this->commands[strtoupper($commandID)] = $class; } public function setProcessor(ProcessorInterface $processor = null) { $this->processor = $processor; } public function getProcessor() { return $this->processor; } public function __toString() { return $this->getVersion(); } } 'Predis\Profile\RedisVersion200', '2.2' => 'Predis\Profile\RedisVersion220', '2.4' => 'Predis\Profile\RedisVersion240', '2.6' => 'Predis\Profile\RedisVersion260', '2.8' => 'Predis\Profile\RedisVersion280', '3.0' => 'Predis\Profile\RedisVersion300', '3.2' => 'Predis\Profile\RedisVersion320', 'dev' => 'Predis\Profile\RedisUnstable', 'default' => 'Predis\Profile\RedisVersion320', ); private function __construct() { } public static function getDefault() { return self::get('default'); } public static function getDevelopment() { return self::get('dev'); } public static function define($alias, $class) { $reflection = new \ReflectionClass($class); if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) { throw new \InvalidArgumentException("The class '$class' is not a valid profile class."); } self::$profiles[$alias] = $class; } public static function get($version) { if (!isset(self::$profiles[$version])) { throw new ClientException("Unknown server profile: '$version'."); } $profile = self::$profiles[$version]; return new $profile(); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', ); } } parameters = $this->assertParameters($parameters); } public function __destruct() { $this->disconnect(); } abstract protected function assertParameters(ParametersInterface $parameters); abstract protected function createResource(); public function isConnected() { return isset($this->resource); } public function connect() { if (!$this->isConnected()) { $this->resource = $this->createResource(); return true; } return false; } public function disconnect() { unset($this->resource); } public function addConnectCommand(CommandInterface $command) { $this->initCommands[] = $command; } public function executeCommand(CommandInterface $command) { $this->writeRequest($command); return $this->readResponse($command); } public function readResponse(CommandInterface $command) { return $this->read(); } private function createExceptionMessage($message) { $parameters = $this->parameters; if ($parameters->scheme === 'unix') { return "$message [$parameters->scheme:$parameters->path]"; } if (filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return "$message [$parameters->scheme://[$parameters->host]:$parameters->port]"; } return "$message [$parameters->scheme://$parameters->host:$parameters->port]"; } protected function onConnectionError($message, $code = null) { CommunicationException::handle( new ConnectionException($this, static::createExceptionMessage($message), $code) ); } protected function onProtocolError($message) { CommunicationException::handle( new ProtocolException($this, static::createExceptionMessage($message)) ); } public function getResource() { if (isset($this->resource)) { return $this->resource; } $this->connect(); return $this->resource; } public function getParameters() { return $this->parameters; } protected function getIdentifier() { if ($this->parameters->scheme === 'unix') { return $this->parameters->path; } return "{$this->parameters->host}:{$this->parameters->port}"; } public function __toString() { if (!isset($this->cachedId)) { $this->cachedId = $this->getIdentifier(); } return $this->cachedId; } public function __sleep() { return array('parameters', 'initCommands'); } } assertExtensions(); parent::__construct($parameters); $this->reader = $this->createReader(); } public function __destruct() { parent::__destruct(); phpiredis_reader_destroy($this->reader); } public function disconnect() { phpiredis_reader_reset($this->reader); parent::disconnect(); } private function assertExtensions() { if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } protected function assertParameters(ParametersInterface $parameters) { switch ($parameters->scheme) { case 'tcp': case 'redis': case 'unix': break; case 'tls': case 'rediss': throw new \InvalidArgumentException('SSL encryption is not supported by this connection backend.'); default: throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } return $parameters; } protected function createStreamSocket(ParametersInterface $parameters, $address, $flags, $context = null) { $socket = null; $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); $resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags); if (!$resource) { $this->onConnectionError(trim($errstr), $errno); } if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) { $rwtimeout = (float) $parameters->read_write_timeout; $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; $timeout = array( 'sec' => $timeoutSeconds = floor($rwtimeout), 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000, ); $socket = $socket ?: socket_import_stream($resource); @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout); @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); } if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { $socket = $socket ?: socket_import_stream($resource); socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); } return $resource; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getReader() { return $this->reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } public function read() { $socket = $this->getResource(); $reader = $this->reader; while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { $buffer = stream_socket_recvfrom($socket, 4096); if ($buffer === false || $buffer === '') { $this->onConnectionError('Error while reading bytes from the server.'); } phpiredis_reader_feed($reader, $buffer); } if ($state === PHPIREDIS_READER_STATE_COMPLETE) { return phpiredis_reader_get_reply($reader); } else { $this->onProtocolError(phpiredis_reader_get_error($reader)); return; } } public function writeRequest(CommandInterface $command) { $arguments = $command->getArguments(); array_unshift($arguments, $command->getId()); $this->write(phpiredis_format_command($arguments)); } public function __wakeup() { $this->assertExtensions(); $this->reader = $this->createReader(); } } parameters = $this->assertParameters($parameters); $this->protocol = $protocol ?: new TextProtocolProcessor(); } public function getProtocol() { return $this->protocol; } public function writeBuffer($buffer) { $this->write($buffer); } public function readBuffer($length) { if ($length <= 0) { throw new \InvalidArgumentException('Length parameter must be greater than 0.'); } $value = ''; $socket = $this->getResource(); do { $chunk = fread($socket, $length); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading bytes from the server.'); } $value .= $chunk; } while (($length -= strlen($chunk)) > 0); return $value; } public function readLine() { $value = ''; $socket = $this->getResource(); do { $chunk = fgets($socket); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading line from the server.'); } $value .= $chunk; } while (substr($value, -2) !== "\r\n"); return substr($value, 0, -2); } public function writeRequest(CommandInterface $command) { $this->protocol->write($this, $command); } public function read() { return $this->protocol->read($this); } public function __sleep() { return array_merge(parent::__sleep(), array('protocol')); } } assertExtensions(); if ($parameters->scheme !== 'http') { throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); } $this->parameters = $parameters; $this->resource = $this->createCurl(); $this->reader = $this->createReader(); } public function __destruct() { curl_close($this->resource); phpiredis_reader_destroy($this->reader); } private function throwNotSupportedException($method) { $class = __CLASS__; throw new NotSupportedException("The method $class::$method() is not supported."); } private function assertExtensions() { if (!extension_loaded('curl')) { throw new NotSupportedException( 'The "curl" extension is required by this connection backend.' ); } if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } private function createCurl() { $parameters = $this->getParameters(); $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0) * 1000; if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $host = "[$host]"; } $options = array( CURLOPT_FAILONERROR => true, CURLOPT_CONNECTTIMEOUT_MS => $timeout, CURLOPT_URL => "$parameters->scheme://$host:$parameters->port", CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_POST => true, CURLOPT_WRITEFUNCTION => array($this, 'feedReader'), ); if (isset($parameters->user, $parameters->pass)) { $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}"; } curl_setopt_array($resource = curl_init(), $options); return $resource; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } protected function feedReader($resource, $buffer) { phpiredis_reader_feed($this->reader, $buffer); return strlen($buffer); } public function connect() { } public function disconnect() { } public function isConnected() { return true; } protected function getCommandId(CommandInterface $command) { switch ($commandID = $command->getId()) { case 'AUTH': case 'SELECT': case 'MULTI': case 'EXEC': case 'WATCH': case 'UNWATCH': case 'DISCARD': case 'MONITOR': throw new NotSupportedException("Command '$commandID' is not allowed by Webdis."); default: return $commandID; } } public function writeRequest(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function readResponse(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function executeCommand(CommandInterface $command) { $resource = $this->resource; $commandId = $this->getCommandId($command); if ($arguments = $command->getArguments()) { $arguments = implode('/', array_map('urlencode', $arguments)); $serializedCommand = "$commandId/$arguments.raw"; } else { $serializedCommand = "$commandId.raw"; } curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand); if (curl_exec($resource) === false) { $error = curl_error($resource); $errno = curl_errno($resource); throw new ConnectionException($this, trim($error), $errno); } if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) { throw new ProtocolException($this, phpiredis_reader_get_error($this->reader)); } return phpiredis_reader_get_reply($this->reader); } public function getResource() { return $this->resource; } public function getParameters() { return $this->parameters; } public function addConnectCommand(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function read() { $this->throwNotSupportedException(__FUNCTION__); } public function __toString() { return "{$this->parameters->host}:{$this->parameters->port}"; } public function __sleep() { return array('parameters'); } public function __wakeup() { $this->assertExtensions(); $this->resource = $this->createCurl(); $this->reader = $this->createReader(); } } 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ); public function __construct(array $parameters = array()) { $this->parameters = $this->filter($parameters) + $this->getDefaults(); } protected function getDefaults() { return self::$defaults; } public static function create($parameters) { if (is_string($parameters)) { $parameters = static::parse($parameters); } return new static($parameters ?: array()); } public static function parse($uri) { if (stripos($uri, 'unix://') === 0) { $uri = str_ireplace('unix://', 'unix:', $uri); } if (!$parsed = parse_url($uri)) { throw new \InvalidArgumentException("Invalid parameters URI: $uri"); } if ( isset($parsed['host']) && false !== strpos($parsed['host'], '[') && false !== strpos($parsed['host'], ']') ) { $parsed['host'] = substr($parsed['host'], 1, -1); } if (isset($parsed['query'])) { parse_str($parsed['query'], $queryarray); unset($parsed['query']); $parsed = array_merge($parsed, $queryarray); } if (stripos($uri, 'redis') === 0) { if (isset($parsed['user'])) { if (strlen($parsed['user'])) { $parsed['username'] = $parsed['user']; } unset($parsed['user']); } if (isset($parsed['pass'])) { if (strlen($parsed['pass'])) { $parsed['password'] = $parsed['pass']; } unset($parsed['pass']); } if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) { $parsed['database'] = $path[1]; if (isset($path[2])) { $parsed['path'] = $path[2]; } else { unset($parsed['path']); } } } return $parsed; } protected function filter(array $parameters) { return $parameters ?: array(); } public function __get($parameter) { if (isset($this->parameters[$parameter])) { return $this->parameters[$parameter]; } } public function __isset($parameter) { return isset($this->parameters[$parameter]); } public function toArray() { return $this->parameters; } public function __sleep() { return array('parameters'); } } 'Predis\Connection\StreamConnection', 'unix' => 'Predis\Connection\StreamConnection', 'tls' => 'Predis\Connection\StreamConnection', 'redis' => 'Predis\Connection\StreamConnection', 'rediss' => 'Predis\Connection\StreamConnection', 'http' => 'Predis\Connection\WebdisConnection', ); protected function checkInitializer($initializer) { if (is_callable($initializer)) { return $initializer; } $class = new \ReflectionClass($initializer); if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) { throw new \InvalidArgumentException( 'A connection initializer must be a valid connection class or a callable object.' ); } return $initializer; } public function define($scheme, $initializer) { $this->schemes[$scheme] = $this->checkInitializer($initializer); } public function undefine($scheme) { unset($this->schemes[$scheme]); } public function create($parameters) { if (!$parameters instanceof ParametersInterface) { $parameters = $this->createParameters($parameters); } $scheme = $parameters->scheme; if (!isset($this->schemes[$scheme])) { throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'."); } $initializer = $this->schemes[$scheme]; if (is_callable($initializer)) { $connection = call_user_func($initializer, $parameters, $this); } else { $connection = new $initializer($parameters); $this->prepareConnection($connection); } if (!$connection instanceof NodeConnectionInterface) { throw new \UnexpectedValueException( 'Objects returned by connection initializers must implement '. "'Predis\Connection\NodeConnectionInterface'." ); } return $connection; } public function aggregate(AggregateConnectionInterface $connection, array $parameters) { foreach ($parameters as $node) { $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node)); } } public function setDefaultParameters(array $parameters) { $this->defaults = $parameters; } public function getDefaultParameters() { return $this->defaults; } protected function createParameters($parameters) { if (is_string($parameters)) { $parameters = Parameters::parse($parameters); } else { $parameters = $parameters ?: array(); } if ($this->defaults) { $parameters += $this->defaults; } return new Parameters($parameters); } protected function prepareConnection(NodeConnectionInterface $connection) { $parameters = $connection->getParameters(); if (isset($parameters->password) && strlen($parameters->password)) { $cmdAuthArgs = isset($parameters->username) && strlen($parameters->username) ? array('AUTH', $parameters->username, $parameters->password) : array('AUTH', $parameters->password); $connection->addConnectCommand( new RawCommand($cmdAuthArgs) ); } if (isset($parameters->database) && strlen($parameters->database)) { $connection->addConnectCommand( new RawCommand(array('SELECT', $parameters->database)) ); } } } parameters->persistent) && $this->parameters->persistent) { return; } $this->disconnect(); } protected function assertParameters(ParametersInterface $parameters) { switch ($parameters->scheme) { case 'tcp': case 'redis': case 'unix': break; case 'tls': case 'rediss': $this->assertSslSupport($parameters); break; default: throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } return $parameters; } protected function assertSslSupport(ParametersInterface $parameters) { if ( filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN) && version_compare(PHP_VERSION, '7.0.0beta') < 0 ) { throw new \InvalidArgumentException('Persistent SSL connections require PHP >= 7.0.0.'); } } protected function createResource() { switch ($this->parameters->scheme) { case 'tcp': case 'redis': return $this->tcpStreamInitializer($this->parameters); case 'unix': return $this->unixStreamInitializer($this->parameters); case 'tls': case 'rediss': return $this->tlsStreamInitializer($this->parameters); default: throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'."); } } protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) { $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags)) { $this->onConnectionError(trim($errstr), $errno); } if (isset($parameters->read_write_timeout)) { $rwtimeout = (float) $parameters->read_write_timeout; $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; $timeoutSeconds = floor($rwtimeout); $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); } if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { $socket = socket_import_stream($resource); socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); } return $resource; } protected function tcpStreamInitializer(ParametersInterface $parameters) { if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $address = "tcp://$parameters->host:$parameters->port"; } else { $address = "tcp://[$parameters->host]:$parameters->port"; } $flags = STREAM_CLIENT_CONNECT; if (isset($parameters->async_connect) && $parameters->async_connect) { $flags |= STREAM_CLIENT_ASYNC_CONNECT; } if (isset($parameters->persistent)) { if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $flags |= STREAM_CLIENT_PERSISTENT; if ($persistent === null) { $address = "{$address}/{$parameters->persistent}"; } } } $resource = $this->createStreamSocket($parameters, $address, $flags); return $resource; } protected function unixStreamInitializer(ParametersInterface $parameters) { if (!isset($parameters->path)) { throw new \InvalidArgumentException('Missing UNIX domain socket path.'); } $flags = STREAM_CLIENT_CONNECT; if (isset($parameters->persistent)) { if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $flags |= STREAM_CLIENT_PERSISTENT; if ($persistent === null) { throw new \InvalidArgumentException( 'Persistent connection IDs are not supported when using UNIX domain sockets.' ); } } } $resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags); return $resource; } protected function tlsStreamInitializer(ParametersInterface $parameters) { $resource = $this->tcpStreamInitializer($parameters); $metadata = stream_get_meta_data($resource); if (isset($metadata['crypto'])) { return $resource; } if (is_array($parameters->ssl)) { $options = $parameters->ssl; } else { $options = array(); } if (!isset($options['crypto_type'])) { $options['crypto_type'] = STREAM_CRYPTO_METHOD_TLS_CLIENT; } if (!stream_context_set_option($resource, array('ssl' => $options))) { $this->onConnectionError('Error while setting SSL context options'); } if (!stream_socket_enable_crypto($resource, true, $options['crypto_type'])) { $this->onConnectionError('Error while switching to encrypted communication'); } return $resource; } public function connect() { if (parent::connect() && $this->initCommands) { foreach ($this->initCommands as $command) { $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { $this->onConnectionError("`{$command->getId()}` failed: $response", 0); } } } } public function disconnect() { if ($this->isConnected()) { fclose($this->getResource()); parent::disconnect(); } } protected function write($buffer) { $socket = $this->getResource(); while (($length = strlen($buffer)) > 0) { $written = @fwrite($socket, $buffer); if ($length === $written) { return; } if ($written === false || $written === 0) { $this->onConnectionError('Error while writing bytes to the server.'); } $buffer = substr($buffer, $written); } } public function read() { $socket = $this->getResource(); $chunk = fgets($socket); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading line from the server.'); } $prefix = $chunk[0]; $payload = substr($chunk, 1, -2); switch ($prefix) { case '+': return StatusResponse::get($payload); case '$': $size = (int) $payload; if ($size === -1) { return; } $bulkData = ''; $bytesLeft = ($size += 2); do { $chunk = fread($socket, min($bytesLeft, 4096)); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading bytes from the server.'); } $bulkData .= $chunk; $bytesLeft = $size - strlen($bulkData); } while ($bytesLeft > 0); return substr($bulkData, 0, -2); case '*': $count = (int) $payload; if ($count === -1) { return; } $multibulk = array(); for ($i = 0; $i < $count; ++$i) { $multibulk[$i] = $this->read(); } return $multibulk; case ':': $integer = (int) $payload; return $integer == $payload ? $integer : $payload; case '-': return new ErrorResponse($payload); default: $this->onProtocolError("Unknown response prefix: '$prefix'."); return; } } public function writeRequest(CommandInterface $command) { $commandID = $command->getId(); $arguments = $command->getArguments(); $cmdlen = strlen($commandID); $reqlen = count($arguments) + 1; $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; foreach ($arguments as $argument) { $arglen = strlen(strval($argument)); $buffer .= "\${$arglen}\r\n{$argument}\r\n"; } $this->write($buffer); } } assertExtensions(); parent::__construct($parameters); $this->reader = $this->createReader(); } public function __destruct() { parent::__destruct(); phpiredis_reader_destroy($this->reader); } protected function assertExtensions() { if (!extension_loaded('sockets')) { throw new NotSupportedException( 'The "sockets" extension is required by this connection backend.' ); } if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } protected function assertParameters(ParametersInterface $parameters) { switch ($parameters->scheme) { case 'tcp': case 'redis': case 'unix': break; default: throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } if (isset($parameters->persistent)) { throw new NotSupportedException( 'Persistent connections are not supported by this connection backend.' ); } return $parameters; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getReader() { return $this->reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } private function emitSocketError() { $errno = socket_last_error(); $errstr = socket_strerror($errno); $this->disconnect(); $this->onConnectionError(trim($errstr), $errno); } protected static function getAddress(ParametersInterface $parameters) { if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { return $host; } if ($host === $address = gethostbyname($host)) { return false; } return $address; } protected function createResource() { $parameters = $this->parameters; if ($parameters->scheme === 'unix') { $address = $parameters->path; $domain = AF_UNIX; $protocol = 0; } else { if (false === $address = self::getAddress($parameters)) { $this->onConnectionError("Cannot resolve the address of '$parameters->host'."); } $domain = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? AF_INET6 : AF_INET; $protocol = SOL_TCP; } if (false === $socket = @socket_create($domain, SOCK_STREAM, $protocol)) { $this->emitSocketError(); } $this->setSocketOptions($socket, $parameters); $this->connectWithTimeout($socket, $address, $parameters); return $socket; } private function setSocketOptions($socket, ParametersInterface $parameters) { if ($parameters->scheme !== 'unix') { if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) { $this->emitSocketError(); } if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { $this->emitSocketError(); } } if (isset($parameters->read_write_timeout)) { $rwtimeout = (float) $parameters->read_write_timeout; $timeoutSec = floor($rwtimeout); $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000; $timeout = array( 'sec' => $timeoutSec, 'usec' => $timeoutUsec, ); if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) { $this->emitSocketError(); } if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) { $this->emitSocketError(); } } } private function connectWithTimeout($socket, $address, ParametersInterface $parameters) { socket_set_nonblock($socket); if (@socket_connect($socket, $address, (int) $parameters->port) === false) { $error = socket_last_error(); if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) { $this->emitSocketError(); } } socket_set_block($socket); $null = null; $selectable = array($socket); $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); $timeoutSecs = floor($timeout); $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000; $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs); if ($selected === 2) { $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED); } if ($selected === 0) { $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT); } if ($selected === false) { $this->emitSocketError(); } } public function connect() { if (parent::connect() && $this->initCommands) { foreach ($this->initCommands as $command) { $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { $this->onConnectionError("`{$command->getId()}` failed: $response", 0); } } } } public function disconnect() { if ($this->isConnected()) { phpiredis_reader_reset($this->reader); socket_close($this->getResource()); parent::disconnect(); } } protected function write($buffer) { $socket = $this->getResource(); while (($length = strlen($buffer)) > 0) { $written = socket_write($socket, $buffer, $length); if ($length === $written) { return; } if ($written === false) { $this->onConnectionError('Error while writing bytes to the server.'); } $buffer = substr($buffer, $written); } } public function read() { $socket = $this->getResource(); $reader = $this->reader; while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '' || $buffer === null) { $this->emitSocketError(); } phpiredis_reader_feed($reader, $buffer); } if ($state === PHPIREDIS_READER_STATE_COMPLETE) { return phpiredis_reader_get_reply($reader); } else { $this->onProtocolError(phpiredis_reader_get_error($reader)); return; } } public function writeRequest(CommandInterface $command) { $arguments = $command->getArguments(); array_unshift($arguments, $command->getId()); $this->write(phpiredis_format_command($arguments)); } public function __wakeup() { $this->assertExtensions(); $this->reader = $this->createReader(); } } strategy = $strategy ?: new ReplicationStrategy(); } public function setAutoDiscovery($value) { if (!$this->connectionFactory) { throw new ClientException('Automatic discovery requires a connection factory'); } $this->autoDiscovery = (bool) $value; } public function setConnectionFactory(FactoryInterface $connectionFactory) { $this->connectionFactory = $connectionFactory; } protected function reset() { $this->current = null; } public function add(NodeConnectionInterface $connection) { $alias = $connection->getParameters()->alias; if ($alias === 'master') { $this->master = $connection; } else { $this->slaves[$alias ?: "slave-$connection"] = $connection; } $this->reset(); } public function remove(NodeConnectionInterface $connection) { if ($connection->getParameters()->alias === 'master') { $this->master = null; $this->reset(); return true; } else { if (($id = array_search($connection, $this->slaves, true)) !== false) { unset($this->slaves[$id]); $this->reset(); return true; } } return false; } public function getConnection(CommandInterface $command) { if (!$this->current) { if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { $this->current = $slave; } else { $this->current = $this->getMasterOrDie(); } return $this->current; } if ($this->current === $master = $this->getMasterOrDie()) { return $master; } if (!$this->strategy->isReadOperation($command) || !$this->slaves) { $this->current = $master; } return $this->current; } public function getConnectionById($connectionId) { if ($connectionId === 'master') { return $this->master; } if (isset($this->slaves[$connectionId])) { return $this->slaves[$connectionId]; } return; } public function switchTo($connection) { if (!$connection instanceof NodeConnectionInterface) { $connection = $this->getConnectionById($connection); } if (!$connection) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } $this->current = $connection; } public function switchToMaster() { $this->switchTo('master'); } public function switchToSlave() { $connection = $this->pickSlave(); $this->switchTo($connection); } public function getCurrent() { return $this->current; } public function getMaster() { return $this->master; } private function getMasterOrDie() { if (!$connection = $this->getMaster()) { throw new MissingMasterException('No master server available for replication'); } return $connection; } public function getSlaves() { return array_values($this->slaves); } public function getReplicationStrategy() { return $this->strategy; } protected function pickSlave() { if ($this->slaves) { return $this->slaves[array_rand($this->slaves)]; } } public function isConnected() { return $this->current ? $this->current->isConnected() : false; } public function connect() { if (!$this->current) { if (!$this->current = $this->pickSlave()) { if (!$this->current = $this->getMaster()) { throw new ClientException('No available connection for replication'); } } } $this->current->connect(); } public function disconnect() { if ($this->master) { $this->master->disconnect(); } foreach ($this->slaves as $connection) { $connection->disconnect(); } } private function handleInfoResponse($response) { $info = array(); foreach (preg_split('/\r?\n/', $response) as $row) { if (strpos($row, ':') === false) { continue; } list($k, $v) = explode(':', $row, 2); $info[$k] = $v; } return $info; } public function discover() { if (!$this->connectionFactory) { throw new ClientException('Discovery requires a connection factory'); } RETRY_FETCH: { try { if ($connection = $this->getMaster()) { $this->discoverFromMaster($connection, $this->connectionFactory); } elseif ($connection = $this->pickSlave()) { $this->discoverFromSlave($connection, $this->connectionFactory); } else { throw new ClientException('No connection available for discovery'); } } catch (ConnectionException $exception) { $this->remove($connection); goto RETRY_FETCH; } } } protected function discoverFromMaster(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) { $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); $replication = $this->handleInfoResponse($response); if ($replication['role'] !== 'master') { throw new ClientException("Role mismatch (expected master, got slave) [$connection]"); } $this->slaves = array(); foreach ($replication as $k => $v) { $parameters = null; if (strpos($k, 'slave') === 0 && preg_match('/ip=(?P.*),port=(?P\d+)/', $v, $parameters)) { $slaveConnection = $connectionFactory->create(array( 'host' => $parameters['host'], 'port' => $parameters['port'], )); $this->add($slaveConnection); } } } protected function discoverFromSlave(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) { $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); $replication = $this->handleInfoResponse($response); if ($replication['role'] !== 'slave') { throw new ClientException("Role mismatch (expected slave, got master) [$connection]"); } $masterConnection = $connectionFactory->create(array( 'host' => $replication['master_host'], 'port' => $replication['master_port'], 'alias' => 'master', )); $this->add($masterConnection); $this->discoverFromMaster($masterConnection, $connectionFactory); } private function retryCommandOnFailure(CommandInterface $command, $method) { RETRY_COMMAND: { try { $connection = $this->getConnection($command); $response = $connection->$method($command); if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') { throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]"); } } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); if ($connection === $this->master && !$this->autoDiscovery) { throw $exception; } else { $this->remove($connection); } if (!$this->slaves && !$this->master) { throw $exception; } elseif ($this->autoDiscovery) { $this->discover(); } goto RETRY_COMMAND; } catch (MissingMasterException $exception) { if ($this->autoDiscovery) { $this->discover(); } else { throw $exception; } goto RETRY_COMMAND; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function __sleep() { return array('master', 'slaves', 'strategy'); } } pool = array(); $this->strategy = $strategy ?: new PredisStrategy(); $this->distributor = $this->strategy->getDistributor(); } public function isConnected() { foreach ($this->pool as $connection) { if ($connection->isConnected()) { return true; } } return false; } public function connect() { foreach ($this->pool as $connection) { $connection->connect(); } } public function disconnect() { foreach ($this->pool as $connection) { $connection->disconnect(); } } public function add(NodeConnectionInterface $connection) { $parameters = $connection->getParameters(); if (isset($parameters->alias)) { $this->pool[$parameters->alias] = $connection; } else { $this->pool[] = $connection; } $weight = isset($parameters->weight) ? $parameters->weight : null; $this->distributor->add($connection, $weight); } public function remove(NodeConnectionInterface $connection) { if (($id = array_search($connection, $this->pool, true)) !== false) { unset($this->pool[$id]); $this->distributor->remove($connection); return true; } return false; } public function removeById($connectionID) { if ($connection = $this->getConnectionById($connectionID)) { return $this->remove($connection); } return false; } public function getConnection(CommandInterface $command) { $slot = $this->strategy->getSlot($command); if (!isset($slot)) { throw new NotSupportedException( "Cannot use '{$command->getId()}' over clusters of connections." ); } $node = $this->distributor->getBySlot($slot); return $node; } public function getConnectionById($connectionID) { return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null; } public function getConnectionByKey($key) { $hash = $this->strategy->getSlotByKey($key); $node = $this->distributor->getBySlot($hash); return $node; } public function getClusterStrategy() { return $this->strategy; } #[\ReturnTypeWillChange] public function count() { return count($this->pool); } #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->pool); } public function writeRequest(CommandInterface $command) { $this->getConnection($command)->writeRequest($command); } public function readResponse(CommandInterface $command) { return $this->getConnection($command)->readResponse($command); } public function executeCommand(CommandInterface $command) { return $this->getConnection($command)->executeCommand($command); } public function executeCommandOnNodes(CommandInterface $command) { $responses = array(); foreach ($this->pool as $connection) { $responses[] = $connection->executeCommand($command); } return $responses; } } sentinels = $sentinels; $this->service = $service; $this->connectionFactory = $connectionFactory; $this->strategy = $strategy ?: new ReplicationStrategy(); } public function setSentinelTimeout($timeout) { $this->sentinelTimeout = (float) $timeout; } public function setRetryLimit($retry) { $this->retryLimit = (int) $retry; } public function setRetryWait($milliseconds) { $this->retryWait = (float) $milliseconds; } public function setUpdateSentinels($update) { $this->updateSentinels = (bool) $update; } protected function reset() { $this->current = null; } protected function wipeServerList() { $this->reset(); $this->master = null; $this->slaves = array(); } public function add(NodeConnectionInterface $connection) { $alias = $connection->getParameters()->alias; if ($alias === 'master') { $this->master = $connection; } else { $this->slaves[$alias ?: count($this->slaves)] = $connection; } $this->reset(); } public function remove(NodeConnectionInterface $connection) { if ($connection === $this->master) { $this->master = null; $this->reset(); return true; } if (false !== $id = array_search($connection, $this->slaves, true)) { unset($this->slaves[$id]); $this->reset(); return true; } return false; } protected function createSentinelConnection($parameters) { if ($parameters instanceof NodeConnectionInterface) { return $parameters; } if (is_string($parameters)) { $parameters = Parameters::parse($parameters); } if (is_array($parameters)) { $parameters['database'] = null; $parameters['username'] = null; $parameters['password'] = null; if (!isset($parameters['timeout'])) { $parameters['timeout'] = $this->sentinelTimeout; } } $connection = $this->connectionFactory->create($parameters); return $connection; } public function getSentinelConnection() { if (!$this->sentinelConnection) { if (!$this->sentinels) { throw new \Predis\ClientException('No sentinel server available for autodiscovery.'); } $sentinel = array_shift($this->sentinels); $this->sentinelConnection = $this->createSentinelConnection($sentinel); } return $this->sentinelConnection; } public function updateSentinels() { SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'sentinels', $this->service) ); $this->sentinels = array(); $this->sentinels[] = $sentinel->getParameters()->toArray(); foreach ($payload as $sentinel) { $this->sentinels[] = array( 'host' => $sentinel[3], 'port' => $sentinel[5], ); } } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } } public function querySentinel() { $this->wipeServerList(); $this->updateSentinels(); $this->getMaster(); $this->getSlaves(); } private function handleSentinelErrorResponse(NodeConnectionInterface $sentinel, ErrorResponseInterface $error) { if ($error->getErrorType() === 'IDONTKNOW') { throw new ConnectionException($sentinel, $error->getMessage()); } else { throw new ServerException($error->getMessage()); } } protected function querySentinelForMaster(NodeConnectionInterface $sentinel, $service) { $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'get-master-addr-by-name', $service) ); if ($payload === null) { throw new ServerException('ERR No such master with that name'); } if ($payload instanceof ErrorResponseInterface) { $this->handleSentinelErrorResponse($sentinel, $payload); } return array( 'host' => $payload[0], 'port' => $payload[1], 'alias' => 'master', ); } protected function querySentinelForSlaves(NodeConnectionInterface $sentinel, $service) { $slaves = array(); $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'slaves', $service) ); if ($payload instanceof ErrorResponseInterface) { $this->handleSentinelErrorResponse($sentinel, $payload); } foreach ($payload as $slave) { $flags = explode(',', $slave[9]); if (array_intersect($flags, array('s_down', 'o_down', 'disconnected'))) { continue; } $slaves[] = array( 'host' => $slave[3], 'port' => $slave[5], 'alias' => "slave-$slave[1]", ); } return $slaves; } public function getCurrent() { return $this->current; } public function getMaster() { if ($this->master) { return $this->master; } if ($this->updateSentinels) { $this->updateSentinels(); } SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $masterParameters = $this->querySentinelForMaster($sentinel, $this->service); $masterConnection = $this->connectionFactory->create($masterParameters); $this->add($masterConnection); } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } return $masterConnection; } public function getSlaves() { if ($this->slaves) { return array_values($this->slaves); } if ($this->updateSentinels) { $this->updateSentinels(); } SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $slavesParameters = $this->querySentinelForSlaves($sentinel, $this->service); foreach ($slavesParameters as $slaveParameters) { $this->add($this->connectionFactory->create($slaveParameters)); } } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } return array_values($this->slaves ?: array()); } protected function pickSlave() { if ($slaves = $this->getSlaves()) { return $slaves[rand(1, count($slaves)) - 1]; } } private function getConnectionInternal(CommandInterface $command) { if (!$this->current) { if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { $this->current = $slave; } else { $this->current = $this->getMaster(); } return $this->current; } if ($this->current === $this->master) { return $this->current; } if (!$this->strategy->isReadOperation($command)) { $this->current = $this->getMaster(); } return $this->current; } protected function assertConnectionRole(NodeConnectionInterface $connection, $role) { $role = strtolower($role); $actualRole = $connection->executeCommand(RawCommand::create('ROLE')); if ($actualRole instanceof Error) { throw new ConnectionException($connection, $actualRole->getMessage()); } if ($role !== $actualRole[0]) { throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]"); } } public function getConnection(CommandInterface $command) { $connection = $this->getConnectionInternal($command); if (!$connection->isConnected()) { $expectedRole = $this->strategy->isReadOperation($command) && $this->slaves ? 'slave' : 'master'; $this->assertConnectionRole($connection, $expectedRole); } return $connection; } public function getConnectionById($connectionId) { if ($connectionId === 'master') { return $this->getMaster(); } $this->getSlaves(); if (isset($this->slaves[$connectionId])) { return $this->slaves[$connectionId]; } } public function switchTo($connection) { if (!$connection instanceof NodeConnectionInterface) { $connection = $this->getConnectionById($connection); } if ($connection && $connection === $this->current) { return; } if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } $connection->connect(); if ($this->current) { $this->current->disconnect(); } $this->current = $connection; } public function switchToMaster() { $this->switchTo('master'); } public function switchToSlave() { $connection = $this->pickSlave(); $this->switchTo($connection); } public function isConnected() { return $this->current ? $this->current->isConnected() : false; } public function connect() { if (!$this->current) { if (!$this->current = $this->pickSlave()) { $this->current = $this->getMaster(); } } $this->current->connect(); } public function disconnect() { if ($this->master) { $this->master->disconnect(); } foreach ($this->slaves as $connection) { $connection->disconnect(); } } private function retryCommandOnFailure(CommandInterface $command, $method) { $retries = 0; SENTINEL_RETRY: { try { $response = $this->getConnection($command)->$method($command); } catch (CommunicationException $exception) { $this->wipeServerList(); $exception->getConnection()->disconnect(); if ($retries == $this->retryLimit) { throw $exception; } usleep($this->retryWait * 1000); ++$retries; goto SENTINEL_RETRY; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function getReplicationStrategy() { return $this->strategy; } public function __sleep() { return array( 'master', 'slaves', 'service', 'sentinels', 'connectionFactory', 'strategy', ); } } connections = $connections; $this->strategy = $strategy ?: new RedisClusterStrategy(); } public function setRetryLimit($retry) { $this->retryLimit = (int) $retry; } public function isConnected() { foreach ($this->pool as $connection) { if ($connection->isConnected()) { return true; } } return false; } public function connect() { if ($connection = $this->getRandomConnection()) { $connection->connect(); } } public function disconnect() { foreach ($this->pool as $connection) { $connection->disconnect(); } } public function add(NodeConnectionInterface $connection) { $this->pool[(string) $connection] = $connection; unset($this->slotsMap); } public function remove(NodeConnectionInterface $connection) { if (false !== $id = array_search($connection, $this->pool, true)) { unset( $this->pool[$id], $this->slotsMap ); $this->slots = array_diff($this->slots, array($connection)); return true; } return false; } public function removeById($connectionID) { if (isset($this->pool[$connectionID])) { unset( $this->pool[$connectionID], $this->slotsMap ); return true; } return false; } public function buildSlotsMap() { $this->slotsMap = array(); foreach ($this->pool as $connectionID => $connection) { $parameters = $connection->getParameters(); if (!isset($parameters->slots)) { continue; } foreach (explode(',', $parameters->slots) as $slotRange) { $slots = explode('-', $slotRange, 2); if (!isset($slots[1])) { $slots[1] = $slots[0]; } $this->setSlots($slots[0], $slots[1], $connectionID); } } return $this->slotsMap; } private function queryClusterNodeForSlotsMap(NodeConnectionInterface $connection) { $retries = 0; $command = RawCommand::create('CLUSTER', 'SLOTS'); RETRY_COMMAND: { try { $response = $connection->executeCommand($command); } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); $this->remove($connection); if ($retries === $this->retryLimit) { throw $exception; } if (!$connection = $this->getRandomConnection()) { throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`'); } ++$retries; goto RETRY_COMMAND; } } return $response; } public function askSlotsMap(NodeConnectionInterface $connection = null) { if (!$connection && !$connection = $this->getRandomConnection()) { return array(); } $this->resetSlotsMap(); $response = $this->queryClusterNodeForSlotsMap($connection); foreach ($response as $slots) { list($start, $end, $master) = $slots; if ($master[0] === '') { $this->setSlots($start, $end, (string) $connection); } else { $this->setSlots($start, $end, "{$master[0]}:{$master[1]}"); } } return $this->slotsMap; } public function resetSlotsMap() { $this->slotsMap = array(); } public function getSlotsMap() { if (!isset($this->slotsMap)) { $this->slotsMap = array(); } return $this->slotsMap; } public function setSlots($first, $last, $connection) { if ($first < 0x0000 || $first > 0x3FFF || $last < 0x0000 || $last > 0x3FFF || $last < $first ) { throw new \OutOfBoundsException( "Invalid slot range for $connection: [$first-$last]." ); } $slots = array_fill($first, $last - $first + 1, (string) $connection); $this->slotsMap = $this->getSlotsMap() + $slots; } protected function guessNode($slot) { if (!$this->pool) { throw new ClientException('No connections available in the pool'); } if (!isset($this->slotsMap)) { $this->buildSlotsMap(); } if (isset($this->slotsMap[$slot])) { return $this->slotsMap[$slot]; } $count = count($this->pool); $index = min((int) ($slot / (int) (16384 / $count)), $count - 1); $nodes = array_keys($this->pool); return $nodes[$index]; } protected function createConnection($connectionID) { $separator = strrpos($connectionID, ':'); return $this->connections->create(array( 'host' => substr($connectionID, 0, $separator), 'port' => substr($connectionID, $separator + 1), )); } public function getConnection(CommandInterface $command) { $slot = $this->strategy->getSlot($command); if (!isset($slot)) { throw new NotSupportedException( "Cannot use '{$command->getId()}' with redis-cluster." ); } if (isset($this->slots[$slot])) { return $this->slots[$slot]; } else { return $this->getConnectionBySlot($slot); } } public function getConnectionBySlot($slot) { if ($slot < 0x0000 || $slot > 0x3FFF) { throw new \OutOfBoundsException("Invalid slot [$slot]."); } if (isset($this->slots[$slot])) { return $this->slots[$slot]; } $connectionID = $this->guessNode($slot); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); $this->pool[$connectionID] = $connection; } return $this->slots[$slot] = $connection; } public function getConnectionById($connectionID) { if (isset($this->pool[$connectionID])) { return $this->pool[$connectionID]; } } protected function getRandomConnection() { if ($this->pool) { return $this->pool[array_rand($this->pool)]; } } protected function move(NodeConnectionInterface $connection, $slot) { $this->pool[(string) $connection] = $connection; $this->slots[(int) $slot] = $connection; } protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error) { $details = explode(' ', $error->getMessage(), 2); switch ($details[0]) { case 'MOVED': return $this->onMovedResponse($command, $details[1]); case 'ASK': return $this->onAskResponse($command, $details[1]); default: return $error; } } protected function onMovedResponse(CommandInterface $command, $details) { list($slot, $connectionID) = explode(' ', $details, 2); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); } if ($this->useClusterSlots) { $this->askSlotsMap($connection); } $this->move($connection, $slot); $response = $this->executeCommand($command); return $response; } protected function onAskResponse(CommandInterface $command, $details) { list($slot, $connectionID) = explode(' ', $details, 2); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); } $connection->executeCommand(RawCommand::create('ASKING')); $response = $connection->executeCommand($command); return $response; } private function retryCommandOnFailure(CommandInterface $command, $method) { $failure = false; RETRY_COMMAND: { try { $response = $this->getConnection($command)->$method($command); } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); $this->remove($connection); if ($failure) { throw $exception; } elseif ($this->useClusterSlots) { $this->askSlotsMap(); } $failure = true; goto RETRY_COMMAND; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { $response = $this->retryCommandOnFailure($command, __FUNCTION__); if ($response instanceof ErrorResponseInterface) { return $this->onErrorResponse($command, $response); } return $response; } #[\ReturnTypeWillChange] public function count() { return count($this->pool); } #[\ReturnTypeWillChange] public function getIterator() { if ($this->useClusterSlots) { $slotsmap = $this->getSlotsMap() ?: $this->askSlotsMap(); } else { $slotsmap = $this->getSlotsMap() ?: $this->buildSlotsMap(); } $connections = array(); foreach (array_unique($slotsmap) as $node) { if (!$connection = $this->getConnectionById($node)) { $this->add($connection = $this->createConnection($node)); } $connections[] = $connection; } return new \ArrayIterator($connections); } public function getClusterStrategy() { return $this->strategy; } public function getConnectionFactory() { return $this->connections; } public function useClusterSlots($value) { $this->useClusterSlots = (bool) $value; } } distributor = $distributor ?: new HashRing(); } public function getSlotByKey($key) { $key = $this->extractKeyTag($key); $hash = $this->distributor->hash($key); $slot = $this->distributor->getSlot($hash); return $slot; } protected function checkSameSlotForKeys(array $keys) { if (!$count = count($keys)) { return false; } $currentKey = $this->extractKeyTag($keys[0]); for ($i = 1; $i < $count; ++$i) { $nextKey = $this->extractKeyTag($keys[$i]); if ($currentKey !== $nextKey) { return false; } $currentKey = $nextKey; } return true; } public function getDistributor() { return $this->distributor; } } > 8) ^ ord($value[$i])]) & 0xFFFF; } return $crc; } } hashGenerator = $hashGenerator ?: new CRC16(); } public function getSlotByKey($key) { $key = $this->extractKeyTag($key); $slot = $this->hashGenerator->hash($key) & 0x3FFF; return $slot; } public function getDistributor() { throw new NotSupportedException( 'This cluster strategy does not provide an external distributor' ); } } commands = $this->getDefaultCommands(); } protected function getDefaultCommands() { $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument'); $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments'); return array( 'EXISTS' => $getKeyFromAllArguments, 'DEL' => $getKeyFromAllArguments, 'TYPE' => $getKeyFromFirstArgument, 'EXPIRE' => $getKeyFromFirstArgument, 'EXPIREAT' => $getKeyFromFirstArgument, 'PERSIST' => $getKeyFromFirstArgument, 'PEXPIRE' => $getKeyFromFirstArgument, 'PEXPIREAT' => $getKeyFromFirstArgument, 'TTL' => $getKeyFromFirstArgument, 'PTTL' => $getKeyFromFirstArgument, 'SORT' => array($this, 'getKeyFromSortCommand'), 'DUMP' => $getKeyFromFirstArgument, 'RESTORE' => $getKeyFromFirstArgument, 'APPEND' => $getKeyFromFirstArgument, 'DECR' => $getKeyFromFirstArgument, 'DECRBY' => $getKeyFromFirstArgument, 'GET' => $getKeyFromFirstArgument, 'GETBIT' => $getKeyFromFirstArgument, 'MGET' => $getKeyFromAllArguments, 'SET' => $getKeyFromFirstArgument, 'GETRANGE' => $getKeyFromFirstArgument, 'GETSET' => $getKeyFromFirstArgument, 'INCR' => $getKeyFromFirstArgument, 'INCRBY' => $getKeyFromFirstArgument, 'INCRBYFLOAT' => $getKeyFromFirstArgument, 'SETBIT' => $getKeyFromFirstArgument, 'SETEX' => $getKeyFromFirstArgument, 'MSET' => array($this, 'getKeyFromInterleavedArguments'), 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'), 'SETNX' => $getKeyFromFirstArgument, 'SETRANGE' => $getKeyFromFirstArgument, 'STRLEN' => $getKeyFromFirstArgument, 'SUBSTR' => $getKeyFromFirstArgument, 'BITOP' => array($this, 'getKeyFromBitOp'), 'BITCOUNT' => $getKeyFromFirstArgument, 'BITFIELD' => $getKeyFromFirstArgument, 'LINSERT' => $getKeyFromFirstArgument, 'LINDEX' => $getKeyFromFirstArgument, 'LLEN' => $getKeyFromFirstArgument, 'LPOP' => $getKeyFromFirstArgument, 'RPOP' => $getKeyFromFirstArgument, 'RPOPLPUSH' => $getKeyFromAllArguments, 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'), 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'), 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'), 'LPUSH' => $getKeyFromFirstArgument, 'LPUSHX' => $getKeyFromFirstArgument, 'RPUSH' => $getKeyFromFirstArgument, 'RPUSHX' => $getKeyFromFirstArgument, 'LRANGE' => $getKeyFromFirstArgument, 'LREM' => $getKeyFromFirstArgument, 'LSET' => $getKeyFromFirstArgument, 'LTRIM' => $getKeyFromFirstArgument, 'SADD' => $getKeyFromFirstArgument, 'SCARD' => $getKeyFromFirstArgument, 'SDIFF' => $getKeyFromAllArguments, 'SDIFFSTORE' => $getKeyFromAllArguments, 'SINTER' => $getKeyFromAllArguments, 'SINTERSTORE' => $getKeyFromAllArguments, 'SUNION' => $getKeyFromAllArguments, 'SUNIONSTORE' => $getKeyFromAllArguments, 'SISMEMBER' => $getKeyFromFirstArgument, 'SMEMBERS' => $getKeyFromFirstArgument, 'SSCAN' => $getKeyFromFirstArgument, 'SPOP' => $getKeyFromFirstArgument, 'SRANDMEMBER' => $getKeyFromFirstArgument, 'SREM' => $getKeyFromFirstArgument, 'ZADD' => $getKeyFromFirstArgument, 'ZCARD' => $getKeyFromFirstArgument, 'ZCOUNT' => $getKeyFromFirstArgument, 'ZINCRBY' => $getKeyFromFirstArgument, 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), 'ZRANGE' => $getKeyFromFirstArgument, 'ZRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZRANK' => $getKeyFromFirstArgument, 'ZREM' => $getKeyFromFirstArgument, 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument, 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZREVRANGE' => $getKeyFromFirstArgument, 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZREVRANK' => $getKeyFromFirstArgument, 'ZSCORE' => $getKeyFromFirstArgument, 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), 'ZSCAN' => $getKeyFromFirstArgument, 'ZLEXCOUNT' => $getKeyFromFirstArgument, 'ZRANGEBYLEX' => $getKeyFromFirstArgument, 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument, 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument, 'HDEL' => $getKeyFromFirstArgument, 'HEXISTS' => $getKeyFromFirstArgument, 'HGET' => $getKeyFromFirstArgument, 'HGETALL' => $getKeyFromFirstArgument, 'HMGET' => $getKeyFromFirstArgument, 'HMSET' => $getKeyFromFirstArgument, 'HINCRBY' => $getKeyFromFirstArgument, 'HINCRBYFLOAT' => $getKeyFromFirstArgument, 'HKEYS' => $getKeyFromFirstArgument, 'HLEN' => $getKeyFromFirstArgument, 'HSET' => $getKeyFromFirstArgument, 'HSETNX' => $getKeyFromFirstArgument, 'HVALS' => $getKeyFromFirstArgument, 'HSCAN' => $getKeyFromFirstArgument, 'HSTRLEN' => $getKeyFromFirstArgument, 'PFADD' => $getKeyFromFirstArgument, 'PFCOUNT' => $getKeyFromAllArguments, 'PFMERGE' => $getKeyFromAllArguments, 'EVAL' => array($this, 'getKeyFromScriptingCommands'), 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'), 'GEOADD' => $getKeyFromFirstArgument, 'GEOHASH' => $getKeyFromFirstArgument, 'GEOPOS' => $getKeyFromFirstArgument, 'GEODIST' => $getKeyFromFirstArgument, 'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'), 'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'), ); } public function getSupportedCommands() { return array_keys($this->commands); } public function setCommandHandler($commandID, $callback = null) { $commandID = strtoupper($commandID); if (!isset($callback)) { unset($this->commands[$commandID]); return; } if (!is_callable($callback)) { throw new \InvalidArgumentException( 'The argument must be a callable object or NULL.' ); } $this->commands[$commandID] = $callback; } protected function getKeyFromFirstArgument(CommandInterface $command) { return $command->getArgument(0); } protected function getKeyFromAllArguments(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys($arguments)) { return $arguments[0]; } } protected function getKeyFromInterleavedArguments(CommandInterface $command) { $arguments = $command->getArguments(); $keys = array(); for ($i = 0; $i < count($arguments); $i += 2) { $keys[] = $arguments[$i]; } if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } } protected function getKeyFromSortCommand(CommandInterface $command) { $arguments = $command->getArguments(); $firstKey = $arguments[0]; if (1 === $argc = count($arguments)) { return $firstKey; } $keys = array($firstKey); for ($i = 1; $i < $argc; ++$i) { if (strtoupper($arguments[$i]) === 'STORE') { $keys[] = $arguments[++$i]; } } if ($this->checkSameSlotForKeys($keys)) { return $firstKey; } } protected function getKeyFromBlockingListCommands(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { return $arguments[0]; } } protected function getKeyFromBitOp(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { return $arguments[1]; } } protected function getKeyFromGeoradiusCommands(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if ($argc > $startIndex) { $keys = array($arguments[0]); for ($i = $startIndex; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'STORE' || $argument === 'STOREDIST') { $keys[] = $arguments[++$i]; } } if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } else { return; } } return $arguments[0]; } protected function getKeyFromZsetAggregationCommands(CommandInterface $command) { $arguments = $command->getArguments(); $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1])); if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } } protected function getKeyFromScriptingCommands(CommandInterface $command) { if ($command instanceof ScriptCommand) { $keys = $command->getKeys(); } else { $keys = array_slice($args = $command->getArguments(), 2, $args[1]); } if ($keys && $this->checkSameSlotForKeys($keys)) { return $keys[0]; } } public function getSlot(CommandInterface $command) { $slot = $command->getSlot(); if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) { $key = call_user_func($this->commands[$cmdID], $command); if (isset($key)) { $slot = $this->getSlotByKey($key); $command->setSlot($slot); } } return $slot; } protected function checkSameSlotForKeys(array $keys) { if (!$count = count($keys)) { return false; } $currentSlot = $this->getSlotByKey($keys[0]); for ($i = 1; $i < $count; ++$i) { $nextSlot = $this->getSlotByKey($keys[$i]); if ($currentSlot !== $nextSlot) { return false; } $currentSlot = $nextSlot; } return true; } protected function extractKeyTag($key) { if (false !== $start = strpos($key, '{')) { if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) { $key = substr($key, $start, $end - $start); } } return $key; } } replicas = $replicas; $this->nodeHashCallback = $nodeHashCallback; } public function add($node, $weight = null) { $this->nodes[] = array( 'object' => $node, 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT, ); $this->reset(); } public function remove($node) { for ($i = 0; $i < count($this->nodes); ++$i) { if ($this->nodes[$i]['object'] === $node) { array_splice($this->nodes, $i, 1); $this->reset(); break; } } } private function reset() { unset( $this->ring, $this->ringKeys, $this->ringKeysCount ); } private function isInitialized() { return isset($this->ringKeys); } private function computeTotalWeight() { $totalWeight = 0; foreach ($this->nodes as $node) { $totalWeight += $node['weight']; } return $totalWeight; } private function initialize() { if ($this->isInitialized()) { return; } if (!$this->nodes) { throw new EmptyRingException('Cannot initialize an empty hashring.'); } $this->ring = array(); $totalWeight = $this->computeTotalWeight(); $nodesCount = count($this->nodes); foreach ($this->nodes as $node) { $weightRatio = $node['weight'] / $totalWeight; $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio); } ksort($this->ring, SORT_NUMERIC); $this->ringKeys = array_keys($this->ring); $this->ringKeysCount = count($this->ringKeys); } protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) { $nodeObject = $node['object']; $nodeHash = $this->getNodeHash($nodeObject); $replicas = (int) round($weightRatio * $totalNodes * $replicas); for ($i = 0; $i < $replicas; ++$i) { $key = $this->hash("$nodeHash:$i"); $ring[$key] = $nodeObject; } } protected function getNodeHash($nodeObject) { if (!isset($this->nodeHashCallback)) { return (string) $nodeObject; } return call_user_func($this->nodeHashCallback, $nodeObject); } public function hash($value) { return crc32($value); } public function getByHash($hash) { return $this->ring[$this->getSlot($hash)]; } public function getBySlot($slot) { $this->initialize(); if (isset($this->ring[$slot])) { return $this->ring[$slot]; } } public function getSlot($hash) { $this->initialize(); $ringKeys = $this->ringKeys; $upper = $this->ringKeysCount - 1; $lower = 0; while ($lower <= $upper) { $index = ($lower + $upper) >> 1; $item = $ringKeys[$index]; if ($item > $hash) { $upper = $index - 1; } elseif ($item < $hash) { $lower = $index + 1; } else { return $item; } } return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)]; } public function get($value) { $hash = $this->hash($value); $node = $this->getByHash($hash); return $node; } protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { return $upper >= 0 ? $upper : $ringKeysCount - 1; } public function getHashGenerator() { return $this; } } getNodeHash($nodeObject); $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); for ($i = 0; $i < $replicas; ++$i) { $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); foreach ($unpackedDigest as $key) { $ring[$key] = $nodeObject; } } } public function hash($value) { $hash = unpack('V', md5($value, true)); return $hash[1]; } protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { return $lower < $ringKeysCount ? $lower : 0; } } setRequestSerializer($serializer ?: new RequestSerializer()); $this->setResponseReader($reader ?: new ResponseReader()); } public function write(CompositeConnectionInterface $connection, CommandInterface $command) { $connection->writeBuffer($this->serializer->serialize($command)); } public function read(CompositeConnectionInterface $connection) { return $this->reader->read($connection); } public function setRequestSerializer(RequestSerializerInterface $serializer) { $this->serializer = $serializer; } public function getRequestSerializer() { return $this->serializer; } public function setResponseReader(ResponseReaderInterface $reader) { $this->reader = $reader; } public function getResponseReader() { return $this->reader; } } mbiterable = false; $this->serializer = new RequestSerializer(); } public function write(CompositeConnectionInterface $connection, CommandInterface $command) { $request = $this->serializer->serialize($command); $connection->writeBuffer($request); } public function read(CompositeConnectionInterface $connection) { $chunk = $connection->readLine(); $prefix = $chunk[0]; $payload = substr($chunk, 1); switch ($prefix) { case '+': return new StatusResponse($payload); case '$': $size = (int) $payload; if ($size === -1) { return; } return substr($connection->readBuffer($size + 2), 0, -2); case '*': $count = (int) $payload; if ($count === -1) { return; } if ($this->mbiterable) { return new MultiBulkIterator($connection, $count); } $multibulk = array(); for ($i = 0; $i < $count; ++$i) { $multibulk[$i] = $this->read($connection); } return $multibulk; case ':': $integer = (int) $payload; return $integer == $payload ? $integer : $payload; case '-': return new ErrorResponse($payload); default: CommunicationException::handle(new ProtocolException( $connection, "Unknown response prefix: '$prefix'." )); return; } } public function useIterableMultibulk($value) { $this->mbiterable = (bool) $value; } } handlers = $this->getDefaultHandlers(); } protected function getDefaultHandlers() { return array( '+' => new Handler\StatusResponse(), '-' => new Handler\ErrorResponse(), ':' => new Handler\IntegerResponse(), '$' => new Handler\BulkResponse(), '*' => new Handler\MultiBulkResponse(), ); } public function setHandler($prefix, Handler\ResponseHandlerInterface $handler) { $this->handlers[$prefix] = $handler; } public function getHandler($prefix) { if (isset($this->handlers[$prefix])) { return $this->handlers[$prefix]; } return; } public function read(CompositeConnectionInterface $connection) { $header = $connection->readLine(); if ($header === '') { $this->onProtocolError($connection, 'Unexpected empty reponse header.'); } $prefix = $header[0]; if (!isset($this->handlers[$prefix])) { $this->onProtocolError($connection, "Unknown response prefix: '$prefix'."); } $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1)); return $payload; } protected function onProtocolError(CompositeConnectionInterface $connection, $message) { CommunicationException::handle( new ProtocolException($connection, $message) ); } } 0) { $handlersCache = array(); $reader = $connection->getProtocol()->getResponseReader(); for ($i = 0; $i < $length; ++$i) { $header = $connection->readLine(); $prefix = $header[0]; if (isset($handlersCache[$prefix])) { $handler = $handlersCache[$prefix]; } else { $handler = $reader->getHandler($prefix); $handlersCache[$prefix] = $handler; } $list[$i] = $handler->handle($connection, substr($header, 1)); } } return $list; } } = 0) { return substr($connection->readBuffer($length + 2), 0, -2); } if ($length == -1) { return; } CommunicationException::handle(new ProtocolException( $connection, "Value '$payload' is not a valid length for a bulk response." )); return; } } getId(); $arguments = $command->getArguments(); $cmdlen = strlen($commandID); $reqlen = count($arguments) + 1; $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; foreach ($arguments as $argument) { $arglen = strlen($argument); $buffer .= "\${$arglen}\r\n{$argument}\r\n"; } return $buffer; } } directory = $baseDirectory; $this->prefix = __NAMESPACE__.'\\'; $this->prefixLength = strlen($this->prefix); } public static function register($prepend = false) { spl_autoload_register(array(new self(), 'autoload'), true, $prepend); } public function autoload($className) { if (0 === strpos($className, $this->prefix)) { $parts = explode('\\', substr($className, $this->prefixLength)); $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; if (is_file($filepath)) { require $filepath; } } } } getMessage(), 2); return $errorType; } public function toErrorResponse() { return new Error($this->getMessage()); } } checkPreconditions($iterator); $this->size = count($iterator) / 2; $this->iterator = $iterator; $this->position = $iterator->getPosition(); $this->current = $this->size > 0 ? $this->getValue() : null; } protected function checkPreconditions(MultiBulk $iterator) { if ($iterator->getPosition() !== 0) { throw new \InvalidArgumentException( 'Cannot initialize a tuple iterator using an already initiated iterator.' ); } if (($size = count($iterator)) % 2 !== 0) { throw new \UnexpectedValueException('Invalid response size for a tuple iterator.'); } } #[\ReturnTypeWillChange] public function getInnerIterator() { return $this->iterator; } public function __destruct() { $this->iterator->drop(true); } protected function getValue() { $k = $this->iterator->current(); $this->iterator->next(); $v = $this->iterator->current(); $this->iterator->next(); return array($k, $v); } } current; } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function next() { if (++$this->position < $this->size) { $this->current = $this->getValue(); } } #[\ReturnTypeWillChange] public function valid() { return $this->position < $this->size; } #[\ReturnTypeWillChange] public function count() { return $this->size; } public function getPosition() { return $this->position; } abstract protected function getValue(); } connection = $connection; $this->size = $size; $this->position = 0; $this->current = $size > 0 ? $this->getValue() : null; } public function __destruct() { $this->drop(true); } public function drop($disconnect = false) { if ($disconnect) { if ($this->valid()) { $this->position = $this->size; $this->connection->disconnect(); } } else { while ($this->valid()) { $this->next(); } } } protected function getValue() { return $this->connection->read(); } } payload = $payload; } public function __toString() { return $this->payload; } public function getPayload() { return $this->payload; } public static function get($payload) { switch ($payload) { case 'OK': case 'QUEUED': if (isset(self::$$payload)) { return self::$$payload; } return self::$$payload = new self($payload); default: return new self($payload); } } } message = $message; } public function getMessage() { return $this->message; } public function getErrorType() { list($errorType) = explode(' ', $this->getMessage(), 2); return $errorType; } public function __toString() { return $this->getMessage(); } } getArguments(); for ($i = 3; $i < count($arguments); ++$i) { switch (strtoupper($arguments[$i])) { case 'WITHSCORES': return true; case 'LIMIT': $i += 2; break; } } return false; } } 2 && is_array($arguments[$argc - 1])) { $options = $this->prepareOptions(array_pop($arguments)); } if (is_array($arguments[1])) { $arguments = array_merge( array($arguments[0], count($arguments[1])), $arguments[1] ); } return array_merge($arguments, $options); } private function prepareOptions($options) { $opts = array_change_key_case($options, CASE_UPPER); $finalizedOpts = array(); if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) { $finalizedOpts[] = 'WEIGHTS'; foreach ($opts['WEIGHTS'] as $weight) { $finalizedOpts[] = $weight; } } if (isset($opts['AGGREGATE'])) { $finalizedOpts[] = 'AGGREGATE'; $finalizedOpts[] = $opts['AGGREGATE']; } return $finalizedOpts; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } public function parseResponse($data) { if (is_array($data)) { $fields = $data[1]; $result = array(); for ($i = 0; $i < count($fields); ++$i) { $result[$fields[$i]] = $fields[++$i]; } $data[1] = $result; } return $data; } } getArgument(0))) { case 'numsub': return self::processNumsub($data); default: return $data; } } protected static function processNumsub(array $channels) { $processed = array(); $count = count($channels); for ($i = 0; $i < $count; ++$i) { $processed[$channels[$i]] = $channels[++$i]; } return $processed; } } getArguments(), 2, $this->getKeysCount()); } protected function filterArguments(array $arguments) { if (($numkeys = $this->getKeysCount()) && $numkeys < 0) { $numkeys = count($arguments) + $numkeys; } return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments); } public function getEvalArguments() { $arguments = $this->getArguments(); $arguments[0] = $this->getScript(); return $arguments; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } } $v) { $flattenedKVs[] = $k; $flattenedKVs[] = $v; } return $flattenedKVs; } return $arguments; } } $score) { $arguments[] = $score; $arguments[] = $member; } } return $arguments; } } parseRow($row); $current[$k] = $v; } return $info; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } public function parseResponse($data) { if (is_array($data)) { $members = $data[1]; $result = array(); for ($i = 0; $i < count($members); ++$i) { $result[$members[$i]] = (float) $members[++$i]; } $data[1] = $result; } return $data; } } parseRow($row); $info[$k] = $v; } return $info; } protected function parseRow($row) { list($k, $v) = explode(':', $row, 2); if (preg_match('/^db\d+$/', $k)) { $v = $this->parseDatabaseStats($v); } return array($k, $v); } protected function parseDatabaseStats($str) { $db = array(); foreach (explode(',', $str) as $dbvar) { list($dbvk, $dbvv) = explode('=', $dbvar); $db[trim($dbvk)] = $dbvv; } return $db; } protected function parseAllocationStats($str) { $stats = array(); foreach (explode(',', $str) as $kv) { @list($size, $objects, $extra) = explode('=', $kv); if (isset($extra)) { $size = ">=$objects"; $objects = $extra; } $stats[$size] = $objects; } return $stats; } } getArgument(0)); } } $entry) { $log[$index] = array( 'id' => $entry[0], 'timestamp' => $entry[1], 'duration' => $entry[2], 'command' => $entry[3], ); } return $log; } return $data; } } getArgument(0); $argument = is_null($argument) ? null : strtolower($argument); switch ($argument) { case 'masters': case 'slaves': return self::processMastersOrSlaves($data); default: return $data; } } protected static function processMastersOrSlaves(array $servers) { foreach ($servers as $idx => $node) { $processed = array(); $count = count($node); for ($i = 0; $i < $count; ++$i) { $processed[$node[$i]] = $node[++$i]; } $servers[$idx] = $processed; } return $servers; } } getArgument(0); } } getArguments(), CASE_UPPER); switch (strtoupper($args[0])) { case 'LIST': return $this->parseClientList($data); case 'KILL': case 'GETNAME': case 'SETNAME': default: return $data; } } protected function parseClientList($data) { $clients = array(); foreach (explode("\n", $data, -1) as $clientData) { $client = array(); foreach (explode(' ', $clientData) as $kv) { @list($k, $v) = explode('=', $kv); $client[$k] = $v; } $clients[] = $client; } return $clients; } } commandID = strtoupper(array_shift($arguments)); $this->arguments = $arguments; } public static function create($commandID ) { $arguments = func_get_args(); $command = new self($arguments); return $command; } public function getId() { return $this->commandID; } public function setArguments(array $arguments) { $this->arguments = $arguments; unset($this->slot); } public function setRawArguments(array $arguments) { $this->setArguments($arguments); } public function getArguments() { return $this->arguments; } public function getArgument($index) { if (isset($this->arguments[$index])) { return $this->arguments[$index]; } } public function setSlot($slot) { $this->slot = $slot; } public function getSlot() { if (isset($this->slot)) { return $this->slot; } } public function parseResponse($data) { return $data; } } $value) { $modifier = strtoupper($modifier); if ($modifier === 'COPY' && $value == true) { $arguments[] = $modifier; } if ($modifier === 'REPLACE' && $value == true) { $arguments[] = $modifier; } } } return $arguments; } } true); $lastType = 'array'; } if ($lastType === 'array') { $options = $this->prepareOptions(array_pop($arguments)); return array_merge($arguments, $options); } } return $arguments; } protected function prepareOptions($options) { $opts = array_change_key_case($options, CASE_UPPER); $finalizedOpts = array(); if (!empty($opts['WITHSCORES'])) { $finalizedOpts[] = 'WITHSCORES'; } return $finalizedOpts; } protected function withScores() { $arguments = $this->getArguments(); if (count($arguments) < 4) { return false; } return strtoupper($arguments[3]) === 'WITHSCORES'; } public function parseResponse($data) { if ($this->withScores()) { $result = array(); for ($i = 0; $i < count($data); ++$i) { $result[$data[$i]] = $data[++$i]; } return $result; } return $data; } } arguments = $this->filterArguments($arguments); unset($this->slot); } public function setRawArguments(array $arguments) { $this->arguments = $arguments; unset($this->slot); } public function getArguments() { return $this->arguments; } public function getArgument($index) { if (isset($this->arguments[$index])) { return $this->arguments[$index]; } } public function setSlot($slot) { $this->slot = $slot; } public function getSlot() { if (isset($this->slot)) { return $this->slot; } } public function parseResponse($data) { return $data; } public static function normalizeArguments(array $arguments) { if (count($arguments) === 1 && isset($arguments[0]) && is_array($arguments[0])) { return $arguments[0]; } return $arguments; } public static function normalizeVariadic(array $arguments) { if (count($arguments) === 2 && is_array($arguments[1])) { return array_merge(array($arguments[0]), $arguments[1]); } return $arguments; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } } $v) { $flattenedKVs[] = $k; $flattenedKVs[] = $v; } return $flattenedKVs; } return $arguments; } } prefix = $prefix; $this->commands = array( 'EXISTS' => 'static::all', 'DEL' => 'static::all', 'TYPE' => 'static::first', 'KEYS' => 'static::first', 'RENAME' => 'static::all', 'RENAMENX' => 'static::all', 'EXPIRE' => 'static::first', 'EXPIREAT' => 'static::first', 'TTL' => 'static::first', 'MOVE' => 'static::first', 'SORT' => 'static::sort', 'DUMP' => 'static::first', 'RESTORE' => 'static::first', 'SET' => 'static::first', 'SETNX' => 'static::first', 'MSET' => 'static::interleaved', 'MSETNX' => 'static::interleaved', 'GET' => 'static::first', 'MGET' => 'static::all', 'GETSET' => 'static::first', 'INCR' => 'static::first', 'INCRBY' => 'static::first', 'DECR' => 'static::first', 'DECRBY' => 'static::first', 'RPUSH' => 'static::first', 'LPUSH' => 'static::first', 'LLEN' => 'static::first', 'LRANGE' => 'static::first', 'LTRIM' => 'static::first', 'LINDEX' => 'static::first', 'LSET' => 'static::first', 'LREM' => 'static::first', 'LPOP' => 'static::first', 'RPOP' => 'static::first', 'RPOPLPUSH' => 'static::all', 'SADD' => 'static::first', 'SREM' => 'static::first', 'SPOP' => 'static::first', 'SMOVE' => 'static::skipLast', 'SCARD' => 'static::first', 'SISMEMBER' => 'static::first', 'SINTER' => 'static::all', 'SINTERSTORE' => 'static::all', 'SUNION' => 'static::all', 'SUNIONSTORE' => 'static::all', 'SDIFF' => 'static::all', 'SDIFFSTORE' => 'static::all', 'SMEMBERS' => 'static::first', 'SRANDMEMBER' => 'static::first', 'ZADD' => 'static::first', 'ZINCRBY' => 'static::first', 'ZREM' => 'static::first', 'ZRANGE' => 'static::first', 'ZREVRANGE' => 'static::first', 'ZRANGEBYSCORE' => 'static::first', 'ZCARD' => 'static::first', 'ZSCORE' => 'static::first', 'ZREMRANGEBYSCORE' => 'static::first', 'SETEX' => 'static::first', 'APPEND' => 'static::first', 'SUBSTR' => 'static::first', 'BLPOP' => 'static::skipLast', 'BRPOP' => 'static::skipLast', 'ZUNIONSTORE' => 'static::zsetStore', 'ZINTERSTORE' => 'static::zsetStore', 'ZCOUNT' => 'static::first', 'ZRANK' => 'static::first', 'ZREVRANK' => 'static::first', 'ZREMRANGEBYRANK' => 'static::first', 'HSET' => 'static::first', 'HSETNX' => 'static::first', 'HMSET' => 'static::first', 'HINCRBY' => 'static::first', 'HGET' => 'static::first', 'HMGET' => 'static::first', 'HDEL' => 'static::first', 'HEXISTS' => 'static::first', 'HLEN' => 'static::first', 'HKEYS' => 'static::first', 'HVALS' => 'static::first', 'HGETALL' => 'static::first', 'SUBSCRIBE' => 'static::all', 'UNSUBSCRIBE' => 'static::all', 'PSUBSCRIBE' => 'static::all', 'PUNSUBSCRIBE' => 'static::all', 'PUBLISH' => 'static::first', 'PERSIST' => 'static::first', 'STRLEN' => 'static::first', 'SETRANGE' => 'static::first', 'GETRANGE' => 'static::first', 'SETBIT' => 'static::first', 'GETBIT' => 'static::first', 'RPUSHX' => 'static::first', 'LPUSHX' => 'static::first', 'LINSERT' => 'static::first', 'BRPOPLPUSH' => 'static::skipLast', 'ZREVRANGEBYSCORE' => 'static::first', 'WATCH' => 'static::all', 'PTTL' => 'static::first', 'PEXPIRE' => 'static::first', 'PEXPIREAT' => 'static::first', 'PSETEX' => 'static::first', 'INCRBYFLOAT' => 'static::first', 'BITOP' => 'static::skipFirst', 'BITCOUNT' => 'static::first', 'HINCRBYFLOAT' => 'static::first', 'EVAL' => 'static::evalKeys', 'EVALSHA' => 'static::evalKeys', 'MIGRATE' => 'static::migrate', 'SSCAN' => 'static::first', 'ZSCAN' => 'static::first', 'HSCAN' => 'static::first', 'PFADD' => 'static::first', 'PFCOUNT' => 'static::all', 'PFMERGE' => 'static::all', 'ZLEXCOUNT' => 'static::first', 'ZRANGEBYLEX' => 'static::first', 'ZREMRANGEBYLEX' => 'static::first', 'ZREVRANGEBYLEX' => 'static::first', 'BITPOS' => 'static::first', 'HSTRLEN' => 'static::first', 'BITFIELD' => 'static::first', 'GEOADD' => 'static::first', 'GEOHASH' => 'static::first', 'GEOPOS' => 'static::first', 'GEODIST' => 'static::first', 'GEORADIUS' => 'static::georadius', 'GEORADIUSBYMEMBER' => 'static::georadius', ); } public function setPrefix($prefix) { $this->prefix = $prefix; } public function getPrefix() { return $this->prefix; } public function process(CommandInterface $command) { if ($command instanceof PrefixableCommandInterface) { $command->prefixKeys($this->prefix); } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) { call_user_func($this->commands[$commandID], $command, $this->prefix); } } public function setCommandHandler($commandID, $callback = null) { $commandID = strtoupper($commandID); if (!isset($callback)) { unset($this->commands[$commandID]); return; } if (!is_callable($callback)) { throw new \InvalidArgumentException( 'Callback must be a valid callable object or NULL' ); } $this->commands[$commandID] = $callback; } public function __toString() { return $this->getPrefix(); } public static function first(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $command->setRawArguments($arguments); } } public static function all(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { foreach ($arguments as &$key) { $key = "$prefix$key"; } $command->setRawArguments($arguments); } } public static function interleaved(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 0; $i < $length; $i += 2) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function skipFirst(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 1; $i < $length; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function skipLast(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 0; $i < $length - 1; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function sort(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; if (($count = count($arguments)) > 1) { for ($i = 1; $i < $count; ++$i) { switch (strtoupper($arguments[$i])) { case 'BY': case 'STORE': $arguments[$i] = "$prefix{$arguments[++$i]}"; break; case 'GET': $value = $arguments[++$i]; if ($value !== '#') { $arguments[$i] = "$prefix$value"; } break; case 'LIMIT'; $i += 2; break; } } } $command->setRawArguments($arguments); } } public static function evalKeys(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { for ($i = 2; $i < $arguments[1] + 2; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function zsetStore(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $length = ((int) $arguments[1]) + 2; for ($i = 2; $i < $length; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function migrate(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[2] = "$prefix{$arguments[2]}"; $command->setRawArguments($arguments); } } public static function georadius(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if (($count = count($arguments)) > $startIndex) { for ($i = $startIndex; $i < $count; ++$i) { switch (strtoupper($arguments[$i])) { case 'STORE': case 'STOREDIST': $arguments[$i] = "$prefix{$arguments[++$i]}"; break; } } } $command->setRawArguments($arguments); } } } add($processor); } } public function add(ProcessorInterface $processor) { $this->processors[] = $processor; } public function remove(ProcessorInterface $processor) { if (false !== $index = array_search($processor, $this->processors, true)) { unset($this[$index]); } } public function process(CommandInterface $command) { for ($i = 0; $i < $count = count($this->processors); ++$i) { $this->processors[$i]->process($command); } } public function getProcessors() { return $this->processors; } public function getIterator() { return new \ArrayIterator($this->processors); } public function count() { return count($this->processors); } #[\ReturnTypeWillChange] public function offsetExists($index) { return isset($this->processors[$index]); } #[\ReturnTypeWillChange] public function offsetGet($index) { return $this->processors[$index]; } #[\ReturnTypeWillChange] public function offsetSet($index, $processor) { if (!$processor instanceof ProcessorInterface) { throw new \InvalidArgumentException( 'A processor chain accepts only instances of '. "'Predis\Command\Processor\ProcessorInterface'." ); } $this->processors[$index] = $processor; } #[\ReturnTypeWillChange] public function offsetUnset($index) { unset($this->processors[$index]); $this->processors = array_values($this->processors); } } requiredCommand($client, 'SCAN'); parent::__construct($client, $match, $count); } protected function executeCommand() { return $this->client->scan($this->cursor, $this->getScanOptions()); } } requiredCommand($client, 'HSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions()); } protected function extractNext() { $this->position = key($this->elements); $this->current = current($this->elements); unset($this->elements[$this->position]); } } requiredCommand($client, 'LRANGE'); if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) { throw new \InvalidArgumentException('The $count argument must be a positive integer.'); } $this->client = $client; $this->key = $key; $this->count = $count; $this->reset(); } protected function requiredCommand(ClientInterface $client, $commandID) { if (!$client->getProfile()->supportsCommand($commandID)) { throw new NotSupportedException("The current profile does not support '$commandID'."); } } protected function reset() { $this->valid = true; $this->fetchmore = true; $this->elements = array(); $this->position = -1; $this->current = null; } protected function executeCommand() { return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count); } protected function fetch() { $elements = $this->executeCommand(); if (count($elements) < $this->count) { $this->fetchmore = false; } $this->elements = $elements; } protected function extractNext() { ++$this->position; $this->current = array_shift($this->elements); } #[\ReturnTypeWillChange] public function rewind() { $this->reset(); $this->next(); } #[\ReturnTypeWillChange] public function current() { return $this->current; } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function next() { if (!$this->elements && $this->fetchmore) { $this->fetch(); } if ($this->elements) { $this->extractNext(); } else { $this->valid = false; } } #[\ReturnTypeWillChange] public function valid() { return $this->valid; } } requiredCommand($client, 'ZSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions()); } protected function extractNext() { $this->position = key($this->elements); $this->current = current($this->elements); unset($this->elements[$this->position]); } } client = $client; $this->match = $match; $this->count = $count; $this->reset(); } protected function requiredCommand(ClientInterface $client, $commandID) { if (!$client->getProfile()->supportsCommand($commandID)) { throw new NotSupportedException("The current profile does not support '$commandID'."); } } protected function reset() { $this->valid = true; $this->fetchmore = true; $this->elements = array(); $this->cursor = 0; $this->position = -1; $this->current = null; } protected function getScanOptions() { $options = array(); if (strlen(strval($this->match)) > 0) { $options['MATCH'] = $this->match; } if ($this->count > 0) { $options['COUNT'] = $this->count; } return $options; } abstract protected function executeCommand(); protected function fetch() { list($cursor, $elements) = $this->executeCommand(); if (!$cursor) { $this->fetchmore = false; } $this->cursor = $cursor; $this->elements = $elements; } protected function extractNext() { ++$this->position; $this->current = array_shift($this->elements); } #[\ReturnTypeWillChange] public function rewind() { $this->reset(); $this->next(); } #[\ReturnTypeWillChange] public function current() { return $this->current; } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function next() { tryFetch: { if (!$this->elements && $this->fetchmore) { $this->fetch(); } if ($this->elements) { $this->extractNext(); } elseif ($this->cursor) { goto tryFetch; } else { $this->valid = false; } } } #[\ReturnTypeWillChange] public function valid() { return $this->valid; } } requiredCommand($client, 'SSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions()); } } connection = $connection; } public function getConnection() { return $this->connection; } public function shouldResetConnection() { return true; } public static function handle(CommunicationException $exception) { if ($exception->shouldResetConnection()) { $connection = $exception->getConnection(); if ($connection->isConnected()) { $connection->disconnect(); } } throw $exception; } } function ($options, $option) { $profile = $options->getDefault($option); $profile->defineCommand('hmgetall', 'HashMultipleGetAll'); return $profile; }, 'replication' => function () { $strategy = new ReplicationStrategy(); $strategy->setScriptReadOnly(HashMultipleGetAll::BODY); $replication = new MasterSlaveReplication($strategy); return $replication; }, ); $client = new Predis\Client($parameters, $options); $hashes = $client->hmgetall('metavars', 'servers'); $replication = $client->getConnection(); $stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave'); echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL; var_export($hashes); 'nrk:')); $client->mset(array('foo' => 'bar', 'lol' => 'wut')); var_export($client->mget('foo', 'lol')); var_export($client->keys('*')); nodes = array(); $this->nodesCount = 0; } public function add($node, $weight = null) { $this->nodes[] = $node; ++$this->nodesCount; } public function remove($node) { $this->nodes = array_filter($this->nodes, function ($n) use ($node) { return $n !== $node; }); $this->nodesCount = count($this->nodes); } public function getSlot($hash) { return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0; } public function getBySlot($slot) { return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null; } public function getByHash($hash) { if (!$this->nodesCount) { throw new RuntimeException('No connections.'); } $slot = $this->getSlot($hash); $node = $this->getBySlot($slot); return $node; } public function get($value) { $hash = $this->hash($value); $node = $this->getByHash($hash); return $node; } public function hash($value) { return crc32($value); } public function getHashGenerator() { return $this; } } $options = array( 'cluster' => function () { $distributor = new NaiveDistributor(); $strategy = new PredisStrategy($distributor); $cluster = new PredisCluster($strategy); return $cluster; }, ); $client = new Predis\Client($multiple_servers, $options); for ($i = 0; $i < 100; ++$i) { $client->set("key:$i", str_pad($i, 4, '0', 0)); $client->get("key:$i"); } $server1 = $client->getClientFor('first')->info(); $server2 = $client->getClientFor('second')->info(); if (isset($server1['Keyspace'], $server2['Keyspace'])) { $server1 = $server1['Keyspace']; $server2 = $server2['Keyspace']; } printf("Server '%s' has %d keys while server '%s' has %d keys.\n", 'first', $server1['db15']['keys'], 'second', $server2['db15']['keys'] ); true, 'watch' => $key, 'retry' => 3, ); $client->transaction($options, function ($tx) use ($key, &$element) { @list($element) = $tx->zrange($key, 0, 0); if (isset($element)) { $tx->multi(); $tx->zrem($key, $element); } }); return $element; } $client = new Predis\Client($single_server); $zpopped = zpop($client, 'zset'); echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL; '127.0.0.1', 'port' => 6379, 'database' => 15, ); $multiple_servers = array( array( 'host' => '127.0.0.1', 'port' => 6379, 'database' => 15, 'alias' => 'first', ), array( 'host' => '127.0.0.1', 'port' => 6380, 'database' => 15, 'alias' => 'second', ), ); tstart = microtime(true); parent::connect(); } private function storeDebug(CommandInterface $command, $direction) { $firtsArg = $command->getArgument(0); $timestamp = round(microtime(true) - $this->tstart, 4); $debug = $command->getId(); $debug .= isset($firtsArg) ? " $firtsArg " : ' '; $debug .= "$direction $this"; $debug .= " [{$timestamp}s]"; $this->debugBuffer[] = $debug; } public function writeRequest(CommandInterface $command) { parent::writeRequest($command); $this->storeDebug($command, '->'); } public function readResponse(CommandInterface $command) { $response = parent::readResponse($command); $this->storeDebug($command, '<-'); return $response; } public function getDebugBuffer() { return $this->debugBuffer; } } $options = array( 'connections' => array( 'tcp' => 'SimpleDebuggableConnection', ), ); $client = new Predis\Client($single_server, $options); $client->set('foo', 'bar'); $client->get('foo'); $client->info(); var_export($client->getConnection()->getDebugBuffer()); '2.8')); $client->del('predis:set', 'predis:zset', 'predis:hash'); for ($i = 0; $i < 5; ++$i) { $client->sadd('predis:set', "member:$i"); $client->zadd('predis:zset', -$i, "member:$i"); $client->hset('predis:hash', "field:$i", "value:$i"); } echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL; foreach (new Iterator\Keyspace($client, 'predis:*') as $key) { echo " - $key", PHP_EOL; } echo 'Scan members of `predis:set`:', PHP_EOL; foreach (new Iterator\SetKey($client, 'predis:set') as $member) { echo " - $member", PHP_EOL; } echo 'Scan members and ranks of `predis:zset`:', PHP_EOL; foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) { echo " - $member [rank: $rank]", PHP_EOL; } echo 'Scan fields and values of `predis:hash`:', PHP_EOL; foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) { echo " - $field => $value", PHP_EOL; } pipeline(function ($pipe) { $pipe->flushdb(); $pipe->incrby('counter', 10); $pipe->incrby('counter', 30); $pipe->exists('counter'); $pipe->get('counter'); $pipe->mget('does_not_exist', 'counter'); }); var_export($responses); 0)); $timestamp = new DateTime(); foreach (($monitor = $client->monitor()) as $event) { $timestamp->setTimestamp((int) $event->timestamp); if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') { echo 'Exiting the monitor loop...', PHP_EOL; $monitor->stop(); break; } echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL; if (isset($event->arguments)) { echo " Arguments: {$event->arguments}", PHP_EOL; } } $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; 'sentinel', 'service' => 'mymaster', )); $exists = $client->exists('foo') ? 'yes' : 'no'; $current = $client->getConnection()->getCurrent()->getParameters(); echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; $client->set('foo', 'bar'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; $bar = $client->get('foo'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; 0)); $pubsub = $client->pubSubLoop(); $dispatcher = new Predis\PubSub\DispatcherLoop($pubsub); class EventsListener implements Countable { private $events; public function __construct() { $this->events = array(); } public function count() { return count($this->events); } public function getEvents() { return $this->events; } public function __invoke($payload) { $this->events[] = $payload; } } $dispatcher->attachCallback('events', ($events = new EventsListener())); $dispatcher->attachCallback('control', function ($payload) use ($dispatcher) { if ($payload === 'terminate_dispatcher') { $dispatcher->stop(); } }); $dispatcher->run(); echo "We received {$events->count()} messages!", PHP_EOL; $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; true); $client = new Predis\Client($parameters, $options); $exists = $client->exists('foo') ? 'yes' : 'no'; $current = $client->getConnection()->getCurrent()->getParameters(); echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; $client->set('foo', 'bar'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; $bar = $client->get('foo'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; function ($options) { $profile = $options->getDefault('profile'); $profile->defineCommand('increxby', 'IncrementExistingKeysBy'); return $profile; }, )); $client->mset('foo', 10, 'foobar', 100); var_export($client->increxby('foo', 'foofoo', 'foobar', 50)); 0)); $pubsub = $client->pubSubLoop(); $pubsub->subscribe('control_channel', 'notifications'); foreach ($pubsub as $message) { switch ($message->kind) { case 'subscribe': echo "Subscribed to {$message->channel}", PHP_EOL; break; case 'message': if ($message->channel == 'control_channel') { if ($message->payload == 'quit_loop') { echo 'Aborting pubsub loop...', PHP_EOL; $pubsub->unsubscribe(); } else { echo "Received an unrecognized command: {$message->payload}.", PHP_EOL; } } else { echo "Received the following message from {$message->channel}:", PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL; } break; } } unset($pubsub); $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; set('library', 'predis'); $response = $client->get('library'); var_export($response); echo PHP_EOL; $mkv = array( 'uid:0001' => '1st user', 'uid:0002' => '2nd user', 'uid:0003' => '3rd user', ); $client->mset($mkv); $response = $client->mget(array_keys($mkv)); var_export($response); echo PHP_EOL; $response = $client->executeRaw(array( 'MGET', 'uid:0001', 'uid:0002', 'uid:0003', )); var_export($response); echo PHP_EOL; = 5.4.0 '. "or a polyfill for SessionHandlerInterface provided by an external package.\n"); } $client = new Predis\Client($single_server, array('prefix' => 'sessions:')); $handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5)); $handler->register(); session_id('example_session_id'); session_start(); if (isset($_SESSION['foo'])) { echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL; } else { $_SESSION['foo'] = $value = mt_rand(); echo "Empty session, `foo` has been set with $value", PHP_EOL; } type = $type; } public function matches($element) { return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type; } } copy($value); } } property = $property; } public function matches($object, $property) { return $property == $this->property; } } class = $class; $this->property = $property; } public function matches($object, $property) { return ($object instanceof $this->class) && $property == $this->property; } } propertyType = $propertyType; } public function matches($object, $property) { try { $reflectionProperty = ReflectionHelper::getProperty($object, $property); } catch (ReflectionException $exception) { return false; } $reflectionProperty->setAccessible(true); if (method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) { return false; } return $reflectionProperty->getValue($object) instanceof $this->propertyType; } } getProperties(); $propsArr = array(); foreach ($props as $prop) { $propertyName = $prop->getName(); $propsArr[$propertyName] = $prop; } if ($parentClass = $ref->getParentClass()) { $parentPropsArr = self::getProperties($parentClass); foreach ($propsArr as $key => $property) { $parentPropsArr[$key] = $property; } return $parentPropsArr; } return $propsArr; } public static function getProperty($object, $name) { $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); if ($reflection->hasProperty($name)) { return $reflection->getProperty($name); } if ($parentClass = $reflection->getParentClass()) { return self::getProperty($parentClass->getName(), $name); } throw new PropertyException( sprintf( 'The class "%s" doesn\'t have a property with the given name: "%s".', is_object($object) ? get_class($object) : $object, $name ) ); } } useCloneMethod = $useCloneMethod; $this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class)); $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); } public function skipUncloneable($skipUncloneable = true) { $this->skipUncloneable = $skipUncloneable; return $this; } public function copy($object) { $this->hashMap = []; return $this->recursiveCopy($object); } public function addFilter(Filter $filter, Matcher $matcher) { $this->filters[] = [ 'matcher' => $matcher, 'filter' => $filter, ]; } public function prependFilter(Filter $filter, Matcher $matcher) { array_unshift($this->filters, [ 'matcher' => $matcher, 'filter' => $filter, ]); } public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) { $this->typeFilters[] = [ 'matcher' => $matcher, 'filter' => $filter, ]; } private function recursiveCopy($var) { if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { return $filter->apply($var); } if (is_resource($var)) { return $var; } if (is_array($var)) { return $this->copyArray($var); } if (! is_object($var)) { return $var; } if (PHP_VERSION_ID >= 80100 && enum_exists(get_class($var))) { return $var; } return $this->copyObject($var); } private function copyArray(array $array) { foreach ($array as $key => $value) { $array[$key] = $this->recursiveCopy($value); } return $array; } private function copyObject($object) { $objectHash = spl_object_hash($object); if (isset($this->hashMap[$objectHash])) { return $this->hashMap[$objectHash]; } $reflectedObject = new ReflectionObject($object); $isCloneable = $reflectedObject->isCloneable(); if (false === $isCloneable) { if ($this->skipUncloneable) { $this->hashMap[$objectHash] = $object; return $object; } throw new CloneException( sprintf( 'The class "%s" is not cloneable.', $reflectedObject->getName() ) ); } $newObject = clone $object; $this->hashMap[$objectHash] = $newObject; if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { return $newObject; } if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { return $newObject; } foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { $this->copyObjectProperty($newObject, $property); } return $newObject; } private function copyObjectProperty($object, ReflectionProperty $property) { if ($property->isStatic()) { return; } foreach ($this->filters as $item) { $matcher = $item['matcher']; $filter = $item['filter']; if ($matcher->matches($object, $property->getName())) { $filter->apply( $object, $property->getName(), function ($object) { return $this->recursiveCopy($object); } ); return; } } $property->setAccessible(true); if (method_exists($property, 'isInitialized') && !$property->isInitialized($object)) { return; } $propertyValue = $property->getValue($object); $property->setValue($object, $this->recursiveCopy($propertyValue)); } private function getFirstMatchedTypeFilter(array $filterRecords, $var) { $matched = $this->first( $filterRecords, function (array $record) use ($var) { $matcher = $record['matcher']; return $matcher->matches($var); } ); return isset($matched) ? $matched['filter'] : null; } private function first(array $elements, callable $predicate) { foreach ($elements as $element) { if (call_user_func($predicate, $element)) { return $element; } } return null; } } $propertyValue) { $copy->{$propertyName} = $propertyValue; } return $copy; } } callback = $callable; } public function apply($element) { return call_user_func($this->callback, $element); } } copier = $copier; } public function apply($element) { $newElement = clone $element; $copy = $this->createCopyClosure(); return $copy($newElement); } private function createCopyClosure() { $copier = $this->copier; $copy = function (SplDoublyLinkedList $list) use ($copier) { for ($i = 1; $i <= $list->count(); $i++) { $copy = $copier->recursiveCopy($list->shift()); $list->push($copy); } return $list; }; return Closure::bind($copy, null, DeepCopy::class); } } copier = $copier; } public function apply($arrayObject) { $clone = clone $arrayObject; foreach ($arrayObject->getArrayCopy() as $k => $v) { $clone->offsetSet($k, $this->copier->copy($v)); } return $clone; } } __load(); } } setAccessible(true); $oldCollection = $reflectionProperty->getValue($object); $newCollection = $oldCollection->map( function ($item) use ($objectCopier) { return $objectCopier($item); } ); $reflectionProperty->setValue($object, $newCollection); } } setAccessible(true); $reflectionProperty->setValue($object, new ArrayCollection()); } } setAccessible(true); $reflectionProperty->setValue($object, null); } } callback = $callable; } public function apply($object, $property, $objectCopier) { $reflectionProperty = ReflectionHelper::getProperty($object, $property); $reflectionProperty->setAccessible(true); $value = call_user_func($this->callback, $reflectionProperty->getValue($object)); $reflectionProperty->setValue($object, $value); } } = 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); } } 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 & (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") { 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) { $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 { $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") { 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) { 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) { $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])) { if (!isset($c[$combClass[$uchr]])) { $c[$combClass[$uchr]] = ''; } $c[$combClass[$uchr]] .= $uchr; continue; } } else { $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; } } 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, ); ' ', '¨' => ' ̈', 'ª' => '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', ); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '΅' => '΅', 'Ά' => 'Ά', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ὲ' => 'ὲ', 'ὴ' => 'ὴ', 'ὶ' => 'ὶ', 'ὸ' => 'ὸ', 'ὺ' => 'ὺ', 'ὼ' => 'ὼ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'ᾼ' => 'ᾼ', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Ὴ' => 'Ὴ', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ῥ' => 'Ῥ', '῭' => '῭', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ὼ' => 'Ὼ', 'ῼ' => 'ῼ', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', ); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '̀' => '̀', '́' => '́', '̓' => '̓', '̈́' => '̈́', 'ʹ' => 'ʹ', ';' => ';', '΅' => '΅', 'Ά' => 'Ά', '·' => '·', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'क़' => 'क़', 'ख़' => 'ख़', 'ग़' => 'ग़', 'ज़' => 'ज़', 'ड़' => 'ड़', 'ढ़' => 'ढ़', 'फ़' => 'फ़', 'य़' => 'य़', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ড়' => 'ড়', 'ঢ়' => 'ঢ়', 'য়' => 'য়', 'ਲ਼' => 'ਲ਼', 'ਸ਼' => 'ਸ਼', 'ਖ਼' => 'ਖ਼', 'ਗ਼' => 'ਗ਼', 'ਜ਼' => 'ਜ਼', 'ਫ਼' => 'ਫ਼', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ଡ଼' => 'ଡ଼', 'ଢ଼' => 'ଢ଼', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'གྷ' => 'གྷ', 'ཌྷ' => 'ཌྷ', 'དྷ' => 'དྷ', 'བྷ' => 'བྷ', 'ཛྷ' => 'ཛྷ', 'ཀྵ' => 'ཀྵ', 'ཱི' => 'ཱི', 'ཱུ' => 'ཱུ', 'ྲྀ' => 'ྲྀ', 'ླྀ' => 'ླྀ', 'ཱྀ' => 'ཱྀ', 'ྒྷ' => 'ྒྷ', 'ྜྷ' => 'ྜྷ', 'ྡྷ' => 'ྡྷ', 'ྦྷ' => 'ྦྷ', 'ྫྷ' => 'ྫྷ', 'ྐྵ' => 'ྐྵ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ά' => 'ά', 'ὲ' => 'ὲ', 'έ' => 'έ', 'ὴ' => 'ὴ', 'ή' => 'ή', 'ὶ' => 'ὶ', 'ί' => 'ί', 'ὸ' => 'ὸ', 'ό' => 'ό', 'ὺ' => 'ὺ', 'ύ' => 'ύ', 'ὼ' => 'ὼ', 'ώ' => 'ώ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'Ά' => 'Ά', 'ᾼ' => 'ᾼ', 'ι' => 'ι', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Έ' => 'Έ', 'Ὴ' => 'Ὴ', 'Ή' => 'Ή', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', 'Ί' => 'Ί', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ύ' => 'Ύ', 'Ῥ' => 'Ῥ', '῭' => '῭', '΅' => '΅', '`' => '`', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ό' => 'Ό', 'Ὼ' => 'Ὼ', 'Ώ' => 'Ώ', 'ῼ' => 'ῼ', '´' => '´', ' ' => ' ', ' ' => ' ', 'Ω' => 'Ω', 'K' => 'K', 'Å' => 'Å', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', '〈' => '〈', '〉' => '〉', '⫝̸' => '⫝̸', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '豈' => '豈', '更' => '更', '車' => '車', '賈' => '賈', '滑' => '滑', '串' => '串', '句' => '句', '龜' => '龜', '龜' => '龜', '契' => '契', '金' => '金', '喇' => '喇', '奈' => '奈', '懶' => '懶', '癩' => '癩', '羅' => '羅', '蘿' => '蘿', '螺' => '螺', '裸' => '裸', '邏' => '邏', '樂' => '樂', '洛' => '洛', '烙' => '烙', '珞' => '珞', '落' => '落', '酪' => '酪', '駱' => '駱', '亂' => '亂', '卵' => '卵', '欄' => '欄', '爛' => '爛', '蘭' => '蘭', '鸞' => '鸞', '嵐' => '嵐', '濫' => '濫', '藍' => '藍', '襤' => '襤', '拉' => '拉', '臘' => '臘', '蠟' => '蠟', '廊' => '廊', '朗' => '朗', '浪' => '浪', '狼' => '狼', '郎' => '郎', '來' => '來', '冷' => '冷', '勞' => '勞', '擄' => '擄', '櫓' => '櫓', '爐' => '爐', '盧' => '盧', '老' => '老', '蘆' => '蘆', '虜' => '虜', '路' => '路', '露' => '露', '魯' => '魯', '鷺' => '鷺', '碌' => '碌', '祿' => '祿', '綠' => '綠', '菉' => '菉', '錄' => '錄', '鹿' => '鹿', '論' => '論', '壟' => '壟', '弄' => '弄', '籠' => '籠', '聾' => '聾', '牢' => '牢', '磊' => '磊', '賂' => '賂', '雷' => '雷', '壘' => '壘', '屢' => '屢', '樓' => '樓', '淚' => '淚', '漏' => '漏', '累' => '累', '縷' => '縷', '陋' => '陋', '勒' => '勒', '肋' => '肋', '凜' => '凜', '凌' => '凌', '稜' => '稜', '綾' => '綾', '菱' => '菱', '陵' => '陵', '讀' => '讀', '拏' => '拏', '樂' => '樂', '諾' => '諾', '丹' => '丹', '寧' => '寧', '怒' => '怒', '率' => '率', '異' => '異', '北' => '北', '磻' => '磻', '便' => '便', '復' => '復', '不' => '不', '泌' => '泌', '數' => '數', '索' => '索', '參' => '參', '塞' => '塞', '省' => '省', '葉' => '葉', '說' => '說', '殺' => '殺', '辰' => '辰', '沈' => '沈', '拾' => '拾', '若' => '若', '掠' => '掠', '略' => '略', '亮' => '亮', '兩' => '兩', '凉' => '凉', '梁' => '梁', '糧' => '糧', '良' => '良', '諒' => '諒', '量' => '量', '勵' => '勵', '呂' => '呂', '女' => '女', '廬' => '廬', '旅' => '旅', '濾' => '濾', '礪' => '礪', '閭' => '閭', '驪' => '驪', '麗' => '麗', '黎' => '黎', '力' => '力', '曆' => '曆', '歷' => '歷', '轢' => '轢', '年' => '年', '憐' => '憐', '戀' => '戀', '撚' => '撚', '漣' => '漣', '煉' => '煉', '璉' => '璉', '秊' => '秊', '練' => '練', '聯' => '聯', '輦' => '輦', '蓮' => '蓮', '連' => '連', '鍊' => '鍊', '列' => '列', '劣' => '劣', '咽' => '咽', '烈' => '烈', '裂' => '裂', '說' => '說', '廉' => '廉', '念' => '念', '捻' => '捻', '殮' => '殮', '簾' => '簾', '獵' => '獵', '令' => '令', '囹' => '囹', '寧' => '寧', '嶺' => '嶺', '怜' => '怜', '玲' => '玲', '瑩' => '瑩', '羚' => '羚', '聆' => '聆', '鈴' => '鈴', '零' => '零', '靈' => '靈', '領' => '領', '例' => '例', '禮' => '禮', '醴' => '醴', '隸' => '隸', '惡' => '惡', '了' => '了', '僚' => '僚', '寮' => '寮', '尿' => '尿', '料' => '料', '樂' => '樂', '燎' => '燎', '療' => '療', '蓼' => '蓼', '遼' => '遼', '龍' => '龍', '暈' => '暈', '阮' => '阮', '劉' => '劉', '杻' => '杻', '柳' => '柳', '流' => '流', '溜' => '溜', '琉' => '琉', '留' => '留', '硫' => '硫', '紐' => '紐', '類' => '類', '六' => '六', '戮' => '戮', '陸' => '陸', '倫' => '倫', '崙' => '崙', '淪' => '淪', '輪' => '輪', '律' => '律', '慄' => '慄', '栗' => '栗', '率' => '率', '隆' => '隆', '利' => '利', '吏' => '吏', '履' => '履', '易' => '易', '李' => '李', '梨' => '梨', '泥' => '泥', '理' => '理', '痢' => '痢', '罹' => '罹', '裏' => '裏', '裡' => '裡', '里' => '里', '離' => '離', '匿' => '匿', '溺' => '溺', '吝' => '吝', '燐' => '燐', '璘' => '璘', '藺' => '藺', '隣' => '隣', '鱗' => '鱗', '麟' => '麟', '林' => '林', '淋' => '淋', '臨' => '臨', '立' => '立', '笠' => '笠', '粒' => '粒', '狀' => '狀', '炙' => '炙', '識' => '識', '什' => '什', '茶' => '茶', '刺' => '刺', '切' => '切', '度' => '度', '拓' => '拓', '糖' => '糖', '宅' => '宅', '洞' => '洞', '暴' => '暴', '輻' => '輻', '行' => '行', '降' => '降', '見' => '見', '廓' => '廓', '兀' => '兀', '嗀' => '嗀', '塚' => '塚', '晴' => '晴', '凞' => '凞', '猪' => '猪', '益' => '益', '礼' => '礼', '神' => '神', '祥' => '祥', '福' => '福', '靖' => '靖', '精' => '精', '羽' => '羽', '蘒' => '蘒', '諸' => '諸', '逸' => '逸', '都' => '都', '飯' => '飯', '飼' => '飼', '館' => '館', '鶴' => '鶴', '郞' => '郞', '隷' => '隷', '侮' => '侮', '僧' => '僧', '免' => '免', '勉' => '勉', '勤' => '勤', '卑' => '卑', '喝' => '喝', '嘆' => '嘆', '器' => '器', '塀' => '塀', '墨' => '墨', '層' => '層', '屮' => '屮', '悔' => '悔', '慨' => '慨', '憎' => '憎', '懲' => '懲', '敏' => '敏', '既' => '既', '暑' => '暑', '梅' => '梅', '海' => '海', '渚' => '渚', '漢' => '漢', '煮' => '煮', '爫' => '爫', '琢' => '琢', '碑' => '碑', '社' => '社', '祉' => '祉', '祈' => '祈', '祐' => '祐', '祖' => '祖', '祝' => '祝', '禍' => '禍', '禎' => '禎', '穀' => '穀', '突' => '突', '節' => '節', '練' => '練', '縉' => '縉', '繁' => '繁', '署' => '署', '者' => '者', '臭' => '臭', '艹' => '艹', '艹' => '艹', '著' => '著', '褐' => '褐', '視' => '視', '謁' => '謁', '謹' => '謹', '賓' => '賓', '贈' => '贈', '辶' => '辶', '逸' => '逸', '難' => '難', '響' => '響', '頻' => '頻', '恵' => '恵', '𤋮' => '𤋮', '舘' => '舘', '並' => '並', '况' => '况', '全' => '全', '侀' => '侀', '充' => '充', '冀' => '冀', '勇' => '勇', '勺' => '勺', '喝' => '喝', '啕' => '啕', '喙' => '喙', '嗢' => '嗢', '塚' => '塚', '墳' => '墳', '奄' => '奄', '奔' => '奔', '婢' => '婢', '嬨' => '嬨', '廒' => '廒', '廙' => '廙', '彩' => '彩', '徭' => '徭', '惘' => '惘', '慎' => '慎', '愈' => '愈', '憎' => '憎', '慠' => '慠', '懲' => '懲', '戴' => '戴', '揄' => '揄', '搜' => '搜', '摒' => '摒', '敖' => '敖', '晴' => '晴', '朗' => '朗', '望' => '望', '杖' => '杖', '歹' => '歹', '殺' => '殺', '流' => '流', '滛' => '滛', '滋' => '滋', '漢' => '漢', '瀞' => '瀞', '煮' => '煮', '瞧' => '瞧', '爵' => '爵', '犯' => '犯', '猪' => '猪', '瑱' => '瑱', '甆' => '甆', '画' => '画', '瘝' => '瘝', '瘟' => '瘟', '益' => '益', '盛' => '盛', '直' => '直', '睊' => '睊', '着' => '着', '磌' => '磌', '窱' => '窱', '節' => '節', '类' => '类', '絛' => '絛', '練' => '練', '缾' => '缾', '者' => '者', '荒' => '荒', '華' => '華', '蝹' => '蝹', '襁' => '襁', '覆' => '覆', '視' => '視', '調' => '調', '諸' => '諸', '請' => '請', '謁' => '謁', '諾' => '諾', '諭' => '諭', '謹' => '謹', '變' => '變', '贈' => '贈', '輸' => '輸', '遲' => '遲', '醙' => '醙', '鉶' => '鉶', '陼' => '陼', '難' => '難', '靖' => '靖', '韛' => '韛', '響' => '響', '頋' => '頋', '頻' => '頻', '鬒' => '鬒', '龜' => '龜', '𢡊' => '𢡊', '𢡄' => '𢡄', '𣏕' => '𣏕', '㮝' => '㮝', '䀘' => '䀘', '䀹' => '䀹', '𥉉' => '𥉉', '𥳐' => '𥳐', '𧻓' => '𧻓', '齃' => '齃', '龎' => '龎', 'יִ' => 'יִ', 'ײַ' => 'ײַ', 'שׁ' => 'שׁ', 'שׂ' => 'שׂ', 'שּׁ' => 'שּׁ', 'שּׂ' => 'שּׂ', 'אַ' => 'אַ', 'אָ' => 'אָ', 'אּ' => 'אּ', 'בּ' => 'בּ', 'גּ' => 'גּ', 'דּ' => 'דּ', 'הּ' => 'הּ', 'וּ' => 'וּ', 'זּ' => 'זּ', 'טּ' => 'טּ', 'יּ' => 'יּ', 'ךּ' => 'ךּ', 'כּ' => 'כּ', 'לּ' => 'לּ', 'מּ' => 'מּ', 'נּ' => 'נּ', 'סּ' => 'סּ', 'ףּ' => 'ףּ', 'פּ' => 'פּ', 'צּ' => 'צּ', 'קּ' => 'קּ', 'רּ' => 'רּ', 'שּ' => 'שּ', 'תּ' => 'תּ', 'וֹ' => 'וֹ', 'בֿ' => 'בֿ', 'כֿ' => 'כֿ', 'פֿ' => 'פֿ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', '𝅗𝅥' => '𝅗𝅥', '𝅘𝅥' => '𝅘𝅥', '𝅘𝅥𝅮' => '𝅘𝅥𝅮', '𝅘𝅥𝅯' => '𝅘𝅥𝅯', '𝅘𝅥𝅰' => '𝅘𝅥𝅰', '𝅘𝅥𝅱' => '𝅘𝅥𝅱', '𝅘𝅥𝅲' => '𝅘𝅥𝅲', '𝆹𝅥' => '𝆹𝅥', '𝆺𝅥' => '𝆺𝅥', '𝆹𝅥𝅮' => '𝆹𝅥𝅮', '𝆺𝅥𝅮' => '𝆺𝅥𝅮', '𝆹𝅥𝅯' => '𝆹𝅥𝅯', '𝆺𝅥𝅯' => '𝆺𝅥𝅯', '丽' => '丽', '丸' => '丸', '乁' => '乁', '𠄢' => '𠄢', '你' => '你', '侮' => '侮', '侻' => '侻', '倂' => '倂', '偺' => '偺', '備' => '備', '僧' => '僧', '像' => '像', '㒞' => '㒞', '𠘺' => '𠘺', '免' => '免', '兔' => '兔', '兤' => '兤', '具' => '具', '𠔜' => '𠔜', '㒹' => '㒹', '內' => '內', '再' => '再', '𠕋' => '𠕋', '冗' => '冗', '冤' => '冤', '仌' => '仌', '冬' => '冬', '况' => '况', '𩇟' => '𩇟', '凵' => '凵', '刃' => '刃', '㓟' => '㓟', '刻' => '刻', '剆' => '剆', '割' => '割', '剷' => '剷', '㔕' => '㔕', '勇' => '勇', '勉' => '勉', '勤' => '勤', '勺' => '勺', '包' => '包', '匆' => '匆', '北' => '北', '卉' => '卉', '卑' => '卑', '博' => '博', '即' => '即', '卽' => '卽', '卿' => '卿', '卿' => '卿', '卿' => '卿', '𠨬' => '𠨬', '灰' => '灰', '及' => '及', '叟' => '叟', '𠭣' => '𠭣', '叫' => '叫', '叱' => '叱', '吆' => '吆', '咞' => '咞', '吸' => '吸', '呈' => '呈', '周' => '周', '咢' => '咢', '哶' => '哶', '唐' => '唐', '啓' => '啓', '啣' => '啣', '善' => '善', '善' => '善', '喙' => '喙', '喫' => '喫', '喳' => '喳', '嗂' => '嗂', '圖' => '圖', '嘆' => '嘆', '圗' => '圗', '噑' => '噑', '噴' => '噴', '切' => '切', '壮' => '壮', '城' => '城', '埴' => '埴', '堍' => '堍', '型' => '型', '堲' => '堲', '報' => '報', '墬' => '墬', '𡓤' => '𡓤', '売' => '売', '壷' => '壷', '夆' => '夆', '多' => '多', '夢' => '夢', '奢' => '奢', '𡚨' => '𡚨', '𡛪' => '𡛪', '姬' => '姬', '娛' => '娛', '娧' => '娧', '姘' => '姘', '婦' => '婦', '㛮' => '㛮', '㛼' => '㛼', '嬈' => '嬈', '嬾' => '嬾', '嬾' => '嬾', '𡧈' => '𡧈', '寃' => '寃', '寘' => '寘', '寧' => '寧', '寳' => '寳', '𡬘' => '𡬘', '寿' => '寿', '将' => '将', '当' => '当', '尢' => '尢', '㞁' => '㞁', '屠' => '屠', '屮' => '屮', '峀' => '峀', '岍' => '岍', '𡷤' => '𡷤', '嵃' => '嵃', '𡷦' => '𡷦', '嵮' => '嵮', '嵫' => '嵫', '嵼' => '嵼', '巡' => '巡', '巢' => '巢', '㠯' => '㠯', '巽' => '巽', '帨' => '帨', '帽' => '帽', '幩' => '幩', '㡢' => '㡢', '𢆃' => '𢆃', '㡼' => '㡼', '庰' => '庰', '庳' => '庳', '庶' => '庶', '廊' => '廊', '𪎒' => '𪎒', '廾' => '廾', '𢌱' => '𢌱', '𢌱' => '𢌱', '舁' => '舁', '弢' => '弢', '弢' => '弢', '㣇' => '㣇', '𣊸' => '𣊸', '𦇚' => '𦇚', '形' => '形', '彫' => '彫', '㣣' => '㣣', '徚' => '徚', '忍' => '忍', '志' => '志', '忹' => '忹', '悁' => '悁', '㤺' => '㤺', '㤜' => '㤜', '悔' => '悔', '𢛔' => '𢛔', '惇' => '惇', '慈' => '慈', '慌' => '慌', '慎' => '慎', '慌' => '慌', '慺' => '慺', '憎' => '憎', '憲' => '憲', '憤' => '憤', '憯' => '憯', '懞' => '懞', '懲' => '懲', '懶' => '懶', '成' => '成', '戛' => '戛', '扝' => '扝', '抱' => '抱', '拔' => '拔', '捐' => '捐', '𢬌' => '𢬌', '挽' => '挽', '拼' => '拼', '捨' => '捨', '掃' => '掃', '揤' => '揤', '𢯱' => '𢯱', '搢' => '搢', '揅' => '揅', '掩' => '掩', '㨮' => '㨮', '摩' => '摩', '摾' => '摾', '撝' => '撝', '摷' => '摷', '㩬' => '㩬', '敏' => '敏', '敬' => '敬', '𣀊' => '𣀊', '旣' => '旣', '書' => '書', '晉' => '晉', '㬙' => '㬙', '暑' => '暑', '㬈' => '㬈', '㫤' => '㫤', '冒' => '冒', '冕' => '冕', '最' => '最', '暜' => '暜', '肭' => '肭', '䏙' => '䏙', '朗' => '朗', '望' => '望', '朡' => '朡', '杞' => '杞', '杓' => '杓', '𣏃' => '𣏃', '㭉' => '㭉', '柺' => '柺', '枅' => '枅', '桒' => '桒', '梅' => '梅', '𣑭' => '𣑭', '梎' => '梎', '栟' => '栟', '椔' => '椔', '㮝' => '㮝', '楂' => '楂', '榣' => '榣', '槪' => '槪', '檨' => '檨', '𣚣' => '𣚣', '櫛' => '櫛', '㰘' => '㰘', '次' => '次', '𣢧' => '𣢧', '歔' => '歔', '㱎' => '㱎', '歲' => '歲', '殟' => '殟', '殺' => '殺', '殻' => '殻', '𣪍' => '𣪍', '𡴋' => '𡴋', '𣫺' => '𣫺', '汎' => '汎', '𣲼' => '𣲼', '沿' => '沿', '泍' => '泍', '汧' => '汧', '洖' => '洖', '派' => '派', '海' => '海', '流' => '流', '浩' => '浩', '浸' => '浸', '涅' => '涅', '𣴞' => '𣴞', '洴' => '洴', '港' => '港', '湮' => '湮', '㴳' => '㴳', '滋' => '滋', '滇' => '滇', '𣻑' => '𣻑', '淹' => '淹', '潮' => '潮', '𣽞' => '𣽞', '𣾎' => '𣾎', '濆' => '濆', '瀹' => '瀹', '瀞' => '瀞', '瀛' => '瀛', '㶖' => '㶖', '灊' => '灊', '災' => '災', '灷' => '灷', '炭' => '炭', '𠔥' => '𠔥', '煅' => '煅', '𤉣' => '𤉣', '熜' => '熜', '𤎫' => '𤎫', '爨' => '爨', '爵' => '爵', '牐' => '牐', '𤘈' => '𤘈', '犀' => '犀', '犕' => '犕', '𤜵' => '𤜵', '𤠔' => '𤠔', '獺' => '獺', '王' => '王', '㺬' => '㺬', '玥' => '玥', '㺸' => '㺸', '㺸' => '㺸', '瑇' => '瑇', '瑜' => '瑜', '瑱' => '瑱', '璅' => '璅', '瓊' => '瓊', '㼛' => '㼛', '甤' => '甤', '𤰶' => '𤰶', '甾' => '甾', '𤲒' => '𤲒', '異' => '異', '𢆟' => '𢆟', '瘐' => '瘐', '𤾡' => '𤾡', '𤾸' => '𤾸', '𥁄' => '𥁄', '㿼' => '㿼', '䀈' => '䀈', '直' => '直', '𥃳' => '𥃳', '𥃲' => '𥃲', '𥄙' => '𥄙', '𥄳' => '𥄳', '眞' => '眞', '真' => '真', '真' => '真', '睊' => '睊', '䀹' => '䀹', '瞋' => '瞋', '䁆' => '䁆', '䂖' => '䂖', '𥐝' => '𥐝', '硎' => '硎', '碌' => '碌', '磌' => '磌', '䃣' => '䃣', '𥘦' => '𥘦', '祖' => '祖', '𥚚' => '𥚚', '𥛅' => '𥛅', '福' => '福', '秫' => '秫', '䄯' => '䄯', '穀' => '穀', '穊' => '穊', '穏' => '穏', '𥥼' => '𥥼', '𥪧' => '𥪧', '𥪧' => '𥪧', '竮' => '竮', '䈂' => '䈂', '𥮫' => '𥮫', '篆' => '篆', '築' => '築', '䈧' => '䈧', '𥲀' => '𥲀', '糒' => '糒', '䊠' => '䊠', '糨' => '糨', '糣' => '糣', '紀' => '紀', '𥾆' => '𥾆', '絣' => '絣', '䌁' => '䌁', '緇' => '緇', '縂' => '縂', '繅' => '繅', '䌴' => '䌴', '𦈨' => '𦈨', '𦉇' => '𦉇', '䍙' => '䍙', '𦋙' => '𦋙', '罺' => '罺', '𦌾' => '𦌾', '羕' => '羕', '翺' => '翺', '者' => '者', '𦓚' => '𦓚', '𦔣' => '𦔣', '聠' => '聠', '𦖨' => '𦖨', '聰' => '聰', '𣍟' => '𣍟', '䏕' => '䏕', '育' => '育', '脃' => '脃', '䐋' => '䐋', '脾' => '脾', '媵' => '媵', '𦞧' => '𦞧', '𦞵' => '𦞵', '𣎓' => '𣎓', '𣎜' => '𣎜', '舁' => '舁', '舄' => '舄', '辞' => '辞', '䑫' => '䑫', '芑' => '芑', '芋' => '芋', '芝' => '芝', '劳' => '劳', '花' => '花', '芳' => '芳', '芽' => '芽', '苦' => '苦', '𦬼' => '𦬼', '若' => '若', '茝' => '茝', '荣' => '荣', '莭' => '莭', '茣' => '茣', '莽' => '莽', '菧' => '菧', '著' => '著', '荓' => '荓', '菊' => '菊', '菌' => '菌', '菜' => '菜', '𦰶' => '𦰶', '𦵫' => '𦵫', '𦳕' => '𦳕', '䔫' => '䔫', '蓱' => '蓱', '蓳' => '蓳', '蔖' => '蔖', '𧏊' => '𧏊', '蕤' => '蕤', '𦼬' => '𦼬', '䕝' => '䕝', '䕡' => '䕡', '𦾱' => '𦾱', '𧃒' => '𧃒', '䕫' => '䕫', '虐' => '虐', '虜' => '虜', '虧' => '虧', '虩' => '虩', '蚩' => '蚩', '蚈' => '蚈', '蜎' => '蜎', '蛢' => '蛢', '蝹' => '蝹', '蜨' => '蜨', '蝫' => '蝫', '螆' => '螆', '䗗' => '䗗', '蟡' => '蟡', '蠁' => '蠁', '䗹' => '䗹', '衠' => '衠', '衣' => '衣', '𧙧' => '𧙧', '裗' => '裗', '裞' => '裞', '䘵' => '䘵', '裺' => '裺', '㒻' => '㒻', '𧢮' => '𧢮', '𧥦' => '𧥦', '䚾' => '䚾', '䛇' => '䛇', '誠' => '誠', '諭' => '諭', '變' => '變', '豕' => '豕', '𧲨' => '𧲨', '貫' => '貫', '賁' => '賁', '贛' => '贛', '起' => '起', '𧼯' => '𧼯', '𠠄' => '𠠄', '跋' => '跋', '趼' => '趼', '跰' => '跰', '𠣞' => '𠣞', '軔' => '軔', '輸' => '輸', '𨗒' => '𨗒', '𨗭' => '𨗭', '邔' => '邔', '郱' => '郱', '鄑' => '鄑', '𨜮' => '𨜮', '鄛' => '鄛', '鈸' => '鈸', '鋗' => '鋗', '鋘' => '鋘', '鉼' => '鉼', '鏹' => '鏹', '鐕' => '鐕', '𨯺' => '𨯺', '開' => '開', '䦕' => '䦕', '閷' => '閷', '𨵷' => '𨵷', '䧦' => '䧦', '雃' => '雃', '嶲' => '嶲', '霣' => '霣', '𩅅' => '𩅅', '𩈚' => '𩈚', '䩮' => '䩮', '䩶' => '䩶', '韠' => '韠', '𩐊' => '𩐊', '䪲' => '䪲', '𩒖' => '𩒖', '頋' => '頋', '頋' => '頋', '頩' => '頩', '𩖶' => '𩖶', '飢' => '飢', '䬳' => '䬳', '餩' => '餩', '馧' => '馧', '駂' => '駂', '駾' => '駾', '䯎' => '䯎', '𩬰' => '𩬰', '鬒' => '鬒', '鱀' => '鱀', '鳽' => '鳽', '䳎' => '䳎', '䳭' => '䳭', '鵧' => '鵧', '𪃎' => '𪃎', '䳸' => '䳸', '𪄅' => '𪄅', '𪈎' => '𪈎', '𪊑' => '𪊑', '麻' => '麻', '䵖' => '䵖', '黹' => '黹', '黾' => '黾', '鼅' => '鼅', '鼏' => '鼏', '鼖' => '鼖', '鼻' => '鼻', '𪘀' => '𪘀', ); = 80000) { return require __DIR__.'/bootstrap80.php'; } if (!defined('U_IDNA_PROHIBITED_ERROR')) { define('U_IDNA_PROHIBITED_ERROR', 66560); } if (!defined('U_IDNA_ERROR_START')) { define('U_IDNA_ERROR_START', 66560); } if (!defined('U_IDNA_UNASSIGNED_ERROR')) { define('U_IDNA_UNASSIGNED_ERROR', 66561); } if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { define('U_IDNA_CHECK_BIDI_ERROR', 66562); } if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); } if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { define('U_IDNA_ACE_PREFIX_ERROR', 66564); } if (!defined('U_IDNA_VERIFICATION_ERROR')) { define('U_IDNA_VERIFICATION_ERROR', 66565); } if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); } if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); } if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); } if (!defined('U_IDNA_ERROR_LIMIT')) { define('U_IDNA_ERROR_LIMIT', 66569); } if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { define('U_STRINGPREP_PROHIBITED_ERROR', 66560); } if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); } if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); } if (!defined('IDNA_DEFAULT')) { define('IDNA_DEFAULT', 0); } if (!defined('IDNA_ALLOW_UNASSIGNED')) { define('IDNA_ALLOW_UNASSIGNED', 1); } if (!defined('IDNA_USE_STD3_RULES')) { define('IDNA_USE_STD3_RULES', 2); } if (!defined('IDNA_CHECK_BIDI')) { define('IDNA_CHECK_BIDI', 4); } if (!defined('IDNA_CHECK_CONTEXTJ')) { define('IDNA_CHECK_CONTEXTJ', 8); } if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); } if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); } if (!defined('INTL_IDNA_VARIANT_2003')) { define('INTL_IDNA_VARIANT_2003', 0); } if (!defined('INTL_IDNA_VARIANT_UTS46')) { define('INTL_IDNA_VARIANT_UTS46', 1); } if (!defined('IDNA_ERROR_EMPTY_LABEL')) { define('IDNA_ERROR_EMPTY_LABEL', 1); } if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { define('IDNA_ERROR_LABEL_TOO_LONG', 2); } if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); } if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { define('IDNA_ERROR_LEADING_HYPHEN', 8); } if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { define('IDNA_ERROR_TRAILING_HYPHEN', 16); } if (!defined('IDNA_ERROR_HYPHEN_3_4')) { define('IDNA_ERROR_HYPHEN_3_4', 32); } if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); } if (!defined('IDNA_ERROR_DISALLOWED')) { define('IDNA_ERROR_DISALLOWED', 128); } if (!defined('IDNA_ERROR_PUNYCODE')) { define('IDNA_ERROR_PUNYCODE', 256); } if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { define('IDNA_ERROR_LABEL_HAS_DOT', 512); } if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); } if (!defined('IDNA_ERROR_BIDI')) { define('IDNA_ERROR_BIDI', 2048); } if (!defined('IDNA_ERROR_CONTEXTJ')) { define('IDNA_ERROR_CONTEXTJ', 4096); } if (\PHP_VERSION_ID < 70400) { if (!function_exists('idn_to_ascii')) { function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } } if (!function_exists('idn_to_utf8')) { function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } } } else { if (!function_exists('idn_to_ascii')) { function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } } if (!function_exists('idn_to_utf8')) { function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } } } = 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); } $options = [ 'CheckHyphens' => true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), 'VerifyDnsLength' => true, ]; $info = new Info(); $labels = self::process((string) $domainName, $options, $info); foreach ($labels as $i => $label) { if (1 === preg_match('/[^\x00-\x7F]/', $label)) { try { $label = 'xn--'.self::punycodeEncode($label); } catch (Exception $e) { $info->errors |= self::ERROR_PUNYCODE; } $labels[$i] = $label; } } if ($options['VerifyDnsLength']) { self::validateDomainAndLabelLength($labels, $info); } $idna_info = [ 'result' => implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors, ]; return 0 === $info->errors ? $idna_info['result'] : false; } public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) { if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); } $info = new Info(); $labels = self::process((string) $domainName, [ 'CheckHyphens' => true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE), ], $info); $idna_info = [ 'result' => implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors, ]; return 0 === $info->errors ? $idna_info['result'] : false; } private static function isValidContextJ(array $codePoints, $label) { if (!isset(self::$virama)) { self::$virama = require __DIR__.\DIRECTORY_SEPARATOR.'Resources'.\DIRECTORY_SEPARATOR.'unidata'.\DIRECTORY_SEPARATOR.'virama.php'; } $offset = 0; foreach ($codePoints as $i => $codePoint) { if (0x200C !== $codePoint && 0x200D !== $codePoint) { continue; } if (!isset($codePoints[$i - 1])) { return false; } if (isset(self::$virama[$codePoints[$i - 1]])) { continue; } if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { $offset += \strlen($matches[1][0]); continue; } return false; } return true; } private static function mapCodePoints($input, array $options, Info $info) { $str = ''; $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; $transitional = $options['Transitional_Processing']; foreach (self::utf8Decode($input) as $codePoint) { $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); switch ($data['status']) { case 'disallowed': $info->errors |= self::ERROR_DISALLOWED; case 'valid': $str .= mb_chr($codePoint, 'utf-8'); break; case 'ignored': break; case 'mapped': $str .= $data['mapping']; break; case 'deviation': $info->transitionalDifferent = true; $str .= ($transitional ? $data['mapping'] : mb_chr($codePoint, 'utf-8')); break; } } return $str; } private static function process($domain, array $options, Info $info) { $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength']; if ($checkForEmptyLabels && '' === $domain) { $info->errors |= self::ERROR_EMPTY_LABEL; return [$domain]; } $domain = self::mapCodePoints($domain, $options, $info); if (!Normalizer::isNormalized($domain, Normalizer::FORM_C)) { $domain = Normalizer::normalize($domain, Normalizer::FORM_C); } $labels = explode('.', $domain); $lastLabelIndex = \count($labels) - 1; foreach ($labels as $i => $label) { $validationOptions = $options; if ('xn--' === substr($label, 0, 4)) { try { $label = self::punycodeDecode(substr($label, 4)); } catch (Exception $e) { $info->errors |= self::ERROR_PUNYCODE; continue; } $validationOptions['Transitional_Processing'] = false; $labels[$i] = $label; } self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex); } if ($info->bidiDomain && !$info->validBidiDomain) { $info->errors |= self::ERROR_BIDI; } return $labels; } private static function validateBidiLabel($label, Info $info) { if (1 === preg_match(Regex::RTL_LABEL, $label)) { $info->bidiDomain = true; if (1 !== preg_match(Regex::BIDI_STEP_1_RTL, $label)) { $info->validBidiDomain = false; return; } if (1 === preg_match(Regex::BIDI_STEP_2, $label)) { $info->validBidiDomain = false; return; } if (1 !== preg_match(Regex::BIDI_STEP_3, $label)) { $info->validBidiDomain = false; return; } if (1 === preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === preg_match(Regex::BIDI_STEP_4_EN, $label)) { $info->validBidiDomain = false; return; } return; } if (1 !== preg_match(Regex::BIDI_STEP_1_LTR, $label)) { $info->validBidiDomain = false; return; } if (1 === preg_match(Regex::BIDI_STEP_5, $label)) { $info->validBidiDomain = false; return; } if (1 !== preg_match(Regex::BIDI_STEP_6, $label)) { $info->validBidiDomain = false; return; } } private static function validateDomainAndLabelLength(array $labels, Info $info) { $maxDomainSize = self::MAX_DOMAIN_SIZE; $length = \count($labels); $domainLength = $length - 1; if ($length > 1 && '' === $labels[$length - 1]) { ++$maxDomainSize; --$length; } for ($i = 0; $i < $length; ++$i) { $bytes = \strlen($labels[$i]); $domainLength += $bytes; if ($bytes > self::MAX_LABEL_SIZE) { $info->errors |= self::ERROR_LABEL_TOO_LONG; } } if ($domainLength > $maxDomainSize) { $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG; } } private static function validateLabel($label, Info $info, array $options, $canBeEmpty) { if ('' === $label) { if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) { $info->errors |= self::ERROR_EMPTY_LABEL; } return; } if (!Normalizer::isNormalized($label, Normalizer::FORM_C)) { $info->errors |= self::ERROR_INVALID_ACE_LABEL; } $codePoints = self::utf8Decode($label); if ($options['CheckHyphens']) { if (isset($codePoints[2], $codePoints[3]) && 0x002D === $codePoints[2] && 0x002D === $codePoints[3]) { $info->errors |= self::ERROR_HYPHEN_3_4; } if ('-' === substr($label, 0, 1)) { $info->errors |= self::ERROR_LEADING_HYPHEN; } if ('-' === substr($label, -1, 1)) { $info->errors |= self::ERROR_TRAILING_HYPHEN; } } if (false !== strpos($label, '.')) { $info->errors |= self::ERROR_LABEL_HAS_DOT; } if (1 === preg_match(Regex::COMBINING_MARK, $label)) { $info->errors |= self::ERROR_LEADING_COMBINING_MARK; } $transitional = $options['Transitional_Processing']; $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; foreach ($codePoints as $codePoint) { $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); $status = $data['status']; if ('valid' === $status || (!$transitional && 'deviation' === $status)) { continue; } $info->errors |= self::ERROR_DISALLOWED; break; } if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) { $info->errors |= self::ERROR_CONTEXTJ; } if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) { self::validateBidiLabel($label, $info); } } private static function punycodeDecode($input) { $n = self::INITIAL_N; $out = 0; $i = 0; $bias = self::INITIAL_BIAS; $lastDelimIndex = strrpos($input, self::DELIMITER); $b = false === $lastDelimIndex ? 0 : $lastDelimIndex; $inputLength = \strlen($input); $output = []; $bytes = array_map('ord', str_split($input)); for ($j = 0; $j < $b; ++$j) { if ($bytes[$j] > 0x7F) { throw new Exception('Invalid input'); } $output[$out++] = $input[$j]; } if ($b > 0) { ++$b; } for ($in = $b; $in < $inputLength; ++$out) { $oldi = $i; $w = 1; for ($k = self::BASE; ; $k += self::BASE) { if ($in >= $inputLength) { throw new Exception('Invalid input'); } $digit = self::$basicToDigit[$bytes[$in++] & 0xFF]; if ($digit < 0) { throw new Exception('Invalid input'); } if ($digit > intdiv(self::MAX_INT - $i, $w)) { throw new Exception('Integer overflow'); } $i += $digit * $w; if ($k <= $bias) { $t = self::TMIN; } elseif ($k >= $bias + self::TMAX) { $t = self::TMAX; } else { $t = $k - $bias; } if ($digit < $t) { break; } $baseMinusT = self::BASE - $t; if ($w > intdiv(self::MAX_INT, $baseMinusT)) { throw new Exception('Integer overflow'); } $w *= $baseMinusT; } $outPlusOne = $out + 1; $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi); if (intdiv($i, $outPlusOne) > self::MAX_INT - $n) { throw new Exception('Integer overflow'); } $n += intdiv($i, $outPlusOne); $i %= $outPlusOne; array_splice($output, $i++, 0, [mb_chr($n, 'utf-8')]); } return implode('', $output); } private static function punycodeEncode($input) { $n = self::INITIAL_N; $delta = 0; $out = 0; $bias = self::INITIAL_BIAS; $inputLength = 0; $output = ''; $iter = self::utf8Decode($input); foreach ($iter as $codePoint) { ++$inputLength; if ($codePoint < 0x80) { $output .= \chr($codePoint); ++$out; } } $h = $out; $b = $out; if ($b > 0) { $output .= self::DELIMITER; ++$out; } while ($h < $inputLength) { $m = self::MAX_INT; foreach ($iter as $codePoint) { if ($codePoint >= $n && $codePoint < $m) { $m = $codePoint; } } if ($m - $n > intdiv(self::MAX_INT - $delta, $h + 1)) { throw new Exception('Integer overflow'); } $delta += ($m - $n) * ($h + 1); $n = $m; foreach ($iter as $codePoint) { if ($codePoint < $n && 0 === ++$delta) { throw new Exception('Integer overflow'); } if ($codePoint === $n) { $q = $delta; for ($k = self::BASE; ; $k += self::BASE) { if ($k <= $bias) { $t = self::TMIN; } elseif ($k >= $bias + self::TMAX) { $t = self::TMAX; } else { $t = $k - $bias; } if ($q < $t) { break; } $qMinusT = $q - $t; $baseMinusT = self::BASE - $t; $output .= self::encodeDigit($t + ($qMinusT) % ($baseMinusT), false); ++$out; $q = intdiv($qMinusT, $baseMinusT); } $output .= self::encodeDigit($q, false); ++$out; $bias = self::adaptBias($delta, $h + 1, $h === $b); $delta = 0; ++$h; } } ++$delta; ++$n; } return $output; } private static function adaptBias($delta, $numPoints, $firstTime) { $delta = $firstTime ? intdiv($delta, self::DAMP) : $delta >> 1; $delta += intdiv($delta, $numPoints); $k = 0; while ($delta > ((self::BASE - self::TMIN) * self::TMAX) >> 1) { $delta = intdiv($delta, self::BASE - self::TMIN); $k += self::BASE; } return $k + intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW); } private static function encodeDigit($d, $flag) { return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5)); } private static function utf8Decode($input) { $bytesSeen = 0; $bytesNeeded = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = 0; $codePoints = []; $length = \strlen($input); for ($i = 0; $i < $length; ++$i) { $byte = \ord($input[$i]); if (0 === $bytesNeeded) { if ($byte >= 0x00 && $byte <= 0x7F) { $codePoints[] = $byte; continue; } if ($byte >= 0xC2 && $byte <= 0xDF) { $bytesNeeded = 1; $codePoint = $byte & 0x1F; } elseif ($byte >= 0xE0 && $byte <= 0xEF) { if (0xE0 === $byte) { $lowerBoundary = 0xA0; } elseif (0xED === $byte) { $upperBoundary = 0x9F; } $bytesNeeded = 2; $codePoint = $byte & 0xF; } elseif ($byte >= 0xF0 && $byte <= 0xF4) { if (0xF0 === $byte) { $lowerBoundary = 0x90; } elseif (0xF4 === $byte) { $upperBoundary = 0x8F; } $bytesNeeded = 3; $codePoint = $byte & 0x7; } else { $codePoints[] = 0xFFFD; } continue; } if ($byte < $lowerBoundary || $byte > $upperBoundary) { $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; $lowerBoundary = 0x80; $upperBoundary = 0xBF; --$i; $codePoints[] = 0xFFFD; continue; } $lowerBoundary = 0x80; $upperBoundary = 0xBF; $codePoint = ($codePoint << 6) | ($byte & 0x3F); if (++$bytesSeen !== $bytesNeeded) { continue; } $codePoints[] = $codePoint; $codePoint = 0; $bytesNeeded = 0; $bytesSeen = 0; } if (0 !== $bytesNeeded) { $codePoints[] = 0xFFFD; } return $codePoints; } private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) { if (!self::$mappingTableLoaded) { self::$mappingTableLoaded = true; self::$mapped = require __DIR__.'/Resources/unidata/mapped.php'; self::$ignored = require __DIR__.'/Resources/unidata/ignored.php'; self::$deviation = require __DIR__.'/Resources/unidata/deviation.php'; self::$disallowed = require __DIR__.'/Resources/unidata/disallowed.php'; self::$disallowed_STD3_mapped = require __DIR__.'/Resources/unidata/disallowed_STD3_mapped.php'; self::$disallowed_STD3_valid = require __DIR__.'/Resources/unidata/disallowed_STD3_valid.php'; } if (isset(self::$mapped[$codePoint])) { return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; } if (isset(self::$ignored[$codePoint])) { return ['status' => 'ignored']; } if (isset(self::$deviation[$codePoint])) { return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; } if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { return ['status' => 'disallowed']; } $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) { $status = 'disallowed'; if (!$useSTD3ASCIIRules) { $status = $isDisallowedMapped ? 'mapped' : 'valid'; } if ($isDisallowedMapped) { return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; } return ['status' => $status]; } return ['status' => 'valid']; } } true, 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 6 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 14 => true, 15 => true, 16 => true, 17 => true, 18 => true, 19 => true, 20 => true, 21 => true, 22 => true, 23 => true, 24 => true, 25 => true, 26 => true, 27 => true, 28 => true, 29 => true, 30 => true, 31 => true, 32 => true, 33 => true, 34 => true, 35 => true, 36 => true, 37 => true, 38 => true, 39 => true, 40 => true, 41 => true, 42 => true, 43 => true, 44 => true, 47 => true, 58 => true, 59 => true, 60 => true, 61 => true, 62 => true, 63 => true, 64 => true, 91 => true, 92 => true, 93 => true, 94 => true, 95 => true, 96 => true, 123 => true, 124 => true, 125 => true, 126 => true, 127 => true, 8800 => true, 8814 => true, 8815 => true, ); = 128 && $codePoint <= 159) { return true; } if ($codePoint >= 2155 && $codePoint <= 2207) { return true; } if ($codePoint >= 3676 && $codePoint <= 3712) { return true; } if ($codePoint >= 3808 && $codePoint <= 3839) { return true; } if ($codePoint >= 4059 && $codePoint <= 4095) { return true; } if ($codePoint >= 4256 && $codePoint <= 4293) { return true; } if ($codePoint >= 6849 && $codePoint <= 6911) { return true; } if ($codePoint >= 11859 && $codePoint <= 11903) { return true; } if ($codePoint >= 42955 && $codePoint <= 42996) { return true; } if ($codePoint >= 55296 && $codePoint <= 57343) { return true; } if ($codePoint >= 57344 && $codePoint <= 63743) { return true; } if ($codePoint >= 64218 && $codePoint <= 64255) { return true; } if ($codePoint >= 64976 && $codePoint <= 65007) { return true; } if ($codePoint >= 65630 && $codePoint <= 65663) { return true; } if ($codePoint >= 65953 && $codePoint <= 65999) { return true; } if ($codePoint >= 66046 && $codePoint <= 66175) { return true; } if ($codePoint >= 66518 && $codePoint <= 66559) { return true; } if ($codePoint >= 66928 && $codePoint <= 67071) { return true; } if ($codePoint >= 67432 && $codePoint <= 67583) { return true; } if ($codePoint >= 67760 && $codePoint <= 67807) { return true; } if ($codePoint >= 67904 && $codePoint <= 67967) { return true; } if ($codePoint >= 68256 && $codePoint <= 68287) { return true; } if ($codePoint >= 68528 && $codePoint <= 68607) { return true; } if ($codePoint >= 68681 && $codePoint <= 68735) { return true; } if ($codePoint >= 68922 && $codePoint <= 69215) { return true; } if ($codePoint >= 69298 && $codePoint <= 69375) { return true; } if ($codePoint >= 69466 && $codePoint <= 69551) { return true; } if ($codePoint >= 70207 && $codePoint <= 70271) { return true; } if ($codePoint >= 70517 && $codePoint <= 70655) { return true; } if ($codePoint >= 70874 && $codePoint <= 71039) { return true; } if ($codePoint >= 71134 && $codePoint <= 71167) { return true; } if ($codePoint >= 71370 && $codePoint <= 71423) { return true; } if ($codePoint >= 71488 && $codePoint <= 71679) { return true; } if ($codePoint >= 71740 && $codePoint <= 71839) { return true; } if ($codePoint >= 72026 && $codePoint <= 72095) { return true; } if ($codePoint >= 72441 && $codePoint <= 72703) { return true; } if ($codePoint >= 72887 && $codePoint <= 72959) { return true; } if ($codePoint >= 73130 && $codePoint <= 73439) { return true; } if ($codePoint >= 73465 && $codePoint <= 73647) { return true; } if ($codePoint >= 74650 && $codePoint <= 74751) { return true; } if ($codePoint >= 75076 && $codePoint <= 77823) { return true; } if ($codePoint >= 78905 && $codePoint <= 82943) { return true; } if ($codePoint >= 83527 && $codePoint <= 92159) { return true; } if ($codePoint >= 92784 && $codePoint <= 92879) { return true; } if ($codePoint >= 93072 && $codePoint <= 93759) { return true; } if ($codePoint >= 93851 && $codePoint <= 93951) { return true; } if ($codePoint >= 94112 && $codePoint <= 94175) { return true; } if ($codePoint >= 101590 && $codePoint <= 101631) { return true; } if ($codePoint >= 101641 && $codePoint <= 110591) { return true; } if ($codePoint >= 110879 && $codePoint <= 110927) { return true; } if ($codePoint >= 111356 && $codePoint <= 113663) { return true; } if ($codePoint >= 113828 && $codePoint <= 118783) { return true; } if ($codePoint >= 119366 && $codePoint <= 119519) { return true; } if ($codePoint >= 119673 && $codePoint <= 119807) { return true; } if ($codePoint >= 121520 && $codePoint <= 122879) { return true; } if ($codePoint >= 122923 && $codePoint <= 123135) { return true; } if ($codePoint >= 123216 && $codePoint <= 123583) { return true; } if ($codePoint >= 123648 && $codePoint <= 124927) { return true; } if ($codePoint >= 125143 && $codePoint <= 125183) { return true; } if ($codePoint >= 125280 && $codePoint <= 126064) { return true; } if ($codePoint >= 126133 && $codePoint <= 126208) { return true; } if ($codePoint >= 126270 && $codePoint <= 126463) { return true; } if ($codePoint >= 126652 && $codePoint <= 126703) { return true; } if ($codePoint >= 126706 && $codePoint <= 126975) { return true; } if ($codePoint >= 127406 && $codePoint <= 127461) { return true; } if ($codePoint >= 127590 && $codePoint <= 127743) { return true; } if ($codePoint >= 129202 && $codePoint <= 129279) { return true; } if ($codePoint >= 129751 && $codePoint <= 129791) { return true; } if ($codePoint >= 129995 && $codePoint <= 130031) { return true; } if ($codePoint >= 130042 && $codePoint <= 131069) { return true; } if ($codePoint >= 173790 && $codePoint <= 173823) { return true; } if ($codePoint >= 191457 && $codePoint <= 194559) { return true; } if ($codePoint >= 195102 && $codePoint <= 196605) { return true; } if ($codePoint >= 201547 && $codePoint <= 262141) { return true; } if ($codePoint >= 262144 && $codePoint <= 327677) { return true; } if ($codePoint >= 327680 && $codePoint <= 393213) { return true; } if ($codePoint >= 393216 && $codePoint <= 458749) { return true; } if ($codePoint >= 458752 && $codePoint <= 524285) { return true; } if ($codePoint >= 524288 && $codePoint <= 589821) { return true; } if ($codePoint >= 589824 && $codePoint <= 655357) { return true; } if ($codePoint >= 655360 && $codePoint <= 720893) { return true; } if ($codePoint >= 720896 && $codePoint <= 786429) { return true; } if ($codePoint >= 786432 && $codePoint <= 851965) { return true; } if ($codePoint >= 851968 && $codePoint <= 917501) { return true; } if ($codePoint >= 917536 && $codePoint <= 917631) { return true; } if ($codePoint >= 917632 && $codePoint <= 917759) { return true; } if ($codePoint >= 918000 && $codePoint <= 983037) { return true; } if ($codePoint >= 983040 && $codePoint <= 1048573) { return true; } if ($codePoint >= 1048576 && $codePoint <= 1114109) { return true; } return false; } } true, 889 => true, 896 => true, 897 => true, 898 => true, 899 => true, 907 => true, 909 => true, 930 => true, 1216 => true, 1328 => true, 1367 => true, 1368 => true, 1419 => true, 1420 => true, 1424 => true, 1480 => true, 1481 => true, 1482 => true, 1483 => true, 1484 => true, 1485 => true, 1486 => true, 1487 => true, 1515 => true, 1516 => true, 1517 => true, 1518 => true, 1525 => true, 1526 => true, 1527 => true, 1528 => true, 1529 => true, 1530 => true, 1531 => true, 1532 => true, 1533 => true, 1534 => true, 1535 => true, 1536 => true, 1537 => true, 1538 => true, 1539 => true, 1540 => true, 1541 => true, 1564 => true, 1565 => true, 1757 => true, 1806 => true, 1807 => true, 1867 => true, 1868 => true, 1970 => true, 1971 => true, 1972 => true, 1973 => true, 1974 => true, 1975 => true, 1976 => true, 1977 => true, 1978 => true, 1979 => true, 1980 => true, 1981 => true, 1982 => true, 1983 => true, 2043 => true, 2044 => true, 2094 => true, 2095 => true, 2111 => true, 2140 => true, 2141 => true, 2143 => true, 2229 => true, 2248 => true, 2249 => true, 2250 => true, 2251 => true, 2252 => true, 2253 => true, 2254 => true, 2255 => true, 2256 => true, 2257 => true, 2258 => true, 2274 => true, 2436 => true, 2445 => true, 2446 => true, 2449 => true, 2450 => true, 2473 => true, 2481 => true, 2483 => true, 2484 => true, 2485 => true, 2490 => true, 2491 => true, 2501 => true, 2502 => true, 2505 => true, 2506 => true, 2511 => true, 2512 => true, 2513 => true, 2514 => true, 2515 => true, 2516 => true, 2517 => true, 2518 => true, 2520 => true, 2521 => true, 2522 => true, 2523 => true, 2526 => true, 2532 => true, 2533 => true, 2559 => true, 2560 => true, 2564 => true, 2571 => true, 2572 => true, 2573 => true, 2574 => true, 2577 => true, 2578 => true, 2601 => true, 2609 => true, 2612 => true, 2615 => true, 2618 => true, 2619 => true, 2621 => true, 2627 => true, 2628 => true, 2629 => true, 2630 => true, 2633 => true, 2634 => true, 2638 => true, 2639 => true, 2640 => true, 2642 => true, 2643 => true, 2644 => true, 2645 => true, 2646 => true, 2647 => true, 2648 => true, 2653 => true, 2655 => true, 2656 => true, 2657 => true, 2658 => true, 2659 => true, 2660 => true, 2661 => true, 2679 => true, 2680 => true, 2681 => true, 2682 => true, 2683 => true, 2684 => true, 2685 => true, 2686 => true, 2687 => true, 2688 => true, 2692 => true, 2702 => true, 2706 => true, 2729 => true, 2737 => true, 2740 => true, 2746 => true, 2747 => true, 2758 => true, 2762 => true, 2766 => true, 2767 => true, 2769 => true, 2770 => true, 2771 => true, 2772 => true, 2773 => true, 2774 => true, 2775 => true, 2776 => true, 2777 => true, 2778 => true, 2779 => true, 2780 => true, 2781 => true, 2782 => true, 2783 => true, 2788 => true, 2789 => true, 2802 => true, 2803 => true, 2804 => true, 2805 => true, 2806 => true, 2807 => true, 2808 => true, 2816 => true, 2820 => true, 2829 => true, 2830 => true, 2833 => true, 2834 => true, 2857 => true, 2865 => true, 2868 => true, 2874 => true, 2875 => true, 2885 => true, 2886 => true, 2889 => true, 2890 => true, 2894 => true, 2895 => true, 2896 => true, 2897 => true, 2898 => true, 2899 => true, 2900 => true, 2904 => true, 2905 => true, 2906 => true, 2907 => true, 2910 => true, 2916 => true, 2917 => true, 2936 => true, 2937 => true, 2938 => true, 2939 => true, 2940 => true, 2941 => true, 2942 => true, 2943 => true, 2944 => true, 2945 => true, 2948 => true, 2955 => true, 2956 => true, 2957 => true, 2961 => true, 2966 => true, 2967 => true, 2968 => true, 2971 => true, 2973 => true, 2976 => true, 2977 => true, 2978 => true, 2981 => true, 2982 => true, 2983 => true, 2987 => true, 2988 => true, 2989 => true, 3002 => true, 3003 => true, 3004 => true, 3005 => true, 3011 => true, 3012 => true, 3013 => true, 3017 => true, 3022 => true, 3023 => true, 3025 => true, 3026 => true, 3027 => true, 3028 => true, 3029 => true, 3030 => true, 3032 => true, 3033 => true, 3034 => true, 3035 => true, 3036 => true, 3037 => true, 3038 => true, 3039 => true, 3040 => true, 3041 => true, 3042 => true, 3043 => true, 3044 => true, 3045 => true, 3067 => true, 3068 => true, 3069 => true, 3070 => true, 3071 => true, 3085 => true, 3089 => true, 3113 => true, 3130 => true, 3131 => true, 3132 => true, 3141 => true, 3145 => true, 3150 => true, 3151 => true, 3152 => true, 3153 => true, 3154 => true, 3155 => true, 3156 => true, 3159 => true, 3163 => true, 3164 => true, 3165 => true, 3166 => true, 3167 => true, 3172 => true, 3173 => true, 3184 => true, 3185 => true, 3186 => true, 3187 => true, 3188 => true, 3189 => true, 3190 => true, 3213 => true, 3217 => true, 3241 => true, 3252 => true, 3258 => true, 3259 => true, 3269 => true, 3273 => true, 3278 => true, 3279 => true, 3280 => true, 3281 => true, 3282 => true, 3283 => true, 3284 => true, 3287 => true, 3288 => true, 3289 => true, 3290 => true, 3291 => true, 3292 => true, 3293 => true, 3295 => true, 3300 => true, 3301 => true, 3312 => true, 3315 => true, 3316 => true, 3317 => true, 3318 => true, 3319 => true, 3320 => true, 3321 => true, 3322 => true, 3323 => true, 3324 => true, 3325 => true, 3326 => true, 3327 => true, 3341 => true, 3345 => true, 3397 => true, 3401 => true, 3408 => true, 3409 => true, 3410 => true, 3411 => true, 3428 => true, 3429 => true, 3456 => true, 3460 => true, 3479 => true, 3480 => true, 3481 => true, 3506 => true, 3516 => true, 3518 => true, 3519 => true, 3527 => true, 3528 => true, 3529 => true, 3531 => true, 3532 => true, 3533 => true, 3534 => true, 3541 => true, 3543 => true, 3552 => true, 3553 => true, 3554 => true, 3555 => true, 3556 => true, 3557 => true, 3568 => true, 3569 => true, 3573 => true, 3574 => true, 3575 => true, 3576 => true, 3577 => true, 3578 => true, 3579 => true, 3580 => true, 3581 => true, 3582 => true, 3583 => true, 3584 => true, 3643 => true, 3644 => true, 3645 => true, 3646 => true, 3715 => true, 3717 => true, 3723 => true, 3748 => true, 3750 => true, 3774 => true, 3775 => true, 3781 => true, 3783 => true, 3790 => true, 3791 => true, 3802 => true, 3803 => true, 3912 => true, 3949 => true, 3950 => true, 3951 => true, 3952 => true, 3992 => true, 4029 => true, 4045 => true, 4294 => true, 4296 => true, 4297 => true, 4298 => true, 4299 => true, 4300 => true, 4302 => true, 4303 => true, 4447 => true, 4448 => true, 4681 => true, 4686 => true, 4687 => true, 4695 => true, 4697 => true, 4702 => true, 4703 => true, 4745 => true, 4750 => true, 4751 => true, 4785 => true, 4790 => true, 4791 => true, 4799 => true, 4801 => true, 4806 => true, 4807 => true, 4823 => true, 4881 => true, 4886 => true, 4887 => true, 4955 => true, 4956 => true, 4989 => true, 4990 => true, 4991 => true, 5018 => true, 5019 => true, 5020 => true, 5021 => true, 5022 => true, 5023 => true, 5110 => true, 5111 => true, 5118 => true, 5119 => true, 5760 => true, 5789 => true, 5790 => true, 5791 => true, 5881 => true, 5882 => true, 5883 => true, 5884 => true, 5885 => true, 5886 => true, 5887 => true, 5901 => true, 5909 => true, 5910 => true, 5911 => true, 5912 => true, 5913 => true, 5914 => true, 5915 => true, 5916 => true, 5917 => true, 5918 => true, 5919 => true, 5943 => true, 5944 => true, 5945 => true, 5946 => true, 5947 => true, 5948 => true, 5949 => true, 5950 => true, 5951 => true, 5972 => true, 5973 => true, 5974 => true, 5975 => true, 5976 => true, 5977 => true, 5978 => true, 5979 => true, 5980 => true, 5981 => true, 5982 => true, 5983 => true, 5997 => true, 6001 => true, 6004 => true, 6005 => true, 6006 => true, 6007 => true, 6008 => true, 6009 => true, 6010 => true, 6011 => true, 6012 => true, 6013 => true, 6014 => true, 6015 => true, 6068 => true, 6069 => true, 6110 => true, 6111 => true, 6122 => true, 6123 => true, 6124 => true, 6125 => true, 6126 => true, 6127 => true, 6138 => true, 6139 => true, 6140 => true, 6141 => true, 6142 => true, 6143 => true, 6150 => true, 6158 => true, 6159 => true, 6170 => true, 6171 => true, 6172 => true, 6173 => true, 6174 => true, 6175 => true, 6265 => true, 6266 => true, 6267 => true, 6268 => true, 6269 => true, 6270 => true, 6271 => true, 6315 => true, 6316 => true, 6317 => true, 6318 => true, 6319 => true, 6390 => true, 6391 => true, 6392 => true, 6393 => true, 6394 => true, 6395 => true, 6396 => true, 6397 => true, 6398 => true, 6399 => true, 6431 => true, 6444 => true, 6445 => true, 6446 => true, 6447 => true, 6460 => true, 6461 => true, 6462 => true, 6463 => true, 6465 => true, 6466 => true, 6467 => true, 6510 => true, 6511 => true, 6517 => true, 6518 => true, 6519 => true, 6520 => true, 6521 => true, 6522 => true, 6523 => true, 6524 => true, 6525 => true, 6526 => true, 6527 => true, 6572 => true, 6573 => true, 6574 => true, 6575 => true, 6602 => true, 6603 => true, 6604 => true, 6605 => true, 6606 => true, 6607 => true, 6619 => true, 6620 => true, 6621 => true, 6684 => true, 6685 => true, 6751 => true, 6781 => true, 6782 => true, 6794 => true, 6795 => true, 6796 => true, 6797 => true, 6798 => true, 6799 => true, 6810 => true, 6811 => true, 6812 => true, 6813 => true, 6814 => true, 6815 => true, 6830 => true, 6831 => true, 6988 => true, 6989 => true, 6990 => true, 6991 => true, 7037 => true, 7038 => true, 7039 => true, 7156 => true, 7157 => true, 7158 => true, 7159 => true, 7160 => true, 7161 => true, 7162 => true, 7163 => true, 7224 => true, 7225 => true, 7226 => true, 7242 => true, 7243 => true, 7244 => true, 7305 => true, 7306 => true, 7307 => true, 7308 => true, 7309 => true, 7310 => true, 7311 => true, 7355 => true, 7356 => true, 7368 => true, 7369 => true, 7370 => true, 7371 => true, 7372 => true, 7373 => true, 7374 => true, 7375 => true, 7419 => true, 7420 => true, 7421 => true, 7422 => true, 7423 => true, 7674 => true, 7958 => true, 7959 => true, 7966 => true, 7967 => true, 8006 => true, 8007 => true, 8014 => true, 8015 => true, 8024 => true, 8026 => true, 8028 => true, 8030 => true, 8062 => true, 8063 => true, 8117 => true, 8133 => true, 8148 => true, 8149 => true, 8156 => true, 8176 => true, 8177 => true, 8181 => true, 8191 => true, 8206 => true, 8207 => true, 8228 => true, 8229 => true, 8230 => true, 8232 => true, 8233 => true, 8234 => true, 8235 => true, 8236 => true, 8237 => true, 8238 => true, 8289 => true, 8290 => true, 8291 => true, 8293 => true, 8294 => true, 8295 => true, 8296 => true, 8297 => true, 8298 => true, 8299 => true, 8300 => true, 8301 => true, 8302 => true, 8303 => true, 8306 => true, 8307 => true, 8335 => true, 8349 => true, 8350 => true, 8351 => true, 8384 => true, 8385 => true, 8386 => true, 8387 => true, 8388 => true, 8389 => true, 8390 => true, 8391 => true, 8392 => true, 8393 => true, 8394 => true, 8395 => true, 8396 => true, 8397 => true, 8398 => true, 8399 => true, 8433 => true, 8434 => true, 8435 => true, 8436 => true, 8437 => true, 8438 => true, 8439 => true, 8440 => true, 8441 => true, 8442 => true, 8443 => true, 8444 => true, 8445 => true, 8446 => true, 8447 => true, 8498 => true, 8579 => true, 8588 => true, 8589 => true, 8590 => true, 8591 => true, 9255 => true, 9256 => true, 9257 => true, 9258 => true, 9259 => true, 9260 => true, 9261 => true, 9262 => true, 9263 => true, 9264 => true, 9265 => true, 9266 => true, 9267 => true, 9268 => true, 9269 => true, 9270 => true, 9271 => true, 9272 => true, 9273 => true, 9274 => true, 9275 => true, 9276 => true, 9277 => true, 9278 => true, 9279 => true, 9291 => true, 9292 => true, 9293 => true, 9294 => true, 9295 => true, 9296 => true, 9297 => true, 9298 => true, 9299 => true, 9300 => true, 9301 => true, 9302 => true, 9303 => true, 9304 => true, 9305 => true, 9306 => true, 9307 => true, 9308 => true, 9309 => true, 9310 => true, 9311 => true, 9352 => true, 9353 => true, 9354 => true, 9355 => true, 9356 => true, 9357 => true, 9358 => true, 9359 => true, 9360 => true, 9361 => true, 9362 => true, 9363 => true, 9364 => true, 9365 => true, 9366 => true, 9367 => true, 9368 => true, 9369 => true, 9370 => true, 9371 => true, 11124 => true, 11125 => true, 11158 => true, 11311 => true, 11359 => true, 11508 => true, 11509 => true, 11510 => true, 11511 => true, 11512 => true, 11558 => true, 11560 => true, 11561 => true, 11562 => true, 11563 => true, 11564 => true, 11566 => true, 11567 => true, 11624 => true, 11625 => true, 11626 => true, 11627 => true, 11628 => true, 11629 => true, 11630 => true, 11633 => true, 11634 => true, 11635 => true, 11636 => true, 11637 => true, 11638 => true, 11639 => true, 11640 => true, 11641 => true, 11642 => true, 11643 => true, 11644 => true, 11645 => true, 11646 => true, 11671 => true, 11672 => true, 11673 => true, 11674 => true, 11675 => true, 11676 => true, 11677 => true, 11678 => true, 11679 => true, 11687 => true, 11695 => true, 11703 => true, 11711 => true, 11719 => true, 11727 => true, 11735 => true, 11743 => true, 11930 => true, 12020 => true, 12021 => true, 12022 => true, 12023 => true, 12024 => true, 12025 => true, 12026 => true, 12027 => true, 12028 => true, 12029 => true, 12030 => true, 12031 => true, 12246 => true, 12247 => true, 12248 => true, 12249 => true, 12250 => true, 12251 => true, 12252 => true, 12253 => true, 12254 => true, 12255 => true, 12256 => true, 12257 => true, 12258 => true, 12259 => true, 12260 => true, 12261 => true, 12262 => true, 12263 => true, 12264 => true, 12265 => true, 12266 => true, 12267 => true, 12268 => true, 12269 => true, 12270 => true, 12271 => true, 12272 => true, 12273 => true, 12274 => true, 12275 => true, 12276 => true, 12277 => true, 12278 => true, 12279 => true, 12280 => true, 12281 => true, 12282 => true, 12283 => true, 12284 => true, 12285 => true, 12286 => true, 12287 => true, 12352 => true, 12439 => true, 12440 => true, 12544 => true, 12545 => true, 12546 => true, 12547 => true, 12548 => true, 12592 => true, 12644 => true, 12687 => true, 12772 => true, 12773 => true, 12774 => true, 12775 => true, 12776 => true, 12777 => true, 12778 => true, 12779 => true, 12780 => true, 12781 => true, 12782 => true, 12783 => true, 12831 => true, 13250 => true, 13255 => true, 13272 => true, 40957 => true, 40958 => true, 40959 => true, 42125 => true, 42126 => true, 42127 => true, 42183 => true, 42184 => true, 42185 => true, 42186 => true, 42187 => true, 42188 => true, 42189 => true, 42190 => true, 42191 => true, 42540 => true, 42541 => true, 42542 => true, 42543 => true, 42544 => true, 42545 => true, 42546 => true, 42547 => true, 42548 => true, 42549 => true, 42550 => true, 42551 => true, 42552 => true, 42553 => true, 42554 => true, 42555 => true, 42556 => true, 42557 => true, 42558 => true, 42559 => true, 42744 => true, 42745 => true, 42746 => true, 42747 => true, 42748 => true, 42749 => true, 42750 => true, 42751 => true, 42944 => true, 42945 => true, 43053 => true, 43054 => true, 43055 => true, 43066 => true, 43067 => true, 43068 => true, 43069 => true, 43070 => true, 43071 => true, 43128 => true, 43129 => true, 43130 => true, 43131 => true, 43132 => true, 43133 => true, 43134 => true, 43135 => true, 43206 => true, 43207 => true, 43208 => true, 43209 => true, 43210 => true, 43211 => true, 43212 => true, 43213 => true, 43226 => true, 43227 => true, 43228 => true, 43229 => true, 43230 => true, 43231 => true, 43348 => true, 43349 => true, 43350 => true, 43351 => true, 43352 => true, 43353 => true, 43354 => true, 43355 => true, 43356 => true, 43357 => true, 43358 => true, 43389 => true, 43390 => true, 43391 => true, 43470 => true, 43482 => true, 43483 => true, 43484 => true, 43485 => true, 43519 => true, 43575 => true, 43576 => true, 43577 => true, 43578 => true, 43579 => true, 43580 => true, 43581 => true, 43582 => true, 43583 => true, 43598 => true, 43599 => true, 43610 => true, 43611 => true, 43715 => true, 43716 => true, 43717 => true, 43718 => true, 43719 => true, 43720 => true, 43721 => true, 43722 => true, 43723 => true, 43724 => true, 43725 => true, 43726 => true, 43727 => true, 43728 => true, 43729 => true, 43730 => true, 43731 => true, 43732 => true, 43733 => true, 43734 => true, 43735 => true, 43736 => true, 43737 => true, 43738 => true, 43767 => true, 43768 => true, 43769 => true, 43770 => true, 43771 => true, 43772 => true, 43773 => true, 43774 => true, 43775 => true, 43776 => true, 43783 => true, 43784 => true, 43791 => true, 43792 => true, 43799 => true, 43800 => true, 43801 => true, 43802 => true, 43803 => true, 43804 => true, 43805 => true, 43806 => true, 43807 => true, 43815 => true, 43823 => true, 43884 => true, 43885 => true, 43886 => true, 43887 => true, 44014 => true, 44015 => true, 44026 => true, 44027 => true, 44028 => true, 44029 => true, 44030 => true, 44031 => true, 55204 => true, 55205 => true, 55206 => true, 55207 => true, 55208 => true, 55209 => true, 55210 => true, 55211 => true, 55212 => true, 55213 => true, 55214 => true, 55215 => true, 55239 => true, 55240 => true, 55241 => true, 55242 => true, 55292 => true, 55293 => true, 55294 => true, 55295 => true, 64110 => true, 64111 => true, 64263 => true, 64264 => true, 64265 => true, 64266 => true, 64267 => true, 64268 => true, 64269 => true, 64270 => true, 64271 => true, 64272 => true, 64273 => true, 64274 => true, 64280 => true, 64281 => true, 64282 => true, 64283 => true, 64284 => true, 64311 => true, 64317 => true, 64319 => true, 64322 => true, 64325 => true, 64450 => true, 64451 => true, 64452 => true, 64453 => true, 64454 => true, 64455 => true, 64456 => true, 64457 => true, 64458 => true, 64459 => true, 64460 => true, 64461 => true, 64462 => true, 64463 => true, 64464 => true, 64465 => true, 64466 => true, 64832 => true, 64833 => true, 64834 => true, 64835 => true, 64836 => true, 64837 => true, 64838 => true, 64839 => true, 64840 => true, 64841 => true, 64842 => true, 64843 => true, 64844 => true, 64845 => true, 64846 => true, 64847 => true, 64912 => true, 64913 => true, 64968 => true, 64969 => true, 64970 => true, 64971 => true, 64972 => true, 64973 => true, 64974 => true, 64975 => true, 65022 => true, 65023 => true, 65042 => true, 65049 => true, 65050 => true, 65051 => true, 65052 => true, 65053 => true, 65054 => true, 65055 => true, 65072 => true, 65106 => true, 65107 => true, 65127 => true, 65132 => true, 65133 => true, 65134 => true, 65135 => true, 65141 => true, 65277 => true, 65278 => true, 65280 => true, 65440 => true, 65471 => true, 65472 => true, 65473 => true, 65480 => true, 65481 => true, 65488 => true, 65489 => true, 65496 => true, 65497 => true, 65501 => true, 65502 => true, 65503 => true, 65511 => true, 65519 => true, 65520 => true, 65521 => true, 65522 => true, 65523 => true, 65524 => true, 65525 => true, 65526 => true, 65527 => true, 65528 => true, 65529 => true, 65530 => true, 65531 => true, 65532 => true, 65533 => true, 65534 => true, 65535 => true, 65548 => true, 65575 => true, 65595 => true, 65598 => true, 65614 => true, 65615 => true, 65787 => true, 65788 => true, 65789 => true, 65790 => true, 65791 => true, 65795 => true, 65796 => true, 65797 => true, 65798 => true, 65844 => true, 65845 => true, 65846 => true, 65935 => true, 65949 => true, 65950 => true, 65951 => true, 66205 => true, 66206 => true, 66207 => true, 66257 => true, 66258 => true, 66259 => true, 66260 => true, 66261 => true, 66262 => true, 66263 => true, 66264 => true, 66265 => true, 66266 => true, 66267 => true, 66268 => true, 66269 => true, 66270 => true, 66271 => true, 66300 => true, 66301 => true, 66302 => true, 66303 => true, 66340 => true, 66341 => true, 66342 => true, 66343 => true, 66344 => true, 66345 => true, 66346 => true, 66347 => true, 66348 => true, 66379 => true, 66380 => true, 66381 => true, 66382 => true, 66383 => true, 66427 => true, 66428 => true, 66429 => true, 66430 => true, 66431 => true, 66462 => true, 66500 => true, 66501 => true, 66502 => true, 66503 => true, 66718 => true, 66719 => true, 66730 => true, 66731 => true, 66732 => true, 66733 => true, 66734 => true, 66735 => true, 66772 => true, 66773 => true, 66774 => true, 66775 => true, 66812 => true, 66813 => true, 66814 => true, 66815 => true, 66856 => true, 66857 => true, 66858 => true, 66859 => true, 66860 => true, 66861 => true, 66862 => true, 66863 => true, 66916 => true, 66917 => true, 66918 => true, 66919 => true, 66920 => true, 66921 => true, 66922 => true, 66923 => true, 66924 => true, 66925 => true, 66926 => true, 67383 => true, 67384 => true, 67385 => true, 67386 => true, 67387 => true, 67388 => true, 67389 => true, 67390 => true, 67391 => true, 67414 => true, 67415 => true, 67416 => true, 67417 => true, 67418 => true, 67419 => true, 67420 => true, 67421 => true, 67422 => true, 67423 => true, 67590 => true, 67591 => true, 67593 => true, 67638 => true, 67641 => true, 67642 => true, 67643 => true, 67645 => true, 67646 => true, 67670 => true, 67743 => true, 67744 => true, 67745 => true, 67746 => true, 67747 => true, 67748 => true, 67749 => true, 67750 => true, 67827 => true, 67830 => true, 67831 => true, 67832 => true, 67833 => true, 67834 => true, 67868 => true, 67869 => true, 67870 => true, 67898 => true, 67899 => true, 67900 => true, 67901 => true, 67902 => true, 68024 => true, 68025 => true, 68026 => true, 68027 => true, 68048 => true, 68049 => true, 68100 => true, 68103 => true, 68104 => true, 68105 => true, 68106 => true, 68107 => true, 68116 => true, 68120 => true, 68150 => true, 68151 => true, 68155 => true, 68156 => true, 68157 => true, 68158 => true, 68169 => true, 68170 => true, 68171 => true, 68172 => true, 68173 => true, 68174 => true, 68175 => true, 68185 => true, 68186 => true, 68187 => true, 68188 => true, 68189 => true, 68190 => true, 68191 => true, 68327 => true, 68328 => true, 68329 => true, 68330 => true, 68343 => true, 68344 => true, 68345 => true, 68346 => true, 68347 => true, 68348 => true, 68349 => true, 68350 => true, 68351 => true, 68406 => true, 68407 => true, 68408 => true, 68438 => true, 68439 => true, 68467 => true, 68468 => true, 68469 => true, 68470 => true, 68471 => true, 68498 => true, 68499 => true, 68500 => true, 68501 => true, 68502 => true, 68503 => true, 68504 => true, 68509 => true, 68510 => true, 68511 => true, 68512 => true, 68513 => true, 68514 => true, 68515 => true, 68516 => true, 68517 => true, 68518 => true, 68519 => true, 68520 => true, 68787 => true, 68788 => true, 68789 => true, 68790 => true, 68791 => true, 68792 => true, 68793 => true, 68794 => true, 68795 => true, 68796 => true, 68797 => true, 68798 => true, 68799 => true, 68851 => true, 68852 => true, 68853 => true, 68854 => true, 68855 => true, 68856 => true, 68857 => true, 68904 => true, 68905 => true, 68906 => true, 68907 => true, 68908 => true, 68909 => true, 68910 => true, 68911 => true, 69247 => true, 69290 => true, 69294 => true, 69295 => true, 69416 => true, 69417 => true, 69418 => true, 69419 => true, 69420 => true, 69421 => true, 69422 => true, 69423 => true, 69580 => true, 69581 => true, 69582 => true, 69583 => true, 69584 => true, 69585 => true, 69586 => true, 69587 => true, 69588 => true, 69589 => true, 69590 => true, 69591 => true, 69592 => true, 69593 => true, 69594 => true, 69595 => true, 69596 => true, 69597 => true, 69598 => true, 69599 => true, 69623 => true, 69624 => true, 69625 => true, 69626 => true, 69627 => true, 69628 => true, 69629 => true, 69630 => true, 69631 => true, 69710 => true, 69711 => true, 69712 => true, 69713 => true, 69744 => true, 69745 => true, 69746 => true, 69747 => true, 69748 => true, 69749 => true, 69750 => true, 69751 => true, 69752 => true, 69753 => true, 69754 => true, 69755 => true, 69756 => true, 69757 => true, 69758 => true, 69821 => true, 69826 => true, 69827 => true, 69828 => true, 69829 => true, 69830 => true, 69831 => true, 69832 => true, 69833 => true, 69834 => true, 69835 => true, 69836 => true, 69837 => true, 69838 => true, 69839 => true, 69865 => true, 69866 => true, 69867 => true, 69868 => true, 69869 => true, 69870 => true, 69871 => true, 69882 => true, 69883 => true, 69884 => true, 69885 => true, 69886 => true, 69887 => true, 69941 => true, 69960 => true, 69961 => true, 69962 => true, 69963 => true, 69964 => true, 69965 => true, 69966 => true, 69967 => true, 70007 => true, 70008 => true, 70009 => true, 70010 => true, 70011 => true, 70012 => true, 70013 => true, 70014 => true, 70015 => true, 70112 => true, 70133 => true, 70134 => true, 70135 => true, 70136 => true, 70137 => true, 70138 => true, 70139 => true, 70140 => true, 70141 => true, 70142 => true, 70143 => true, 70162 => true, 70279 => true, 70281 => true, 70286 => true, 70302 => true, 70314 => true, 70315 => true, 70316 => true, 70317 => true, 70318 => true, 70319 => true, 70379 => true, 70380 => true, 70381 => true, 70382 => true, 70383 => true, 70394 => true, 70395 => true, 70396 => true, 70397 => true, 70398 => true, 70399 => true, 70404 => true, 70413 => true, 70414 => true, 70417 => true, 70418 => true, 70441 => true, 70449 => true, 70452 => true, 70458 => true, 70469 => true, 70470 => true, 70473 => true, 70474 => true, 70478 => true, 70479 => true, 70481 => true, 70482 => true, 70483 => true, 70484 => true, 70485 => true, 70486 => true, 70488 => true, 70489 => true, 70490 => true, 70491 => true, 70492 => true, 70500 => true, 70501 => true, 70509 => true, 70510 => true, 70511 => true, 70748 => true, 70754 => true, 70755 => true, 70756 => true, 70757 => true, 70758 => true, 70759 => true, 70760 => true, 70761 => true, 70762 => true, 70763 => true, 70764 => true, 70765 => true, 70766 => true, 70767 => true, 70768 => true, 70769 => true, 70770 => true, 70771 => true, 70772 => true, 70773 => true, 70774 => true, 70775 => true, 70776 => true, 70777 => true, 70778 => true, 70779 => true, 70780 => true, 70781 => true, 70782 => true, 70783 => true, 70856 => true, 70857 => true, 70858 => true, 70859 => true, 70860 => true, 70861 => true, 70862 => true, 70863 => true, 71094 => true, 71095 => true, 71237 => true, 71238 => true, 71239 => true, 71240 => true, 71241 => true, 71242 => true, 71243 => true, 71244 => true, 71245 => true, 71246 => true, 71247 => true, 71258 => true, 71259 => true, 71260 => true, 71261 => true, 71262 => true, 71263 => true, 71277 => true, 71278 => true, 71279 => true, 71280 => true, 71281 => true, 71282 => true, 71283 => true, 71284 => true, 71285 => true, 71286 => true, 71287 => true, 71288 => true, 71289 => true, 71290 => true, 71291 => true, 71292 => true, 71293 => true, 71294 => true, 71295 => true, 71353 => true, 71354 => true, 71355 => true, 71356 => true, 71357 => true, 71358 => true, 71359 => true, 71451 => true, 71452 => true, 71468 => true, 71469 => true, 71470 => true, 71471 => true, 71923 => true, 71924 => true, 71925 => true, 71926 => true, 71927 => true, 71928 => true, 71929 => true, 71930 => true, 71931 => true, 71932 => true, 71933 => true, 71934 => true, 71943 => true, 71944 => true, 71946 => true, 71947 => true, 71956 => true, 71959 => true, 71990 => true, 71993 => true, 71994 => true, 72007 => true, 72008 => true, 72009 => true, 72010 => true, 72011 => true, 72012 => true, 72013 => true, 72014 => true, 72015 => true, 72104 => true, 72105 => true, 72152 => true, 72153 => true, 72165 => true, 72166 => true, 72167 => true, 72168 => true, 72169 => true, 72170 => true, 72171 => true, 72172 => true, 72173 => true, 72174 => true, 72175 => true, 72176 => true, 72177 => true, 72178 => true, 72179 => true, 72180 => true, 72181 => true, 72182 => true, 72183 => true, 72184 => true, 72185 => true, 72186 => true, 72187 => true, 72188 => true, 72189 => true, 72190 => true, 72191 => true, 72264 => true, 72265 => true, 72266 => true, 72267 => true, 72268 => true, 72269 => true, 72270 => true, 72271 => true, 72355 => true, 72356 => true, 72357 => true, 72358 => true, 72359 => true, 72360 => true, 72361 => true, 72362 => true, 72363 => true, 72364 => true, 72365 => true, 72366 => true, 72367 => true, 72368 => true, 72369 => true, 72370 => true, 72371 => true, 72372 => true, 72373 => true, 72374 => true, 72375 => true, 72376 => true, 72377 => true, 72378 => true, 72379 => true, 72380 => true, 72381 => true, 72382 => true, 72383 => true, 72713 => true, 72759 => true, 72774 => true, 72775 => true, 72776 => true, 72777 => true, 72778 => true, 72779 => true, 72780 => true, 72781 => true, 72782 => true, 72783 => true, 72813 => true, 72814 => true, 72815 => true, 72848 => true, 72849 => true, 72872 => true, 72967 => true, 72970 => true, 73015 => true, 73016 => true, 73017 => true, 73019 => true, 73022 => true, 73032 => true, 73033 => true, 73034 => true, 73035 => true, 73036 => true, 73037 => true, 73038 => true, 73039 => true, 73050 => true, 73051 => true, 73052 => true, 73053 => true, 73054 => true, 73055 => true, 73062 => true, 73065 => true, 73103 => true, 73106 => true, 73113 => true, 73114 => true, 73115 => true, 73116 => true, 73117 => true, 73118 => true, 73119 => true, 73649 => true, 73650 => true, 73651 => true, 73652 => true, 73653 => true, 73654 => true, 73655 => true, 73656 => true, 73657 => true, 73658 => true, 73659 => true, 73660 => true, 73661 => true, 73662 => true, 73663 => true, 73714 => true, 73715 => true, 73716 => true, 73717 => true, 73718 => true, 73719 => true, 73720 => true, 73721 => true, 73722 => true, 73723 => true, 73724 => true, 73725 => true, 73726 => true, 74863 => true, 74869 => true, 74870 => true, 74871 => true, 74872 => true, 74873 => true, 74874 => true, 74875 => true, 74876 => true, 74877 => true, 74878 => true, 74879 => true, 78895 => true, 78896 => true, 78897 => true, 78898 => true, 78899 => true, 78900 => true, 78901 => true, 78902 => true, 78903 => true, 78904 => true, 92729 => true, 92730 => true, 92731 => true, 92732 => true, 92733 => true, 92734 => true, 92735 => true, 92767 => true, 92778 => true, 92779 => true, 92780 => true, 92781 => true, 92910 => true, 92911 => true, 92918 => true, 92919 => true, 92920 => true, 92921 => true, 92922 => true, 92923 => true, 92924 => true, 92925 => true, 92926 => true, 92927 => true, 92998 => true, 92999 => true, 93000 => true, 93001 => true, 93002 => true, 93003 => true, 93004 => true, 93005 => true, 93006 => true, 93007 => true, 93018 => true, 93026 => true, 93048 => true, 93049 => true, 93050 => true, 93051 => true, 93052 => true, 94027 => true, 94028 => true, 94029 => true, 94030 => true, 94088 => true, 94089 => true, 94090 => true, 94091 => true, 94092 => true, 94093 => true, 94094 => true, 94181 => true, 94182 => true, 94183 => true, 94184 => true, 94185 => true, 94186 => true, 94187 => true, 94188 => true, 94189 => true, 94190 => true, 94191 => true, 94194 => true, 94195 => true, 94196 => true, 94197 => true, 94198 => true, 94199 => true, 94200 => true, 94201 => true, 94202 => true, 94203 => true, 94204 => true, 94205 => true, 94206 => true, 94207 => true, 100344 => true, 100345 => true, 100346 => true, 100347 => true, 100348 => true, 100349 => true, 100350 => true, 100351 => true, 110931 => true, 110932 => true, 110933 => true, 110934 => true, 110935 => true, 110936 => true, 110937 => true, 110938 => true, 110939 => true, 110940 => true, 110941 => true, 110942 => true, 110943 => true, 110944 => true, 110945 => true, 110946 => true, 110947 => true, 110952 => true, 110953 => true, 110954 => true, 110955 => true, 110956 => true, 110957 => true, 110958 => true, 110959 => true, 113771 => true, 113772 => true, 113773 => true, 113774 => true, 113775 => true, 113789 => true, 113790 => true, 113791 => true, 113801 => true, 113802 => true, 113803 => true, 113804 => true, 113805 => true, 113806 => true, 113807 => true, 113818 => true, 113819 => true, 119030 => true, 119031 => true, 119032 => true, 119033 => true, 119034 => true, 119035 => true, 119036 => true, 119037 => true, 119038 => true, 119039 => true, 119079 => true, 119080 => true, 119155 => true, 119156 => true, 119157 => true, 119158 => true, 119159 => true, 119160 => true, 119161 => true, 119162 => true, 119273 => true, 119274 => true, 119275 => true, 119276 => true, 119277 => true, 119278 => true, 119279 => true, 119280 => true, 119281 => true, 119282 => true, 119283 => true, 119284 => true, 119285 => true, 119286 => true, 119287 => true, 119288 => true, 119289 => true, 119290 => true, 119291 => true, 119292 => true, 119293 => true, 119294 => true, 119295 => true, 119540 => true, 119541 => true, 119542 => true, 119543 => true, 119544 => true, 119545 => true, 119546 => true, 119547 => true, 119548 => true, 119549 => true, 119550 => true, 119551 => true, 119639 => true, 119640 => true, 119641 => true, 119642 => true, 119643 => true, 119644 => true, 119645 => true, 119646 => true, 119647 => true, 119893 => true, 119965 => true, 119968 => true, 119969 => true, 119971 => true, 119972 => true, 119975 => true, 119976 => true, 119981 => true, 119994 => true, 119996 => true, 120004 => true, 120070 => true, 120075 => true, 120076 => true, 120085 => true, 120093 => true, 120122 => true, 120127 => true, 120133 => true, 120135 => true, 120136 => true, 120137 => true, 120145 => true, 120486 => true, 120487 => true, 120780 => true, 120781 => true, 121484 => true, 121485 => true, 121486 => true, 121487 => true, 121488 => true, 121489 => true, 121490 => true, 121491 => true, 121492 => true, 121493 => true, 121494 => true, 121495 => true, 121496 => true, 121497 => true, 121498 => true, 121504 => true, 122887 => true, 122905 => true, 122906 => true, 122914 => true, 122917 => true, 123181 => true, 123182 => true, 123183 => true, 123198 => true, 123199 => true, 123210 => true, 123211 => true, 123212 => true, 123213 => true, 123642 => true, 123643 => true, 123644 => true, 123645 => true, 123646 => true, 125125 => true, 125126 => true, 125260 => true, 125261 => true, 125262 => true, 125263 => true, 125274 => true, 125275 => true, 125276 => true, 125277 => true, 126468 => true, 126496 => true, 126499 => true, 126501 => true, 126502 => true, 126504 => true, 126515 => true, 126520 => true, 126522 => true, 126524 => true, 126525 => true, 126526 => true, 126527 => true, 126528 => true, 126529 => true, 126531 => true, 126532 => true, 126533 => true, 126534 => true, 126536 => true, 126538 => true, 126540 => true, 126544 => true, 126547 => true, 126549 => true, 126550 => true, 126552 => true, 126554 => true, 126556 => true, 126558 => true, 126560 => true, 126563 => true, 126565 => true, 126566 => true, 126571 => true, 126579 => true, 126584 => true, 126589 => true, 126591 => true, 126602 => true, 126620 => true, 126621 => true, 126622 => true, 126623 => true, 126624 => true, 126628 => true, 126634 => true, 127020 => true, 127021 => true, 127022 => true, 127023 => true, 127124 => true, 127125 => true, 127126 => true, 127127 => true, 127128 => true, 127129 => true, 127130 => true, 127131 => true, 127132 => true, 127133 => true, 127134 => true, 127135 => true, 127151 => true, 127152 => true, 127168 => true, 127184 => true, 127222 => true, 127223 => true, 127224 => true, 127225 => true, 127226 => true, 127227 => true, 127228 => true, 127229 => true, 127230 => true, 127231 => true, 127232 => true, 127491 => true, 127492 => true, 127493 => true, 127494 => true, 127495 => true, 127496 => true, 127497 => true, 127498 => true, 127499 => true, 127500 => true, 127501 => true, 127502 => true, 127503 => true, 127548 => true, 127549 => true, 127550 => true, 127551 => true, 127561 => true, 127562 => true, 127563 => true, 127564 => true, 127565 => true, 127566 => true, 127567 => true, 127570 => true, 127571 => true, 127572 => true, 127573 => true, 127574 => true, 127575 => true, 127576 => true, 127577 => true, 127578 => true, 127579 => true, 127580 => true, 127581 => true, 127582 => true, 127583 => true, 128728 => true, 128729 => true, 128730 => true, 128731 => true, 128732 => true, 128733 => true, 128734 => true, 128735 => true, 128749 => true, 128750 => true, 128751 => true, 128765 => true, 128766 => true, 128767 => true, 128884 => true, 128885 => true, 128886 => true, 128887 => true, 128888 => true, 128889 => true, 128890 => true, 128891 => true, 128892 => true, 128893 => true, 128894 => true, 128895 => true, 128985 => true, 128986 => true, 128987 => true, 128988 => true, 128989 => true, 128990 => true, 128991 => true, 129004 => true, 129005 => true, 129006 => true, 129007 => true, 129008 => true, 129009 => true, 129010 => true, 129011 => true, 129012 => true, 129013 => true, 129014 => true, 129015 => true, 129016 => true, 129017 => true, 129018 => true, 129019 => true, 129020 => true, 129021 => true, 129022 => true, 129023 => true, 129036 => true, 129037 => true, 129038 => true, 129039 => true, 129096 => true, 129097 => true, 129098 => true, 129099 => true, 129100 => true, 129101 => true, 129102 => true, 129103 => true, 129114 => true, 129115 => true, 129116 => true, 129117 => true, 129118 => true, 129119 => true, 129160 => true, 129161 => true, 129162 => true, 129163 => true, 129164 => true, 129165 => true, 129166 => true, 129167 => true, 129198 => true, 129199 => true, 129401 => true, 129484 => true, 129620 => true, 129621 => true, 129622 => true, 129623 => true, 129624 => true, 129625 => true, 129626 => true, 129627 => true, 129628 => true, 129629 => true, 129630 => true, 129631 => true, 129646 => true, 129647 => true, 129653 => true, 129654 => true, 129655 => true, 129659 => true, 129660 => true, 129661 => true, 129662 => true, 129663 => true, 129671 => true, 129672 => true, 129673 => true, 129674 => true, 129675 => true, 129676 => true, 129677 => true, 129678 => true, 129679 => true, 129705 => true, 129706 => true, 129707 => true, 129708 => true, 129709 => true, 129710 => true, 129711 => true, 129719 => true, 129720 => true, 129721 => true, 129722 => true, 129723 => true, 129724 => true, 129725 => true, 129726 => true, 129727 => true, 129731 => true, 129732 => true, 129733 => true, 129734 => true, 129735 => true, 129736 => true, 129737 => true, 129738 => true, 129739 => true, 129740 => true, 129741 => true, 129742 => true, 129743 => true, 129939 => true, 131070 => true, 131071 => true, 177973 => true, 177974 => true, 177975 => true, 177976 => true, 177977 => true, 177978 => true, 177979 => true, 177980 => true, 177981 => true, 177982 => true, 177983 => true, 178206 => true, 178207 => true, 183970 => true, 183971 => true, 183972 => true, 183973 => true, 183974 => true, 183975 => true, 183976 => true, 183977 => true, 183978 => true, 183979 => true, 183980 => true, 183981 => true, 183982 => true, 183983 => true, 194664 => true, 194676 => true, 194847 => true, 194911 => true, 195007 => true, 196606 => true, 196607 => true, 262142 => true, 262143 => true, 327678 => true, 327679 => true, 393214 => true, 393215 => true, 458750 => true, 458751 => true, 524286 => true, 524287 => true, 589822 => true, 589823 => true, 655358 => true, 655359 => true, 720894 => true, 720895 => true, 786430 => true, 786431 => true, 851966 => true, 851967 => true, 917502 => true, 917503 => true, 917504 => true, 917505 => true, 917506 => true, 917507 => true, 917508 => true, 917509 => true, 917510 => true, 917511 => true, 917512 => true, 917513 => true, 917514 => true, 917515 => true, 917516 => true, 917517 => true, 917518 => true, 917519 => true, 917520 => true, 917521 => true, 917522 => true, 917523 => true, 917524 => true, 917525 => true, 917526 => true, 917527 => true, 917528 => true, 917529 => true, 917530 => true, 917531 => true, 917532 => true, 917533 => true, 917534 => true, 917535 => true, 983038 => true, 983039 => true, 1048574 => true, 1048575 => true, 1114110 => true, 1114111 => true, ); ' ', 168 => ' ̈', 175 => ' ̄', 180 => ' ́', 184 => ' ̧', 728 => ' ̆', 729 => ' ̇', 730 => ' ̊', 731 => ' ̨', 732 => ' ̃', 733 => ' ̋', 890 => ' ι', 894 => ';', 900 => ' ́', 901 => ' ̈́', 8125 => ' ̓', 8127 => ' ̓', 8128 => ' ͂', 8129 => ' ̈͂', 8141 => ' ̓̀', 8142 => ' ̓́', 8143 => ' ̓͂', 8157 => ' ̔̀', 8158 => ' ̔́', 8159 => ' ̔͂', 8173 => ' ̈̀', 8174 => ' ̈́', 8175 => '`', 8189 => ' ́', 8190 => ' ̔', 8192 => ' ', 8193 => ' ', 8194 => ' ', 8195 => ' ', 8196 => ' ', 8197 => ' ', 8198 => ' ', 8199 => ' ', 8200 => ' ', 8201 => ' ', 8202 => ' ', 8215 => ' ̳', 8239 => ' ', 8252 => '!!', 8254 => ' ̅', 8263 => '??', 8264 => '?!', 8265 => '!?', 8287 => ' ', 8314 => '+', 8316 => '=', 8317 => '(', 8318 => ')', 8330 => '+', 8332 => '=', 8333 => '(', 8334 => ')', 8448 => 'a/c', 8449 => 'a/s', 8453 => 'c/o', 8454 => 'c/u', 9332 => '(1)', 9333 => '(2)', 9334 => '(3)', 9335 => '(4)', 9336 => '(5)', 9337 => '(6)', 9338 => '(7)', 9339 => '(8)', 9340 => '(9)', 9341 => '(10)', 9342 => '(11)', 9343 => '(12)', 9344 => '(13)', 9345 => '(14)', 9346 => '(15)', 9347 => '(16)', 9348 => '(17)', 9349 => '(18)', 9350 => '(19)', 9351 => '(20)', 9372 => '(a)', 9373 => '(b)', 9374 => '(c)', 9375 => '(d)', 9376 => '(e)', 9377 => '(f)', 9378 => '(g)', 9379 => '(h)', 9380 => '(i)', 9381 => '(j)', 9382 => '(k)', 9383 => '(l)', 9384 => '(m)', 9385 => '(n)', 9386 => '(o)', 9387 => '(p)', 9388 => '(q)', 9389 => '(r)', 9390 => '(s)', 9391 => '(t)', 9392 => '(u)', 9393 => '(v)', 9394 => '(w)', 9395 => '(x)', 9396 => '(y)', 9397 => '(z)', 10868 => '::=', 10869 => '==', 10870 => '===', 12288 => ' ', 12443 => ' ゙', 12444 => ' ゚', 12800 => '(ᄀ)', 12801 => '(ᄂ)', 12802 => '(ᄃ)', 12803 => '(ᄅ)', 12804 => '(ᄆ)', 12805 => '(ᄇ)', 12806 => '(ᄉ)', 12807 => '(ᄋ)', 12808 => '(ᄌ)', 12809 => '(ᄎ)', 12810 => '(ᄏ)', 12811 => '(ᄐ)', 12812 => '(ᄑ)', 12813 => '(ᄒ)', 12814 => '(가)', 12815 => '(나)', 12816 => '(다)', 12817 => '(라)', 12818 => '(마)', 12819 => '(바)', 12820 => '(사)', 12821 => '(아)', 12822 => '(자)', 12823 => '(차)', 12824 => '(카)', 12825 => '(타)', 12826 => '(파)', 12827 => '(하)', 12828 => '(주)', 12829 => '(오전)', 12830 => '(오후)', 12832 => '(一)', 12833 => '(二)', 12834 => '(三)', 12835 => '(四)', 12836 => '(五)', 12837 => '(六)', 12838 => '(七)', 12839 => '(八)', 12840 => '(九)', 12841 => '(十)', 12842 => '(月)', 12843 => '(火)', 12844 => '(水)', 12845 => '(木)', 12846 => '(金)', 12847 => '(土)', 12848 => '(日)', 12849 => '(株)', 12850 => '(有)', 12851 => '(社)', 12852 => '(名)', 12853 => '(特)', 12854 => '(財)', 12855 => '(祝)', 12856 => '(労)', 12857 => '(代)', 12858 => '(呼)', 12859 => '(学)', 12860 => '(監)', 12861 => '(企)', 12862 => '(資)', 12863 => '(協)', 12864 => '(祭)', 12865 => '(休)', 12866 => '(自)', 12867 => '(至)', 64297 => '+', 64606 => ' ٌّ', 64607 => ' ٍّ', 64608 => ' َّ', 64609 => ' ُّ', 64610 => ' ِّ', 64611 => ' ّٰ', 65018 => 'صلى الله عليه وسلم', 65019 => 'جل جلاله', 65040 => ',', 65043 => ':', 65044 => ';', 65045 => '!', 65046 => '?', 65075 => '_', 65076 => '_', 65077 => '(', 65078 => ')', 65079 => '{', 65080 => '}', 65095 => '[', 65096 => ']', 65097 => ' ̅', 65098 => ' ̅', 65099 => ' ̅', 65100 => ' ̅', 65101 => '_', 65102 => '_', 65103 => '_', 65104 => ',', 65108 => ';', 65109 => ':', 65110 => '?', 65111 => '!', 65113 => '(', 65114 => ')', 65115 => '{', 65116 => '}', 65119 => '#', 65120 => '&', 65121 => '*', 65122 => '+', 65124 => '<', 65125 => '>', 65126 => '=', 65128 => '\\', 65129 => '$', 65130 => '%', 65131 => '@', 65136 => ' ً', 65138 => ' ٌ', 65140 => ' ٍ', 65142 => ' َ', 65144 => ' ُ', 65146 => ' ِ', 65148 => ' ّ', 65150 => ' ْ', 65281 => '!', 65282 => '"', 65283 => '#', 65284 => '$', 65285 => '%', 65286 => '&', 65287 => '\'', 65288 => '(', 65289 => ')', 65290 => '*', 65291 => '+', 65292 => ',', 65295 => '/', 65306 => ':', 65307 => ';', 65308 => '<', 65309 => '=', 65310 => '>', 65311 => '?', 65312 => '@', 65339 => '[', 65340 => '\\', 65341 => ']', 65342 => '^', 65343 => '_', 65344 => '`', 65371 => '{', 65372 => '|', 65373 => '}', 65374 => '~', 65507 => ' ̄', 127233 => '0,', 127234 => '1,', 127235 => '2,', 127236 => '3,', 127237 => '4,', 127238 => '5,', 127239 => '6,', 127240 => '7,', 127241 => '8,', 127242 => '9,', 127248 => '(a)', 127249 => '(b)', 127250 => '(c)', 127251 => '(d)', 127252 => '(e)', 127253 => '(f)', 127254 => '(g)', 127255 => '(h)', 127256 => '(i)', 127257 => '(j)', 127258 => '(k)', 127259 => '(l)', 127260 => '(m)', 127261 => '(n)', 127262 => '(o)', 127263 => '(p)', 127264 => '(q)', 127265 => '(r)', 127266 => '(s)', 127267 => '(t)', 127268 => '(u)', 127269 => '(v)', 127270 => '(w)', 127271 => '(x)', 127272 => '(y)', 127273 => '(z)', ); 9, 2509 => 9, 2637 => 9, 2765 => 9, 2893 => 9, 3021 => 9, 3149 => 9, 3277 => 9, 3387 => 9, 3388 => 9, 3405 => 9, 3530 => 9, 3642 => 9, 3770 => 9, 3972 => 9, 4153 => 9, 4154 => 9, 5908 => 9, 5940 => 9, 6098 => 9, 6752 => 9, 6980 => 9, 7082 => 9, 7083 => 9, 7154 => 9, 7155 => 9, 11647 => 9, 43014 => 9, 43052 => 9, 43204 => 9, 43347 => 9, 43456 => 9, 43766 => 9, 44013 => 9, 68159 => 9, 69702 => 9, 69759 => 9, 69817 => 9, 69939 => 9, 69940 => 9, 70080 => 9, 70197 => 9, 70378 => 9, 70477 => 9, 70722 => 9, 70850 => 9, 71103 => 9, 71231 => 9, 71350 => 9, 71467 => 9, 71737 => 9, 71997 => 9, 71998 => 9, 72160 => 9, 72244 => 9, 72263 => 9, 72345 => 9, 72767 => 9, 73028 => 9, 73029 => 9, 73111 => 9, ); 'a', 66 => 'b', 67 => 'c', 68 => 'd', 69 => 'e', 70 => 'f', 71 => 'g', 72 => 'h', 73 => 'i', 74 => 'j', 75 => 'k', 76 => 'l', 77 => 'm', 78 => 'n', 79 => 'o', 80 => 'p', 81 => 'q', 82 => 'r', 83 => 's', 84 => 't', 85 => 'u', 86 => 'v', 87 => 'w', 88 => 'x', 89 => 'y', 90 => 'z', 170 => 'a', 178 => '2', 179 => '3', 181 => 'μ', 185 => '1', 186 => 'o', 188 => '1⁄4', 189 => '1⁄2', 190 => '3⁄4', 192 => 'à', 193 => 'á', 194 => 'â', 195 => 'ã', 196 => 'ä', 197 => 'å', 198 => 'æ', 199 => 'ç', 200 => 'è', 201 => 'é', 202 => 'ê', 203 => 'ë', 204 => 'ì', 205 => 'í', 206 => 'î', 207 => 'ï', 208 => 'ð', 209 => 'ñ', 210 => 'ò', 211 => 'ó', 212 => 'ô', 213 => 'õ', 214 => 'ö', 216 => 'ø', 217 => 'ù', 218 => 'ú', 219 => 'û', 220 => 'ü', 221 => 'ý', 222 => 'þ', 256 => 'ā', 258 => 'ă', 260 => 'ą', 262 => 'ć', 264 => 'ĉ', 266 => 'ċ', 268 => 'č', 270 => 'ď', 272 => 'đ', 274 => 'ē', 276 => 'ĕ', 278 => 'ė', 280 => 'ę', 282 => 'ě', 284 => 'ĝ', 286 => 'ğ', 288 => 'ġ', 290 => 'ģ', 292 => 'ĥ', 294 => 'ħ', 296 => 'ĩ', 298 => 'ī', 300 => 'ĭ', 302 => 'į', 304 => 'i̇', 306 => 'ij', 307 => 'ij', 308 => 'ĵ', 310 => 'ķ', 313 => 'ĺ', 315 => 'ļ', 317 => 'ľ', 319 => 'l·', 320 => 'l·', 321 => 'ł', 323 => 'ń', 325 => 'ņ', 327 => 'ň', 329 => 'ʼn', 330 => 'ŋ', 332 => 'ō', 334 => 'ŏ', 336 => 'ő', 338 => 'œ', 340 => 'ŕ', 342 => 'ŗ', 344 => 'ř', 346 => 'ś', 348 => 'ŝ', 350 => 'ş', 352 => 'š', 354 => 'ţ', 356 => 'ť', 358 => 'ŧ', 360 => 'ũ', 362 => 'ū', 364 => 'ŭ', 366 => 'ů', 368 => 'ű', 370 => 'ų', 372 => 'ŵ', 374 => 'ŷ', 376 => 'ÿ', 377 => 'ź', 379 => 'ż', 381 => 'ž', 383 => 's', 385 => 'ɓ', 386 => 'ƃ', 388 => 'ƅ', 390 => 'ɔ', 391 => 'ƈ', 393 => 'ɖ', 394 => 'ɗ', 395 => 'ƌ', 398 => 'ǝ', 399 => 'ə', 400 => 'ɛ', 401 => 'ƒ', 403 => 'ɠ', 404 => 'ɣ', 406 => 'ɩ', 407 => 'ɨ', 408 => 'ƙ', 412 => 'ɯ', 413 => 'ɲ', 415 => 'ɵ', 416 => 'ơ', 418 => 'ƣ', 420 => 'ƥ', 422 => 'ʀ', 423 => 'ƨ', 425 => 'ʃ', 428 => 'ƭ', 430 => 'ʈ', 431 => 'ư', 433 => 'ʊ', 434 => 'ʋ', 435 => 'ƴ', 437 => 'ƶ', 439 => 'ʒ', 440 => 'ƹ', 444 => 'ƽ', 452 => 'dž', 453 => 'dž', 454 => 'dž', 455 => 'lj', 456 => 'lj', 457 => 'lj', 458 => 'nj', 459 => 'nj', 460 => 'nj', 461 => 'ǎ', 463 => 'ǐ', 465 => 'ǒ', 467 => 'ǔ', 469 => 'ǖ', 471 => 'ǘ', 473 => 'ǚ', 475 => 'ǜ', 478 => 'ǟ', 480 => 'ǡ', 482 => 'ǣ', 484 => 'ǥ', 486 => 'ǧ', 488 => 'ǩ', 490 => 'ǫ', 492 => 'ǭ', 494 => 'ǯ', 497 => 'dz', 498 => 'dz', 499 => 'dz', 500 => 'ǵ', 502 => 'ƕ', 503 => 'ƿ', 504 => 'ǹ', 506 => 'ǻ', 508 => 'ǽ', 510 => 'ǿ', 512 => 'ȁ', 514 => 'ȃ', 516 => 'ȅ', 518 => 'ȇ', 520 => 'ȉ', 522 => 'ȋ', 524 => 'ȍ', 526 => 'ȏ', 528 => 'ȑ', 530 => 'ȓ', 532 => 'ȕ', 534 => 'ȗ', 536 => 'ș', 538 => 'ț', 540 => 'ȝ', 542 => 'ȟ', 544 => 'ƞ', 546 => 'ȣ', 548 => 'ȥ', 550 => 'ȧ', 552 => 'ȩ', 554 => 'ȫ', 556 => 'ȭ', 558 => 'ȯ', 560 => 'ȱ', 562 => 'ȳ', 570 => 'ⱥ', 571 => 'ȼ', 573 => 'ƚ', 574 => 'ⱦ', 577 => 'ɂ', 579 => 'ƀ', 580 => 'ʉ', 581 => 'ʌ', 582 => 'ɇ', 584 => 'ɉ', 586 => 'ɋ', 588 => 'ɍ', 590 => 'ɏ', 688 => 'h', 689 => 'ɦ', 690 => 'j', 691 => 'r', 692 => 'ɹ', 693 => 'ɻ', 694 => 'ʁ', 695 => 'w', 696 => 'y', 736 => 'ɣ', 737 => 'l', 738 => 's', 739 => 'x', 740 => 'ʕ', 832 => '̀', 833 => '́', 835 => '̓', 836 => '̈́', 837 => 'ι', 880 => 'ͱ', 882 => 'ͳ', 884 => 'ʹ', 886 => 'ͷ', 895 => 'ϳ', 902 => 'ά', 903 => '·', 904 => 'έ', 905 => 'ή', 906 => 'ί', 908 => 'ό', 910 => 'ύ', 911 => 'ώ', 913 => 'α', 914 => 'β', 915 => 'γ', 916 => 'δ', 917 => 'ε', 918 => 'ζ', 919 => 'η', 920 => 'θ', 921 => 'ι', 922 => 'κ', 923 => 'λ', 924 => 'μ', 925 => 'ν', 926 => 'ξ', 927 => 'ο', 928 => 'π', 929 => 'ρ', 931 => 'σ', 932 => 'τ', 933 => 'υ', 934 => 'φ', 935 => 'χ', 936 => 'ψ', 937 => 'ω', 938 => 'ϊ', 939 => 'ϋ', 975 => 'ϗ', 976 => 'β', 977 => 'θ', 978 => 'υ', 979 => 'ύ', 980 => 'ϋ', 981 => 'φ', 982 => 'π', 984 => 'ϙ', 986 => 'ϛ', 988 => 'ϝ', 990 => 'ϟ', 992 => 'ϡ', 994 => 'ϣ', 996 => 'ϥ', 998 => 'ϧ', 1000 => 'ϩ', 1002 => 'ϫ', 1004 => 'ϭ', 1006 => 'ϯ', 1008 => 'κ', 1009 => 'ρ', 1010 => 'σ', 1012 => 'θ', 1013 => 'ε', 1015 => 'ϸ', 1017 => 'σ', 1018 => 'ϻ', 1021 => 'ͻ', 1022 => 'ͼ', 1023 => 'ͽ', 1024 => 'ѐ', 1025 => 'ё', 1026 => 'ђ', 1027 => 'ѓ', 1028 => 'є', 1029 => 'ѕ', 1030 => 'і', 1031 => 'ї', 1032 => 'ј', 1033 => 'љ', 1034 => 'њ', 1035 => 'ћ', 1036 => 'ќ', 1037 => 'ѝ', 1038 => 'ў', 1039 => 'џ', 1040 => 'а', 1041 => 'б', 1042 => 'в', 1043 => 'г', 1044 => 'д', 1045 => 'е', 1046 => 'ж', 1047 => 'з', 1048 => 'и', 1049 => 'й', 1050 => 'к', 1051 => 'л', 1052 => 'м', 1053 => 'н', 1054 => 'о', 1055 => 'п', 1056 => 'р', 1057 => 'с', 1058 => 'т', 1059 => 'у', 1060 => 'ф', 1061 => 'х', 1062 => 'ц', 1063 => 'ч', 1064 => 'ш', 1065 => 'щ', 1066 => 'ъ', 1067 => 'ы', 1068 => 'ь', 1069 => 'э', 1070 => 'ю', 1071 => 'я', 1120 => 'ѡ', 1122 => 'ѣ', 1124 => 'ѥ', 1126 => 'ѧ', 1128 => 'ѩ', 1130 => 'ѫ', 1132 => 'ѭ', 1134 => 'ѯ', 1136 => 'ѱ', 1138 => 'ѳ', 1140 => 'ѵ', 1142 => 'ѷ', 1144 => 'ѹ', 1146 => 'ѻ', 1148 => 'ѽ', 1150 => 'ѿ', 1152 => 'ҁ', 1162 => 'ҋ', 1164 => 'ҍ', 1166 => 'ҏ', 1168 => 'ґ', 1170 => 'ғ', 1172 => 'ҕ', 1174 => 'җ', 1176 => 'ҙ', 1178 => 'қ', 1180 => 'ҝ', 1182 => 'ҟ', 1184 => 'ҡ', 1186 => 'ң', 1188 => 'ҥ', 1190 => 'ҧ', 1192 => 'ҩ', 1194 => 'ҫ', 1196 => 'ҭ', 1198 => 'ү', 1200 => 'ұ', 1202 => 'ҳ', 1204 => 'ҵ', 1206 => 'ҷ', 1208 => 'ҹ', 1210 => 'һ', 1212 => 'ҽ', 1214 => 'ҿ', 1217 => 'ӂ', 1219 => 'ӄ', 1221 => 'ӆ', 1223 => 'ӈ', 1225 => 'ӊ', 1227 => 'ӌ', 1229 => 'ӎ', 1232 => 'ӑ', 1234 => 'ӓ', 1236 => 'ӕ', 1238 => 'ӗ', 1240 => 'ә', 1242 => 'ӛ', 1244 => 'ӝ', 1246 => 'ӟ', 1248 => 'ӡ', 1250 => 'ӣ', 1252 => 'ӥ', 1254 => 'ӧ', 1256 => 'ө', 1258 => 'ӫ', 1260 => 'ӭ', 1262 => 'ӯ', 1264 => 'ӱ', 1266 => 'ӳ', 1268 => 'ӵ', 1270 => 'ӷ', 1272 => 'ӹ', 1274 => 'ӻ', 1276 => 'ӽ', 1278 => 'ӿ', 1280 => 'ԁ', 1282 => 'ԃ', 1284 => 'ԅ', 1286 => 'ԇ', 1288 => 'ԉ', 1290 => 'ԋ', 1292 => 'ԍ', 1294 => 'ԏ', 1296 => 'ԑ', 1298 => 'ԓ', 1300 => 'ԕ', 1302 => 'ԗ', 1304 => 'ԙ', 1306 => 'ԛ', 1308 => 'ԝ', 1310 => 'ԟ', 1312 => 'ԡ', 1314 => 'ԣ', 1316 => 'ԥ', 1318 => 'ԧ', 1320 => 'ԩ', 1322 => 'ԫ', 1324 => 'ԭ', 1326 => 'ԯ', 1329 => 'ա', 1330 => 'բ', 1331 => 'գ', 1332 => 'դ', 1333 => 'ե', 1334 => 'զ', 1335 => 'է', 1336 => 'ը', 1337 => 'թ', 1338 => 'ժ', 1339 => 'ի', 1340 => 'լ', 1341 => 'խ', 1342 => 'ծ', 1343 => 'կ', 1344 => 'հ', 1345 => 'ձ', 1346 => 'ղ', 1347 => 'ճ', 1348 => 'մ', 1349 => 'յ', 1350 => 'ն', 1351 => 'շ', 1352 => 'ո', 1353 => 'չ', 1354 => 'պ', 1355 => 'ջ', 1356 => 'ռ', 1357 => 'ս', 1358 => 'վ', 1359 => 'տ', 1360 => 'ր', 1361 => 'ց', 1362 => 'ւ', 1363 => 'փ', 1364 => 'ք', 1365 => 'օ', 1366 => 'ֆ', 1415 => 'եւ', 1653 => 'اٴ', 1654 => 'وٴ', 1655 => 'ۇٴ', 1656 => 'يٴ', 2392 => 'क़', 2393 => 'ख़', 2394 => 'ग़', 2395 => 'ज़', 2396 => 'ड़', 2397 => 'ढ़', 2398 => 'फ़', 2399 => 'य़', 2524 => 'ড়', 2525 => 'ঢ়', 2527 => 'য়', 2611 => 'ਲ਼', 2614 => 'ਸ਼', 2649 => 'ਖ਼', 2650 => 'ਗ਼', 2651 => 'ਜ਼', 2654 => 'ਫ਼', 2908 => 'ଡ଼', 2909 => 'ଢ଼', 3635 => 'ํา', 3763 => 'ໍາ', 3804 => 'ຫນ', 3805 => 'ຫມ', 3852 => '་', 3907 => 'གྷ', 3917 => 'ཌྷ', 3922 => 'དྷ', 3927 => 'བྷ', 3932 => 'ཛྷ', 3945 => 'ཀྵ', 3955 => 'ཱི', 3957 => 'ཱུ', 3958 => 'ྲྀ', 3959 => 'ྲཱྀ', 3960 => 'ླྀ', 3961 => 'ླཱྀ', 3969 => 'ཱྀ', 3987 => 'ྒྷ', 3997 => 'ྜྷ', 4002 => 'ྡྷ', 4007 => 'ྦྷ', 4012 => 'ྫྷ', 4025 => 'ྐྵ', 4295 => 'ⴧ', 4301 => 'ⴭ', 4348 => 'ნ', 5112 => 'Ᏸ', 5113 => 'Ᏹ', 5114 => 'Ᏺ', 5115 => 'Ᏻ', 5116 => 'Ᏼ', 5117 => 'Ᏽ', 7296 => 'в', 7297 => 'д', 7298 => 'о', 7299 => 'с', 7300 => 'т', 7301 => 'т', 7302 => 'ъ', 7303 => 'ѣ', 7304 => 'ꙋ', 7312 => 'ა', 7313 => 'ბ', 7314 => 'გ', 7315 => 'დ', 7316 => 'ე', 7317 => 'ვ', 7318 => 'ზ', 7319 => 'თ', 7320 => 'ი', 7321 => 'კ', 7322 => 'ლ', 7323 => 'მ', 7324 => 'ნ', 7325 => 'ო', 7326 => 'პ', 7327 => 'ჟ', 7328 => 'რ', 7329 => 'ს', 7330 => 'ტ', 7331 => 'უ', 7332 => 'ფ', 7333 => 'ქ', 7334 => 'ღ', 7335 => 'ყ', 7336 => 'შ', 7337 => 'ჩ', 7338 => 'ც', 7339 => 'ძ', 7340 => 'წ', 7341 => 'ჭ', 7342 => 'ხ', 7343 => 'ჯ', 7344 => 'ჰ', 7345 => 'ჱ', 7346 => 'ჲ', 7347 => 'ჳ', 7348 => 'ჴ', 7349 => 'ჵ', 7350 => 'ჶ', 7351 => 'ჷ', 7352 => 'ჸ', 7353 => 'ჹ', 7354 => 'ჺ', 7357 => 'ჽ', 7358 => 'ჾ', 7359 => 'ჿ', 7468 => 'a', 7469 => 'æ', 7470 => 'b', 7472 => 'd', 7473 => 'e', 7474 => 'ǝ', 7475 => 'g', 7476 => 'h', 7477 => 'i', 7478 => 'j', 7479 => 'k', 7480 => 'l', 7481 => 'm', 7482 => 'n', 7484 => 'o', 7485 => 'ȣ', 7486 => 'p', 7487 => 'r', 7488 => 't', 7489 => 'u', 7490 => 'w', 7491 => 'a', 7492 => 'ɐ', 7493 => 'ɑ', 7494 => 'ᴂ', 7495 => 'b', 7496 => 'd', 7497 => 'e', 7498 => 'ə', 7499 => 'ɛ', 7500 => 'ɜ', 7501 => 'g', 7503 => 'k', 7504 => 'm', 7505 => 'ŋ', 7506 => 'o', 7507 => 'ɔ', 7508 => 'ᴖ', 7509 => 'ᴗ', 7510 => 'p', 7511 => 't', 7512 => 'u', 7513 => 'ᴝ', 7514 => 'ɯ', 7515 => 'v', 7516 => 'ᴥ', 7517 => 'β', 7518 => 'γ', 7519 => 'δ', 7520 => 'φ', 7521 => 'χ', 7522 => 'i', 7523 => 'r', 7524 => 'u', 7525 => 'v', 7526 => 'β', 7527 => 'γ', 7528 => 'ρ', 7529 => 'φ', 7530 => 'χ', 7544 => 'н', 7579 => 'ɒ', 7580 => 'c', 7581 => 'ɕ', 7582 => 'ð', 7583 => 'ɜ', 7584 => 'f', 7585 => 'ɟ', 7586 => 'ɡ', 7587 => 'ɥ', 7588 => 'ɨ', 7589 => 'ɩ', 7590 => 'ɪ', 7591 => 'ᵻ', 7592 => 'ʝ', 7593 => 'ɭ', 7594 => 'ᶅ', 7595 => 'ʟ', 7596 => 'ɱ', 7597 => 'ɰ', 7598 => 'ɲ', 7599 => 'ɳ', 7600 => 'ɴ', 7601 => 'ɵ', 7602 => 'ɸ', 7603 => 'ʂ', 7604 => 'ʃ', 7605 => 'ƫ', 7606 => 'ʉ', 7607 => 'ʊ', 7608 => 'ᴜ', 7609 => 'ʋ', 7610 => 'ʌ', 7611 => 'z', 7612 => 'ʐ', 7613 => 'ʑ', 7614 => 'ʒ', 7615 => 'θ', 7680 => 'ḁ', 7682 => 'ḃ', 7684 => 'ḅ', 7686 => 'ḇ', 7688 => 'ḉ', 7690 => 'ḋ', 7692 => 'ḍ', 7694 => 'ḏ', 7696 => 'ḑ', 7698 => 'ḓ', 7700 => 'ḕ', 7702 => 'ḗ', 7704 => 'ḙ', 7706 => 'ḛ', 7708 => 'ḝ', 7710 => 'ḟ', 7712 => 'ḡ', 7714 => 'ḣ', 7716 => 'ḥ', 7718 => 'ḧ', 7720 => 'ḩ', 7722 => 'ḫ', 7724 => 'ḭ', 7726 => 'ḯ', 7728 => 'ḱ', 7730 => 'ḳ', 7732 => 'ḵ', 7734 => 'ḷ', 7736 => 'ḹ', 7738 => 'ḻ', 7740 => 'ḽ', 7742 => 'ḿ', 7744 => 'ṁ', 7746 => 'ṃ', 7748 => 'ṅ', 7750 => 'ṇ', 7752 => 'ṉ', 7754 => 'ṋ', 7756 => 'ṍ', 7758 => 'ṏ', 7760 => 'ṑ', 7762 => 'ṓ', 7764 => 'ṕ', 7766 => 'ṗ', 7768 => 'ṙ', 7770 => 'ṛ', 7772 => 'ṝ', 7774 => 'ṟ', 7776 => 'ṡ', 7778 => 'ṣ', 7780 => 'ṥ', 7782 => 'ṧ', 7784 => 'ṩ', 7786 => 'ṫ', 7788 => 'ṭ', 7790 => 'ṯ', 7792 => 'ṱ', 7794 => 'ṳ', 7796 => 'ṵ', 7798 => 'ṷ', 7800 => 'ṹ', 7802 => 'ṻ', 7804 => 'ṽ', 7806 => 'ṿ', 7808 => 'ẁ', 7810 => 'ẃ', 7812 => 'ẅ', 7814 => 'ẇ', 7816 => 'ẉ', 7818 => 'ẋ', 7820 => 'ẍ', 7822 => 'ẏ', 7824 => 'ẑ', 7826 => 'ẓ', 7828 => 'ẕ', 7834 => 'aʾ', 7835 => 'ṡ', 7838 => 'ss', 7840 => 'ạ', 7842 => 'ả', 7844 => 'ấ', 7846 => 'ầ', 7848 => 'ẩ', 7850 => 'ẫ', 7852 => 'ậ', 7854 => 'ắ', 7856 => 'ằ', 7858 => 'ẳ', 7860 => 'ẵ', 7862 => 'ặ', 7864 => 'ẹ', 7866 => 'ẻ', 7868 => 'ẽ', 7870 => 'ế', 7872 => 'ề', 7874 => 'ể', 7876 => 'ễ', 7878 => 'ệ', 7880 => 'ỉ', 7882 => 'ị', 7884 => 'ọ', 7886 => 'ỏ', 7888 => 'ố', 7890 => 'ồ', 7892 => 'ổ', 7894 => 'ỗ', 7896 => 'ộ', 7898 => 'ớ', 7900 => 'ờ', 7902 => 'ở', 7904 => 'ỡ', 7906 => 'ợ', 7908 => 'ụ', 7910 => 'ủ', 7912 => 'ứ', 7914 => 'ừ', 7916 => 'ử', 7918 => 'ữ', 7920 => 'ự', 7922 => 'ỳ', 7924 => 'ỵ', 7926 => 'ỷ', 7928 => 'ỹ', 7930 => 'ỻ', 7932 => 'ỽ', 7934 => 'ỿ', 7944 => 'ἀ', 7945 => 'ἁ', 7946 => 'ἂ', 7947 => 'ἃ', 7948 => 'ἄ', 7949 => 'ἅ', 7950 => 'ἆ', 7951 => 'ἇ', 7960 => 'ἐ', 7961 => 'ἑ', 7962 => 'ἒ', 7963 => 'ἓ', 7964 => 'ἔ', 7965 => 'ἕ', 7976 => 'ἠ', 7977 => 'ἡ', 7978 => 'ἢ', 7979 => 'ἣ', 7980 => 'ἤ', 7981 => 'ἥ', 7982 => 'ἦ', 7983 => 'ἧ', 7992 => 'ἰ', 7993 => 'ἱ', 7994 => 'ἲ', 7995 => 'ἳ', 7996 => 'ἴ', 7997 => 'ἵ', 7998 => 'ἶ', 7999 => 'ἷ', 8008 => 'ὀ', 8009 => 'ὁ', 8010 => 'ὂ', 8011 => 'ὃ', 8012 => 'ὄ', 8013 => 'ὅ', 8025 => 'ὑ', 8027 => 'ὓ', 8029 => 'ὕ', 8031 => 'ὗ', 8040 => 'ὠ', 8041 => 'ὡ', 8042 => 'ὢ', 8043 => 'ὣ', 8044 => 'ὤ', 8045 => 'ὥ', 8046 => 'ὦ', 8047 => 'ὧ', 8049 => 'ά', 8051 => 'έ', 8053 => 'ή', 8055 => 'ί', 8057 => 'ό', 8059 => 'ύ', 8061 => 'ώ', 8064 => 'ἀι', 8065 => 'ἁι', 8066 => 'ἂι', 8067 => 'ἃι', 8068 => 'ἄι', 8069 => 'ἅι', 8070 => 'ἆι', 8071 => 'ἇι', 8072 => 'ἀι', 8073 => 'ἁι', 8074 => 'ἂι', 8075 => 'ἃι', 8076 => 'ἄι', 8077 => 'ἅι', 8078 => 'ἆι', 8079 => 'ἇι', 8080 => 'ἠι', 8081 => 'ἡι', 8082 => 'ἢι', 8083 => 'ἣι', 8084 => 'ἤι', 8085 => 'ἥι', 8086 => 'ἦι', 8087 => 'ἧι', 8088 => 'ἠι', 8089 => 'ἡι', 8090 => 'ἢι', 8091 => 'ἣι', 8092 => 'ἤι', 8093 => 'ἥι', 8094 => 'ἦι', 8095 => 'ἧι', 8096 => 'ὠι', 8097 => 'ὡι', 8098 => 'ὢι', 8099 => 'ὣι', 8100 => 'ὤι', 8101 => 'ὥι', 8102 => 'ὦι', 8103 => 'ὧι', 8104 => 'ὠι', 8105 => 'ὡι', 8106 => 'ὢι', 8107 => 'ὣι', 8108 => 'ὤι', 8109 => 'ὥι', 8110 => 'ὦι', 8111 => 'ὧι', 8114 => 'ὰι', 8115 => 'αι', 8116 => 'άι', 8119 => 'ᾶι', 8120 => 'ᾰ', 8121 => 'ᾱ', 8122 => 'ὰ', 8123 => 'ά', 8124 => 'αι', 8126 => 'ι', 8130 => 'ὴι', 8131 => 'ηι', 8132 => 'ήι', 8135 => 'ῆι', 8136 => 'ὲ', 8137 => 'έ', 8138 => 'ὴ', 8139 => 'ή', 8140 => 'ηι', 8147 => 'ΐ', 8152 => 'ῐ', 8153 => 'ῑ', 8154 => 'ὶ', 8155 => 'ί', 8163 => 'ΰ', 8168 => 'ῠ', 8169 => 'ῡ', 8170 => 'ὺ', 8171 => 'ύ', 8172 => 'ῥ', 8178 => 'ὼι', 8179 => 'ωι', 8180 => 'ώι', 8183 => 'ῶι', 8184 => 'ὸ', 8185 => 'ό', 8186 => 'ὼ', 8187 => 'ώ', 8188 => 'ωι', 8209 => '‐', 8243 => '′′', 8244 => '′′′', 8246 => '‵‵', 8247 => '‵‵‵', 8279 => '′′′′', 8304 => '0', 8305 => 'i', 8308 => '4', 8309 => '5', 8310 => '6', 8311 => '7', 8312 => '8', 8313 => '9', 8315 => '−', 8319 => 'n', 8320 => '0', 8321 => '1', 8322 => '2', 8323 => '3', 8324 => '4', 8325 => '5', 8326 => '6', 8327 => '7', 8328 => '8', 8329 => '9', 8331 => '−', 8336 => 'a', 8337 => 'e', 8338 => 'o', 8339 => 'x', 8340 => 'ə', 8341 => 'h', 8342 => 'k', 8343 => 'l', 8344 => 'm', 8345 => 'n', 8346 => 'p', 8347 => 's', 8348 => 't', 8360 => 'rs', 8450 => 'c', 8451 => '°c', 8455 => 'ɛ', 8457 => '°f', 8458 => 'g', 8459 => 'h', 8460 => 'h', 8461 => 'h', 8462 => 'h', 8463 => 'ħ', 8464 => 'i', 8465 => 'i', 8466 => 'l', 8467 => 'l', 8469 => 'n', 8470 => 'no', 8473 => 'p', 8474 => 'q', 8475 => 'r', 8476 => 'r', 8477 => 'r', 8480 => 'sm', 8481 => 'tel', 8482 => 'tm', 8484 => 'z', 8486 => 'ω', 8488 => 'z', 8490 => 'k', 8491 => 'å', 8492 => 'b', 8493 => 'c', 8495 => 'e', 8496 => 'e', 8497 => 'f', 8499 => 'm', 8500 => 'o', 8501 => 'א', 8502 => 'ב', 8503 => 'ג', 8504 => 'ד', 8505 => 'i', 8507 => 'fax', 8508 => 'π', 8509 => 'γ', 8510 => 'γ', 8511 => 'π', 8512 => '∑', 8517 => 'd', 8518 => 'd', 8519 => 'e', 8520 => 'i', 8521 => 'j', 8528 => '1⁄7', 8529 => '1⁄9', 8530 => '1⁄10', 8531 => '1⁄3', 8532 => '2⁄3', 8533 => '1⁄5', 8534 => '2⁄5', 8535 => '3⁄5', 8536 => '4⁄5', 8537 => '1⁄6', 8538 => '5⁄6', 8539 => '1⁄8', 8540 => '3⁄8', 8541 => '5⁄8', 8542 => '7⁄8', 8543 => '1⁄', 8544 => 'i', 8545 => 'ii', 8546 => 'iii', 8547 => 'iv', 8548 => 'v', 8549 => 'vi', 8550 => 'vii', 8551 => 'viii', 8552 => 'ix', 8553 => 'x', 8554 => 'xi', 8555 => 'xii', 8556 => 'l', 8557 => 'c', 8558 => 'd', 8559 => 'm', 8560 => 'i', 8561 => 'ii', 8562 => 'iii', 8563 => 'iv', 8564 => 'v', 8565 => 'vi', 8566 => 'vii', 8567 => 'viii', 8568 => 'ix', 8569 => 'x', 8570 => 'xi', 8571 => 'xii', 8572 => 'l', 8573 => 'c', 8574 => 'd', 8575 => 'm', 8585 => '0⁄3', 8748 => '∫∫', 8749 => '∫∫∫', 8751 => '∮∮', 8752 => '∮∮∮', 9001 => '〈', 9002 => '〉', 9312 => '1', 9313 => '2', 9314 => '3', 9315 => '4', 9316 => '5', 9317 => '6', 9318 => '7', 9319 => '8', 9320 => '9', 9321 => '10', 9322 => '11', 9323 => '12', 9324 => '13', 9325 => '14', 9326 => '15', 9327 => '16', 9328 => '17', 9329 => '18', 9330 => '19', 9331 => '20', 9398 => 'a', 9399 => 'b', 9400 => 'c', 9401 => 'd', 9402 => 'e', 9403 => 'f', 9404 => 'g', 9405 => 'h', 9406 => 'i', 9407 => 'j', 9408 => 'k', 9409 => 'l', 9410 => 'm', 9411 => 'n', 9412 => 'o', 9413 => 'p', 9414 => 'q', 9415 => 'r', 9416 => 's', 9417 => 't', 9418 => 'u', 9419 => 'v', 9420 => 'w', 9421 => 'x', 9422 => 'y', 9423 => 'z', 9424 => 'a', 9425 => 'b', 9426 => 'c', 9427 => 'd', 9428 => 'e', 9429 => 'f', 9430 => 'g', 9431 => 'h', 9432 => 'i', 9433 => 'j', 9434 => 'k', 9435 => 'l', 9436 => 'm', 9437 => 'n', 9438 => 'o', 9439 => 'p', 9440 => 'q', 9441 => 'r', 9442 => 's', 9443 => 't', 9444 => 'u', 9445 => 'v', 9446 => 'w', 9447 => 'x', 9448 => 'y', 9449 => 'z', 9450 => '0', 10764 => '∫∫∫∫', 10972 => '⫝̸', 11264 => 'ⰰ', 11265 => 'ⰱ', 11266 => 'ⰲ', 11267 => 'ⰳ', 11268 => 'ⰴ', 11269 => 'ⰵ', 11270 => 'ⰶ', 11271 => 'ⰷ', 11272 => 'ⰸ', 11273 => 'ⰹ', 11274 => 'ⰺ', 11275 => 'ⰻ', 11276 => 'ⰼ', 11277 => 'ⰽ', 11278 => 'ⰾ', 11279 => 'ⰿ', 11280 => 'ⱀ', 11281 => 'ⱁ', 11282 => 'ⱂ', 11283 => 'ⱃ', 11284 => 'ⱄ', 11285 => 'ⱅ', 11286 => 'ⱆ', 11287 => 'ⱇ', 11288 => 'ⱈ', 11289 => 'ⱉ', 11290 => 'ⱊ', 11291 => 'ⱋ', 11292 => 'ⱌ', 11293 => 'ⱍ', 11294 => 'ⱎ', 11295 => 'ⱏ', 11296 => 'ⱐ', 11297 => 'ⱑ', 11298 => 'ⱒ', 11299 => 'ⱓ', 11300 => 'ⱔ', 11301 => 'ⱕ', 11302 => 'ⱖ', 11303 => 'ⱗ', 11304 => 'ⱘ', 11305 => 'ⱙ', 11306 => 'ⱚ', 11307 => 'ⱛ', 11308 => 'ⱜ', 11309 => 'ⱝ', 11310 => 'ⱞ', 11360 => 'ⱡ', 11362 => 'ɫ', 11363 => 'ᵽ', 11364 => 'ɽ', 11367 => 'ⱨ', 11369 => 'ⱪ', 11371 => 'ⱬ', 11373 => 'ɑ', 11374 => 'ɱ', 11375 => 'ɐ', 11376 => 'ɒ', 11378 => 'ⱳ', 11381 => 'ⱶ', 11388 => 'j', 11389 => 'v', 11390 => 'ȿ', 11391 => 'ɀ', 11392 => 'ⲁ', 11394 => 'ⲃ', 11396 => 'ⲅ', 11398 => 'ⲇ', 11400 => 'ⲉ', 11402 => 'ⲋ', 11404 => 'ⲍ', 11406 => 'ⲏ', 11408 => 'ⲑ', 11410 => 'ⲓ', 11412 => 'ⲕ', 11414 => 'ⲗ', 11416 => 'ⲙ', 11418 => 'ⲛ', 11420 => 'ⲝ', 11422 => 'ⲟ', 11424 => 'ⲡ', 11426 => 'ⲣ', 11428 => 'ⲥ', 11430 => 'ⲧ', 11432 => 'ⲩ', 11434 => 'ⲫ', 11436 => 'ⲭ', 11438 => 'ⲯ', 11440 => 'ⲱ', 11442 => 'ⲳ', 11444 => 'ⲵ', 11446 => 'ⲷ', 11448 => 'ⲹ', 11450 => 'ⲻ', 11452 => 'ⲽ', 11454 => 'ⲿ', 11456 => 'ⳁ', 11458 => 'ⳃ', 11460 => 'ⳅ', 11462 => 'ⳇ', 11464 => 'ⳉ', 11466 => 'ⳋ', 11468 => 'ⳍ', 11470 => 'ⳏ', 11472 => 'ⳑ', 11474 => 'ⳓ', 11476 => 'ⳕ', 11478 => 'ⳗ', 11480 => 'ⳙ', 11482 => 'ⳛ', 11484 => 'ⳝ', 11486 => 'ⳟ', 11488 => 'ⳡ', 11490 => 'ⳣ', 11499 => 'ⳬ', 11501 => 'ⳮ', 11506 => 'ⳳ', 11631 => 'ⵡ', 11935 => '母', 12019 => '龟', 12032 => '一', 12033 => '丨', 12034 => '丶', 12035 => '丿', 12036 => '乙', 12037 => '亅', 12038 => '二', 12039 => '亠', 12040 => '人', 12041 => '儿', 12042 => '入', 12043 => '八', 12044 => '冂', 12045 => '冖', 12046 => '冫', 12047 => '几', 12048 => '凵', 12049 => '刀', 12050 => '力', 12051 => '勹', 12052 => '匕', 12053 => '匚', 12054 => '匸', 12055 => '十', 12056 => '卜', 12057 => '卩', 12058 => '厂', 12059 => '厶', 12060 => '又', 12061 => '口', 12062 => '囗', 12063 => '土', 12064 => '士', 12065 => '夂', 12066 => '夊', 12067 => '夕', 12068 => '大', 12069 => '女', 12070 => '子', 12071 => '宀', 12072 => '寸', 12073 => '小', 12074 => '尢', 12075 => '尸', 12076 => '屮', 12077 => '山', 12078 => '巛', 12079 => '工', 12080 => '己', 12081 => '巾', 12082 => '干', 12083 => '幺', 12084 => '广', 12085 => '廴', 12086 => '廾', 12087 => '弋', 12088 => '弓', 12089 => '彐', 12090 => '彡', 12091 => '彳', 12092 => '心', 12093 => '戈', 12094 => '戶', 12095 => '手', 12096 => '支', 12097 => '攴', 12098 => '文', 12099 => '斗', 12100 => '斤', 12101 => '方', 12102 => '无', 12103 => '日', 12104 => '曰', 12105 => '月', 12106 => '木', 12107 => '欠', 12108 => '止', 12109 => '歹', 12110 => '殳', 12111 => '毋', 12112 => '比', 12113 => '毛', 12114 => '氏', 12115 => '气', 12116 => '水', 12117 => '火', 12118 => '爪', 12119 => '父', 12120 => '爻', 12121 => '爿', 12122 => '片', 12123 => '牙', 12124 => '牛', 12125 => '犬', 12126 => '玄', 12127 => '玉', 12128 => '瓜', 12129 => '瓦', 12130 => '甘', 12131 => '生', 12132 => '用', 12133 => '田', 12134 => '疋', 12135 => '疒', 12136 => '癶', 12137 => '白', 12138 => '皮', 12139 => '皿', 12140 => '目', 12141 => '矛', 12142 => '矢', 12143 => '石', 12144 => '示', 12145 => '禸', 12146 => '禾', 12147 => '穴', 12148 => '立', 12149 => '竹', 12150 => '米', 12151 => '糸', 12152 => '缶', 12153 => '网', 12154 => '羊', 12155 => '羽', 12156 => '老', 12157 => '而', 12158 => '耒', 12159 => '耳', 12160 => '聿', 12161 => '肉', 12162 => '臣', 12163 => '自', 12164 => '至', 12165 => '臼', 12166 => '舌', 12167 => '舛', 12168 => '舟', 12169 => '艮', 12170 => '色', 12171 => '艸', 12172 => '虍', 12173 => '虫', 12174 => '血', 12175 => '行', 12176 => '衣', 12177 => '襾', 12178 => '見', 12179 => '角', 12180 => '言', 12181 => '谷', 12182 => '豆', 12183 => '豕', 12184 => '豸', 12185 => '貝', 12186 => '赤', 12187 => '走', 12188 => '足', 12189 => '身', 12190 => '車', 12191 => '辛', 12192 => '辰', 12193 => '辵', 12194 => '邑', 12195 => '酉', 12196 => '釆', 12197 => '里', 12198 => '金', 12199 => '長', 12200 => '門', 12201 => '阜', 12202 => '隶', 12203 => '隹', 12204 => '雨', 12205 => '靑', 12206 => '非', 12207 => '面', 12208 => '革', 12209 => '韋', 12210 => '韭', 12211 => '音', 12212 => '頁', 12213 => '風', 12214 => '飛', 12215 => '食', 12216 => '首', 12217 => '香', 12218 => '馬', 12219 => '骨', 12220 => '高', 12221 => '髟', 12222 => '鬥', 12223 => '鬯', 12224 => '鬲', 12225 => '鬼', 12226 => '魚', 12227 => '鳥', 12228 => '鹵', 12229 => '鹿', 12230 => '麥', 12231 => '麻', 12232 => '黃', 12233 => '黍', 12234 => '黑', 12235 => '黹', 12236 => '黽', 12237 => '鼎', 12238 => '鼓', 12239 => '鼠', 12240 => '鼻', 12241 => '齊', 12242 => '齒', 12243 => '龍', 12244 => '龜', 12245 => '龠', 12290 => '.', 12342 => '〒', 12344 => '十', 12345 => '卄', 12346 => '卅', 12447 => 'より', 12543 => 'コト', 12593 => 'ᄀ', 12594 => 'ᄁ', 12595 => 'ᆪ', 12596 => 'ᄂ', 12597 => 'ᆬ', 12598 => 'ᆭ', 12599 => 'ᄃ', 12600 => 'ᄄ', 12601 => 'ᄅ', 12602 => 'ᆰ', 12603 => 'ᆱ', 12604 => 'ᆲ', 12605 => 'ᆳ', 12606 => 'ᆴ', 12607 => 'ᆵ', 12608 => 'ᄚ', 12609 => 'ᄆ', 12610 => 'ᄇ', 12611 => 'ᄈ', 12612 => 'ᄡ', 12613 => 'ᄉ', 12614 => 'ᄊ', 12615 => 'ᄋ', 12616 => 'ᄌ', 12617 => 'ᄍ', 12618 => 'ᄎ', 12619 => 'ᄏ', 12620 => 'ᄐ', 12621 => 'ᄑ', 12622 => 'ᄒ', 12623 => 'ᅡ', 12624 => 'ᅢ', 12625 => 'ᅣ', 12626 => 'ᅤ', 12627 => 'ᅥ', 12628 => 'ᅦ', 12629 => 'ᅧ', 12630 => 'ᅨ', 12631 => 'ᅩ', 12632 => 'ᅪ', 12633 => 'ᅫ', 12634 => 'ᅬ', 12635 => 'ᅭ', 12636 => 'ᅮ', 12637 => 'ᅯ', 12638 => 'ᅰ', 12639 => 'ᅱ', 12640 => 'ᅲ', 12641 => 'ᅳ', 12642 => 'ᅴ', 12643 => 'ᅵ', 12645 => 'ᄔ', 12646 => 'ᄕ', 12647 => 'ᇇ', 12648 => 'ᇈ', 12649 => 'ᇌ', 12650 => 'ᇎ', 12651 => 'ᇓ', 12652 => 'ᇗ', 12653 => 'ᇙ', 12654 => 'ᄜ', 12655 => 'ᇝ', 12656 => 'ᇟ', 12657 => 'ᄝ', 12658 => 'ᄞ', 12659 => 'ᄠ', 12660 => 'ᄢ', 12661 => 'ᄣ', 12662 => 'ᄧ', 12663 => 'ᄩ', 12664 => 'ᄫ', 12665 => 'ᄬ', 12666 => 'ᄭ', 12667 => 'ᄮ', 12668 => 'ᄯ', 12669 => 'ᄲ', 12670 => 'ᄶ', 12671 => 'ᅀ', 12672 => 'ᅇ', 12673 => 'ᅌ', 12674 => 'ᇱ', 12675 => 'ᇲ', 12676 => 'ᅗ', 12677 => 'ᅘ', 12678 => 'ᅙ', 12679 => 'ᆄ', 12680 => 'ᆅ', 12681 => 'ᆈ', 12682 => 'ᆑ', 12683 => 'ᆒ', 12684 => 'ᆔ', 12685 => 'ᆞ', 12686 => 'ᆡ', 12690 => '一', 12691 => '二', 12692 => '三', 12693 => '四', 12694 => '上', 12695 => '中', 12696 => '下', 12697 => '甲', 12698 => '乙', 12699 => '丙', 12700 => '丁', 12701 => '天', 12702 => '地', 12703 => '人', 12868 => '問', 12869 => '幼', 12870 => '文', 12871 => '箏', 12880 => 'pte', 12881 => '21', 12882 => '22', 12883 => '23', 12884 => '24', 12885 => '25', 12886 => '26', 12887 => '27', 12888 => '28', 12889 => '29', 12890 => '30', 12891 => '31', 12892 => '32', 12893 => '33', 12894 => '34', 12895 => '35', 12896 => 'ᄀ', 12897 => 'ᄂ', 12898 => 'ᄃ', 12899 => 'ᄅ', 12900 => 'ᄆ', 12901 => 'ᄇ', 12902 => 'ᄉ', 12903 => 'ᄋ', 12904 => 'ᄌ', 12905 => 'ᄎ', 12906 => 'ᄏ', 12907 => 'ᄐ', 12908 => 'ᄑ', 12909 => 'ᄒ', 12910 => '가', 12911 => '나', 12912 => '다', 12913 => '라', 12914 => '마', 12915 => '바', 12916 => '사', 12917 => '아', 12918 => '자', 12919 => '차', 12920 => '카', 12921 => '타', 12922 => '파', 12923 => '하', 12924 => '참고', 12925 => '주의', 12926 => '우', 12928 => '一', 12929 => '二', 12930 => '三', 12931 => '四', 12932 => '五', 12933 => '六', 12934 => '七', 12935 => '八', 12936 => '九', 12937 => '十', 12938 => '月', 12939 => '火', 12940 => '水', 12941 => '木', 12942 => '金', 12943 => '土', 12944 => '日', 12945 => '株', 12946 => '有', 12947 => '社', 12948 => '名', 12949 => '特', 12950 => '財', 12951 => '祝', 12952 => '労', 12953 => '秘', 12954 => '男', 12955 => '女', 12956 => '適', 12957 => '優', 12958 => '印', 12959 => '注', 12960 => '項', 12961 => '休', 12962 => '写', 12963 => '正', 12964 => '上', 12965 => '中', 12966 => '下', 12967 => '左', 12968 => '右', 12969 => '医', 12970 => '宗', 12971 => '学', 12972 => '監', 12973 => '企', 12974 => '資', 12975 => '協', 12976 => '夜', 12977 => '36', 12978 => '37', 12979 => '38', 12980 => '39', 12981 => '40', 12982 => '41', 12983 => '42', 12984 => '43', 12985 => '44', 12986 => '45', 12987 => '46', 12988 => '47', 12989 => '48', 12990 => '49', 12991 => '50', 12992 => '1月', 12993 => '2月', 12994 => '3月', 12995 => '4月', 12996 => '5月', 12997 => '6月', 12998 => '7月', 12999 => '8月', 13000 => '9月', 13001 => '10月', 13002 => '11月', 13003 => '12月', 13004 => 'hg', 13005 => 'erg', 13006 => 'ev', 13007 => 'ltd', 13008 => 'ア', 13009 => 'イ', 13010 => 'ウ', 13011 => 'エ', 13012 => 'オ', 13013 => 'カ', 13014 => 'キ', 13015 => 'ク', 13016 => 'ケ', 13017 => 'コ', 13018 => 'サ', 13019 => 'シ', 13020 => 'ス', 13021 => 'セ', 13022 => 'ソ', 13023 => 'タ', 13024 => 'チ', 13025 => 'ツ', 13026 => 'テ', 13027 => 'ト', 13028 => 'ナ', 13029 => 'ニ', 13030 => 'ヌ', 13031 => 'ネ', 13032 => 'ノ', 13033 => 'ハ', 13034 => 'ヒ', 13035 => 'フ', 13036 => 'ヘ', 13037 => 'ホ', 13038 => 'マ', 13039 => 'ミ', 13040 => 'ム', 13041 => 'メ', 13042 => 'モ', 13043 => 'ヤ', 13044 => 'ユ', 13045 => 'ヨ', 13046 => 'ラ', 13047 => 'リ', 13048 => 'ル', 13049 => 'レ', 13050 => 'ロ', 13051 => 'ワ', 13052 => 'ヰ', 13053 => 'ヱ', 13054 => 'ヲ', 13055 => '令和', 13056 => 'アパート', 13057 => 'アルファ', 13058 => 'アンペア', 13059 => 'アール', 13060 => 'イニング', 13061 => 'インチ', 13062 => 'ウォン', 13063 => 'エスクード', 13064 => 'エーカー', 13065 => 'オンス', 13066 => 'オーム', 13067 => 'カイリ', 13068 => 'カラット', 13069 => 'カロリー', 13070 => 'ガロン', 13071 => 'ガンマ', 13072 => 'ギガ', 13073 => 'ギニー', 13074 => 'キュリー', 13075 => 'ギルダー', 13076 => 'キロ', 13077 => 'キログラム', 13078 => 'キロメートル', 13079 => 'キロワット', 13080 => 'グラム', 13081 => 'グラムトン', 13082 => 'クルゼイロ', 13083 => 'クローネ', 13084 => 'ケース', 13085 => 'コルナ', 13086 => 'コーポ', 13087 => 'サイクル', 13088 => 'サンチーム', 13089 => 'シリング', 13090 => 'センチ', 13091 => 'セント', 13092 => 'ダース', 13093 => 'デシ', 13094 => 'ドル', 13095 => 'トン', 13096 => 'ナノ', 13097 => 'ノット', 13098 => 'ハイツ', 13099 => 'パーセント', 13100 => 'パーツ', 13101 => 'バーレル', 13102 => 'ピアストル', 13103 => 'ピクル', 13104 => 'ピコ', 13105 => 'ビル', 13106 => 'ファラッド', 13107 => 'フィート', 13108 => 'ブッシェル', 13109 => 'フラン', 13110 => 'ヘクタール', 13111 => 'ペソ', 13112 => 'ペニヒ', 13113 => 'ヘルツ', 13114 => 'ペンス', 13115 => 'ページ', 13116 => 'ベータ', 13117 => 'ポイント', 13118 => 'ボルト', 13119 => 'ホン', 13120 => 'ポンド', 13121 => 'ホール', 13122 => 'ホーン', 13123 => 'マイクロ', 13124 => 'マイル', 13125 => 'マッハ', 13126 => 'マルク', 13127 => 'マンション', 13128 => 'ミクロン', 13129 => 'ミリ', 13130 => 'ミリバール', 13131 => 'メガ', 13132 => 'メガトン', 13133 => 'メートル', 13134 => 'ヤード', 13135 => 'ヤール', 13136 => 'ユアン', 13137 => 'リットル', 13138 => 'リラ', 13139 => 'ルピー', 13140 => 'ルーブル', 13141 => 'レム', 13142 => 'レントゲン', 13143 => 'ワット', 13144 => '0点', 13145 => '1点', 13146 => '2点', 13147 => '3点', 13148 => '4点', 13149 => '5点', 13150 => '6点', 13151 => '7点', 13152 => '8点', 13153 => '9点', 13154 => '10点', 13155 => '11点', 13156 => '12点', 13157 => '13点', 13158 => '14点', 13159 => '15点', 13160 => '16点', 13161 => '17点', 13162 => '18点', 13163 => '19点', 13164 => '20点', 13165 => '21点', 13166 => '22点', 13167 => '23点', 13168 => '24点', 13169 => 'hpa', 13170 => 'da', 13171 => 'au', 13172 => 'bar', 13173 => 'ov', 13174 => 'pc', 13175 => 'dm', 13176 => 'dm2', 13177 => 'dm3', 13178 => 'iu', 13179 => '平成', 13180 => '昭和', 13181 => '大正', 13182 => '明治', 13183 => '株式会社', 13184 => 'pa', 13185 => 'na', 13186 => 'μa', 13187 => 'ma', 13188 => 'ka', 13189 => 'kb', 13190 => 'mb', 13191 => 'gb', 13192 => 'cal', 13193 => 'kcal', 13194 => 'pf', 13195 => 'nf', 13196 => 'μf', 13197 => 'μg', 13198 => 'mg', 13199 => 'kg', 13200 => 'hz', 13201 => 'khz', 13202 => 'mhz', 13203 => 'ghz', 13204 => 'thz', 13205 => 'μl', 13206 => 'ml', 13207 => 'dl', 13208 => 'kl', 13209 => 'fm', 13210 => 'nm', 13211 => 'μm', 13212 => 'mm', 13213 => 'cm', 13214 => 'km', 13215 => 'mm2', 13216 => 'cm2', 13217 => 'm2', 13218 => 'km2', 13219 => 'mm3', 13220 => 'cm3', 13221 => 'm3', 13222 => 'km3', 13223 => 'm∕s', 13224 => 'm∕s2', 13225 => 'pa', 13226 => 'kpa', 13227 => 'mpa', 13228 => 'gpa', 13229 => 'rad', 13230 => 'rad∕s', 13231 => 'rad∕s2', 13232 => 'ps', 13233 => 'ns', 13234 => 'μs', 13235 => 'ms', 13236 => 'pv', 13237 => 'nv', 13238 => 'μv', 13239 => 'mv', 13240 => 'kv', 13241 => 'mv', 13242 => 'pw', 13243 => 'nw', 13244 => 'μw', 13245 => 'mw', 13246 => 'kw', 13247 => 'mw', 13248 => 'kω', 13249 => 'mω', 13251 => 'bq', 13252 => 'cc', 13253 => 'cd', 13254 => 'c∕kg', 13256 => 'db', 13257 => 'gy', 13258 => 'ha', 13259 => 'hp', 13260 => 'in', 13261 => 'kk', 13262 => 'km', 13263 => 'kt', 13264 => 'lm', 13265 => 'ln', 13266 => 'log', 13267 => 'lx', 13268 => 'mb', 13269 => 'mil', 13270 => 'mol', 13271 => 'ph', 13273 => 'ppm', 13274 => 'pr', 13275 => 'sr', 13276 => 'sv', 13277 => 'wb', 13278 => 'v∕m', 13279 => 'a∕m', 13280 => '1日', 13281 => '2日', 13282 => '3日', 13283 => '4日', 13284 => '5日', 13285 => '6日', 13286 => '7日', 13287 => '8日', 13288 => '9日', 13289 => '10日', 13290 => '11日', 13291 => '12日', 13292 => '13日', 13293 => '14日', 13294 => '15日', 13295 => '16日', 13296 => '17日', 13297 => '18日', 13298 => '19日', 13299 => '20日', 13300 => '21日', 13301 => '22日', 13302 => '23日', 13303 => '24日', 13304 => '25日', 13305 => '26日', 13306 => '27日', 13307 => '28日', 13308 => '29日', 13309 => '30日', 13310 => '31日', 13311 => 'gal', 42560 => 'ꙁ', 42562 => 'ꙃ', 42564 => 'ꙅ', 42566 => 'ꙇ', 42568 => 'ꙉ', 42570 => 'ꙋ', 42572 => 'ꙍ', 42574 => 'ꙏ', 42576 => 'ꙑ', 42578 => 'ꙓ', 42580 => 'ꙕ', 42582 => 'ꙗ', 42584 => 'ꙙ', 42586 => 'ꙛ', 42588 => 'ꙝ', 42590 => 'ꙟ', 42592 => 'ꙡ', 42594 => 'ꙣ', 42596 => 'ꙥ', 42598 => 'ꙧ', 42600 => 'ꙩ', 42602 => 'ꙫ', 42604 => 'ꙭ', 42624 => 'ꚁ', 42626 => 'ꚃ', 42628 => 'ꚅ', 42630 => 'ꚇ', 42632 => 'ꚉ', 42634 => 'ꚋ', 42636 => 'ꚍ', 42638 => 'ꚏ', 42640 => 'ꚑ', 42642 => 'ꚓ', 42644 => 'ꚕ', 42646 => 'ꚗ', 42648 => 'ꚙ', 42650 => 'ꚛ', 42652 => 'ъ', 42653 => 'ь', 42786 => 'ꜣ', 42788 => 'ꜥ', 42790 => 'ꜧ', 42792 => 'ꜩ', 42794 => 'ꜫ', 42796 => 'ꜭ', 42798 => 'ꜯ', 42802 => 'ꜳ', 42804 => 'ꜵ', 42806 => 'ꜷ', 42808 => 'ꜹ', 42810 => 'ꜻ', 42812 => 'ꜽ', 42814 => 'ꜿ', 42816 => 'ꝁ', 42818 => 'ꝃ', 42820 => 'ꝅ', 42822 => 'ꝇ', 42824 => 'ꝉ', 42826 => 'ꝋ', 42828 => 'ꝍ', 42830 => 'ꝏ', 42832 => 'ꝑ', 42834 => 'ꝓ', 42836 => 'ꝕ', 42838 => 'ꝗ', 42840 => 'ꝙ', 42842 => 'ꝛ', 42844 => 'ꝝ', 42846 => 'ꝟ', 42848 => 'ꝡ', 42850 => 'ꝣ', 42852 => 'ꝥ', 42854 => 'ꝧ', 42856 => 'ꝩ', 42858 => 'ꝫ', 42860 => 'ꝭ', 42862 => 'ꝯ', 42864 => 'ꝯ', 42873 => 'ꝺ', 42875 => 'ꝼ', 42877 => 'ᵹ', 42878 => 'ꝿ', 42880 => 'ꞁ', 42882 => 'ꞃ', 42884 => 'ꞅ', 42886 => 'ꞇ', 42891 => 'ꞌ', 42893 => 'ɥ', 42896 => 'ꞑ', 42898 => 'ꞓ', 42902 => 'ꞗ', 42904 => 'ꞙ', 42906 => 'ꞛ', 42908 => 'ꞝ', 42910 => 'ꞟ', 42912 => 'ꞡ', 42914 => 'ꞣ', 42916 => 'ꞥ', 42918 => 'ꞧ', 42920 => 'ꞩ', 42922 => 'ɦ', 42923 => 'ɜ', 42924 => 'ɡ', 42925 => 'ɬ', 42926 => 'ɪ', 42928 => 'ʞ', 42929 => 'ʇ', 42930 => 'ʝ', 42931 => 'ꭓ', 42932 => 'ꞵ', 42934 => 'ꞷ', 42936 => 'ꞹ', 42938 => 'ꞻ', 42940 => 'ꞽ', 42942 => 'ꞿ', 42946 => 'ꟃ', 42948 => 'ꞔ', 42949 => 'ʂ', 42950 => 'ᶎ', 42951 => 'ꟈ', 42953 => 'ꟊ', 42997 => 'ꟶ', 43000 => 'ħ', 43001 => 'œ', 43868 => 'ꜧ', 43869 => 'ꬷ', 43870 => 'ɫ', 43871 => 'ꭒ', 43881 => 'ʍ', 43888 => 'Ꭰ', 43889 => 'Ꭱ', 43890 => 'Ꭲ', 43891 => 'Ꭳ', 43892 => 'Ꭴ', 43893 => 'Ꭵ', 43894 => 'Ꭶ', 43895 => 'Ꭷ', 43896 => 'Ꭸ', 43897 => 'Ꭹ', 43898 => 'Ꭺ', 43899 => 'Ꭻ', 43900 => 'Ꭼ', 43901 => 'Ꭽ', 43902 => 'Ꭾ', 43903 => 'Ꭿ', 43904 => 'Ꮀ', 43905 => 'Ꮁ', 43906 => 'Ꮂ', 43907 => 'Ꮃ', 43908 => 'Ꮄ', 43909 => 'Ꮅ', 43910 => 'Ꮆ', 43911 => 'Ꮇ', 43912 => 'Ꮈ', 43913 => 'Ꮉ', 43914 => 'Ꮊ', 43915 => 'Ꮋ', 43916 => 'Ꮌ', 43917 => 'Ꮍ', 43918 => 'Ꮎ', 43919 => 'Ꮏ', 43920 => 'Ꮐ', 43921 => 'Ꮑ', 43922 => 'Ꮒ', 43923 => 'Ꮓ', 43924 => 'Ꮔ', 43925 => 'Ꮕ', 43926 => 'Ꮖ', 43927 => 'Ꮗ', 43928 => 'Ꮘ', 43929 => 'Ꮙ', 43930 => 'Ꮚ', 43931 => 'Ꮛ', 43932 => 'Ꮜ', 43933 => 'Ꮝ', 43934 => 'Ꮞ', 43935 => 'Ꮟ', 43936 => 'Ꮠ', 43937 => 'Ꮡ', 43938 => 'Ꮢ', 43939 => 'Ꮣ', 43940 => 'Ꮤ', 43941 => 'Ꮥ', 43942 => 'Ꮦ', 43943 => 'Ꮧ', 43944 => 'Ꮨ', 43945 => 'Ꮩ', 43946 => 'Ꮪ', 43947 => 'Ꮫ', 43948 => 'Ꮬ', 43949 => 'Ꮭ', 43950 => 'Ꮮ', 43951 => 'Ꮯ', 43952 => 'Ꮰ', 43953 => 'Ꮱ', 43954 => 'Ꮲ', 43955 => 'Ꮳ', 43956 => 'Ꮴ', 43957 => 'Ꮵ', 43958 => 'Ꮶ', 43959 => 'Ꮷ', 43960 => 'Ꮸ', 43961 => 'Ꮹ', 43962 => 'Ꮺ', 43963 => 'Ꮻ', 43964 => 'Ꮼ', 43965 => 'Ꮽ', 43966 => 'Ꮾ', 43967 => 'Ꮿ', 63744 => '豈', 63745 => '更', 63746 => '車', 63747 => '賈', 63748 => '滑', 63749 => '串', 63750 => '句', 63751 => '龜', 63752 => '龜', 63753 => '契', 63754 => '金', 63755 => '喇', 63756 => '奈', 63757 => '懶', 63758 => '癩', 63759 => '羅', 63760 => '蘿', 63761 => '螺', 63762 => '裸', 63763 => '邏', 63764 => '樂', 63765 => '洛', 63766 => '烙', 63767 => '珞', 63768 => '落', 63769 => '酪', 63770 => '駱', 63771 => '亂', 63772 => '卵', 63773 => '欄', 63774 => '爛', 63775 => '蘭', 63776 => '鸞', 63777 => '嵐', 63778 => '濫', 63779 => '藍', 63780 => '襤', 63781 => '拉', 63782 => '臘', 63783 => '蠟', 63784 => '廊', 63785 => '朗', 63786 => '浪', 63787 => '狼', 63788 => '郎', 63789 => '來', 63790 => '冷', 63791 => '勞', 63792 => '擄', 63793 => '櫓', 63794 => '爐', 63795 => '盧', 63796 => '老', 63797 => '蘆', 63798 => '虜', 63799 => '路', 63800 => '露', 63801 => '魯', 63802 => '鷺', 63803 => '碌', 63804 => '祿', 63805 => '綠', 63806 => '菉', 63807 => '錄', 63808 => '鹿', 63809 => '論', 63810 => '壟', 63811 => '弄', 63812 => '籠', 63813 => '聾', 63814 => '牢', 63815 => '磊', 63816 => '賂', 63817 => '雷', 63818 => '壘', 63819 => '屢', 63820 => '樓', 63821 => '淚', 63822 => '漏', 63823 => '累', 63824 => '縷', 63825 => '陋', 63826 => '勒', 63827 => '肋', 63828 => '凜', 63829 => '凌', 63830 => '稜', 63831 => '綾', 63832 => '菱', 63833 => '陵', 63834 => '讀', 63835 => '拏', 63836 => '樂', 63837 => '諾', 63838 => '丹', 63839 => '寧', 63840 => '怒', 63841 => '率', 63842 => '異', 63843 => '北', 63844 => '磻', 63845 => '便', 63846 => '復', 63847 => '不', 63848 => '泌', 63849 => '數', 63850 => '索', 63851 => '參', 63852 => '塞', 63853 => '省', 63854 => '葉', 63855 => '說', 63856 => '殺', 63857 => '辰', 63858 => '沈', 63859 => '拾', 63860 => '若', 63861 => '掠', 63862 => '略', 63863 => '亮', 63864 => '兩', 63865 => '凉', 63866 => '梁', 63867 => '糧', 63868 => '良', 63869 => '諒', 63870 => '量', 63871 => '勵', 63872 => '呂', 63873 => '女', 63874 => '廬', 63875 => '旅', 63876 => '濾', 63877 => '礪', 63878 => '閭', 63879 => '驪', 63880 => '麗', 63881 => '黎', 63882 => '力', 63883 => '曆', 63884 => '歷', 63885 => '轢', 63886 => '年', 63887 => '憐', 63888 => '戀', 63889 => '撚', 63890 => '漣', 63891 => '煉', 63892 => '璉', 63893 => '秊', 63894 => '練', 63895 => '聯', 63896 => '輦', 63897 => '蓮', 63898 => '連', 63899 => '鍊', 63900 => '列', 63901 => '劣', 63902 => '咽', 63903 => '烈', 63904 => '裂', 63905 => '說', 63906 => '廉', 63907 => '念', 63908 => '捻', 63909 => '殮', 63910 => '簾', 63911 => '獵', 63912 => '令', 63913 => '囹', 63914 => '寧', 63915 => '嶺', 63916 => '怜', 63917 => '玲', 63918 => '瑩', 63919 => '羚', 63920 => '聆', 63921 => '鈴', 63922 => '零', 63923 => '靈', 63924 => '領', 63925 => '例', 63926 => '禮', 63927 => '醴', 63928 => '隸', 63929 => '惡', 63930 => '了', 63931 => '僚', 63932 => '寮', 63933 => '尿', 63934 => '料', 63935 => '樂', 63936 => '燎', 63937 => '療', 63938 => '蓼', 63939 => '遼', 63940 => '龍', 63941 => '暈', 63942 => '阮', 63943 => '劉', 63944 => '杻', 63945 => '柳', 63946 => '流', 63947 => '溜', 63948 => '琉', 63949 => '留', 63950 => '硫', 63951 => '紐', 63952 => '類', 63953 => '六', 63954 => '戮', 63955 => '陸', 63956 => '倫', 63957 => '崙', 63958 => '淪', 63959 => '輪', 63960 => '律', 63961 => '慄', 63962 => '栗', 63963 => '率', 63964 => '隆', 63965 => '利', 63966 => '吏', 63967 => '履', 63968 => '易', 63969 => '李', 63970 => '梨', 63971 => '泥', 63972 => '理', 63973 => '痢', 63974 => '罹', 63975 => '裏', 63976 => '裡', 63977 => '里', 63978 => '離', 63979 => '匿', 63980 => '溺', 63981 => '吝', 63982 => '燐', 63983 => '璘', 63984 => '藺', 63985 => '隣', 63986 => '鱗', 63987 => '麟', 63988 => '林', 63989 => '淋', 63990 => '臨', 63991 => '立', 63992 => '笠', 63993 => '粒', 63994 => '狀', 63995 => '炙', 63996 => '識', 63997 => '什', 63998 => '茶', 63999 => '刺', 64000 => '切', 64001 => '度', 64002 => '拓', 64003 => '糖', 64004 => '宅', 64005 => '洞', 64006 => '暴', 64007 => '輻', 64008 => '行', 64009 => '降', 64010 => '見', 64011 => '廓', 64012 => '兀', 64013 => '嗀', 64016 => '塚', 64018 => '晴', 64021 => '凞', 64022 => '猪', 64023 => '益', 64024 => '礼', 64025 => '神', 64026 => '祥', 64027 => '福', 64028 => '靖', 64029 => '精', 64030 => '羽', 64032 => '蘒', 64034 => '諸', 64037 => '逸', 64038 => '都', 64042 => '飯', 64043 => '飼', 64044 => '館', 64045 => '鶴', 64046 => '郞', 64047 => '隷', 64048 => '侮', 64049 => '僧', 64050 => '免', 64051 => '勉', 64052 => '勤', 64053 => '卑', 64054 => '喝', 64055 => '嘆', 64056 => '器', 64057 => '塀', 64058 => '墨', 64059 => '層', 64060 => '屮', 64061 => '悔', 64062 => '慨', 64063 => '憎', 64064 => '懲', 64065 => '敏', 64066 => '既', 64067 => '暑', 64068 => '梅', 64069 => '海', 64070 => '渚', 64071 => '漢', 64072 => '煮', 64073 => '爫', 64074 => '琢', 64075 => '碑', 64076 => '社', 64077 => '祉', 64078 => '祈', 64079 => '祐', 64080 => '祖', 64081 => '祝', 64082 => '禍', 64083 => '禎', 64084 => '穀', 64085 => '突', 64086 => '節', 64087 => '練', 64088 => '縉', 64089 => '繁', 64090 => '署', 64091 => '者', 64092 => '臭', 64093 => '艹', 64094 => '艹', 64095 => '著', 64096 => '褐', 64097 => '視', 64098 => '謁', 64099 => '謹', 64100 => '賓', 64101 => '贈', 64102 => '辶', 64103 => '逸', 64104 => '難', 64105 => '響', 64106 => '頻', 64107 => '恵', 64108 => '𤋮', 64109 => '舘', 64112 => '並', 64113 => '况', 64114 => '全', 64115 => '侀', 64116 => '充', 64117 => '冀', 64118 => '勇', 64119 => '勺', 64120 => '喝', 64121 => '啕', 64122 => '喙', 64123 => '嗢', 64124 => '塚', 64125 => '墳', 64126 => '奄', 64127 => '奔', 64128 => '婢', 64129 => '嬨', 64130 => '廒', 64131 => '廙', 64132 => '彩', 64133 => '徭', 64134 => '惘', 64135 => '慎', 64136 => '愈', 64137 => '憎', 64138 => '慠', 64139 => '懲', 64140 => '戴', 64141 => '揄', 64142 => '搜', 64143 => '摒', 64144 => '敖', 64145 => '晴', 64146 => '朗', 64147 => '望', 64148 => '杖', 64149 => '歹', 64150 => '殺', 64151 => '流', 64152 => '滛', 64153 => '滋', 64154 => '漢', 64155 => '瀞', 64156 => '煮', 64157 => '瞧', 64158 => '爵', 64159 => '犯', 64160 => '猪', 64161 => '瑱', 64162 => '甆', 64163 => '画', 64164 => '瘝', 64165 => '瘟', 64166 => '益', 64167 => '盛', 64168 => '直', 64169 => '睊', 64170 => '着', 64171 => '磌', 64172 => '窱', 64173 => '節', 64174 => '类', 64175 => '絛', 64176 => '練', 64177 => '缾', 64178 => '者', 64179 => '荒', 64180 => '華', 64181 => '蝹', 64182 => '襁', 64183 => '覆', 64184 => '視', 64185 => '調', 64186 => '諸', 64187 => '請', 64188 => '謁', 64189 => '諾', 64190 => '諭', 64191 => '謹', 64192 => '變', 64193 => '贈', 64194 => '輸', 64195 => '遲', 64196 => '醙', 64197 => '鉶', 64198 => '陼', 64199 => '難', 64200 => '靖', 64201 => '韛', 64202 => '響', 64203 => '頋', 64204 => '頻', 64205 => '鬒', 64206 => '龜', 64207 => '𢡊', 64208 => '𢡄', 64209 => '𣏕', 64210 => '㮝', 64211 => '䀘', 64212 => '䀹', 64213 => '𥉉', 64214 => '𥳐', 64215 => '𧻓', 64216 => '齃', 64217 => '龎', 64256 => 'ff', 64257 => 'fi', 64258 => 'fl', 64259 => 'ffi', 64260 => 'ffl', 64261 => 'st', 64262 => 'st', 64275 => 'մն', 64276 => 'մե', 64277 => 'մի', 64278 => 'վն', 64279 => 'մխ', 64285 => 'יִ', 64287 => 'ײַ', 64288 => 'ע', 64289 => 'א', 64290 => 'ד', 64291 => 'ה', 64292 => 'כ', 64293 => 'ל', 64294 => 'ם', 64295 => 'ר', 64296 => 'ת', 64298 => 'שׁ', 64299 => 'שׂ', 64300 => 'שּׁ', 64301 => 'שּׂ', 64302 => 'אַ', 64303 => 'אָ', 64304 => 'אּ', 64305 => 'בּ', 64306 => 'גּ', 64307 => 'דּ', 64308 => 'הּ', 64309 => 'וּ', 64310 => 'זּ', 64312 => 'טּ', 64313 => 'יּ', 64314 => 'ךּ', 64315 => 'כּ', 64316 => 'לּ', 64318 => 'מּ', 64320 => 'נּ', 64321 => 'סּ', 64323 => 'ףּ', 64324 => 'פּ', 64326 => 'צּ', 64327 => 'קּ', 64328 => 'רּ', 64329 => 'שּ', 64330 => 'תּ', 64331 => 'וֹ', 64332 => 'בֿ', 64333 => 'כֿ', 64334 => 'פֿ', 64335 => 'אל', 64336 => 'ٱ', 64337 => 'ٱ', 64338 => 'ٻ', 64339 => 'ٻ', 64340 => 'ٻ', 64341 => 'ٻ', 64342 => 'پ', 64343 => 'پ', 64344 => 'پ', 64345 => 'پ', 64346 => 'ڀ', 64347 => 'ڀ', 64348 => 'ڀ', 64349 => 'ڀ', 64350 => 'ٺ', 64351 => 'ٺ', 64352 => 'ٺ', 64353 => 'ٺ', 64354 => 'ٿ', 64355 => 'ٿ', 64356 => 'ٿ', 64357 => 'ٿ', 64358 => 'ٹ', 64359 => 'ٹ', 64360 => 'ٹ', 64361 => 'ٹ', 64362 => 'ڤ', 64363 => 'ڤ', 64364 => 'ڤ', 64365 => 'ڤ', 64366 => 'ڦ', 64367 => 'ڦ', 64368 => 'ڦ', 64369 => 'ڦ', 64370 => 'ڄ', 64371 => 'ڄ', 64372 => 'ڄ', 64373 => 'ڄ', 64374 => 'ڃ', 64375 => 'ڃ', 64376 => 'ڃ', 64377 => 'ڃ', 64378 => 'چ', 64379 => 'چ', 64380 => 'چ', 64381 => 'چ', 64382 => 'ڇ', 64383 => 'ڇ', 64384 => 'ڇ', 64385 => 'ڇ', 64386 => 'ڍ', 64387 => 'ڍ', 64388 => 'ڌ', 64389 => 'ڌ', 64390 => 'ڎ', 64391 => 'ڎ', 64392 => 'ڈ', 64393 => 'ڈ', 64394 => 'ژ', 64395 => 'ژ', 64396 => 'ڑ', 64397 => 'ڑ', 64398 => 'ک', 64399 => 'ک', 64400 => 'ک', 64401 => 'ک', 64402 => 'گ', 64403 => 'گ', 64404 => 'گ', 64405 => 'گ', 64406 => 'ڳ', 64407 => 'ڳ', 64408 => 'ڳ', 64409 => 'ڳ', 64410 => 'ڱ', 64411 => 'ڱ', 64412 => 'ڱ', 64413 => 'ڱ', 64414 => 'ں', 64415 => 'ں', 64416 => 'ڻ', 64417 => 'ڻ', 64418 => 'ڻ', 64419 => 'ڻ', 64420 => 'ۀ', 64421 => 'ۀ', 64422 => 'ہ', 64423 => 'ہ', 64424 => 'ہ', 64425 => 'ہ', 64426 => 'ھ', 64427 => 'ھ', 64428 => 'ھ', 64429 => 'ھ', 64430 => 'ے', 64431 => 'ے', 64432 => 'ۓ', 64433 => 'ۓ', 64467 => 'ڭ', 64468 => 'ڭ', 64469 => 'ڭ', 64470 => 'ڭ', 64471 => 'ۇ', 64472 => 'ۇ', 64473 => 'ۆ', 64474 => 'ۆ', 64475 => 'ۈ', 64476 => 'ۈ', 64477 => 'ۇٴ', 64478 => 'ۋ', 64479 => 'ۋ', 64480 => 'ۅ', 64481 => 'ۅ', 64482 => 'ۉ', 64483 => 'ۉ', 64484 => 'ې', 64485 => 'ې', 64486 => 'ې', 64487 => 'ې', 64488 => 'ى', 64489 => 'ى', 64490 => 'ئا', 64491 => 'ئا', 64492 => 'ئە', 64493 => 'ئە', 64494 => 'ئو', 64495 => 'ئو', 64496 => 'ئۇ', 64497 => 'ئۇ', 64498 => 'ئۆ', 64499 => 'ئۆ', 64500 => 'ئۈ', 64501 => 'ئۈ', 64502 => 'ئې', 64503 => 'ئې', 64504 => 'ئې', 64505 => 'ئى', 64506 => 'ئى', 64507 => 'ئى', 64508 => 'ی', 64509 => 'ی', 64510 => 'ی', 64511 => 'ی', 64512 => 'ئج', 64513 => 'ئح', 64514 => 'ئم', 64515 => 'ئى', 64516 => 'ئي', 64517 => 'بج', 64518 => 'بح', 64519 => 'بخ', 64520 => 'بم', 64521 => 'بى', 64522 => 'بي', 64523 => 'تج', 64524 => 'تح', 64525 => 'تخ', 64526 => 'تم', 64527 => 'تى', 64528 => 'تي', 64529 => 'ثج', 64530 => 'ثم', 64531 => 'ثى', 64532 => 'ثي', 64533 => 'جح', 64534 => 'جم', 64535 => 'حج', 64536 => 'حم', 64537 => 'خج', 64538 => 'خح', 64539 => 'خم', 64540 => 'سج', 64541 => 'سح', 64542 => 'سخ', 64543 => 'سم', 64544 => 'صح', 64545 => 'صم', 64546 => 'ضج', 64547 => 'ضح', 64548 => 'ضخ', 64549 => 'ضم', 64550 => 'طح', 64551 => 'طم', 64552 => 'ظم', 64553 => 'عج', 64554 => 'عم', 64555 => 'غج', 64556 => 'غم', 64557 => 'فج', 64558 => 'فح', 64559 => 'فخ', 64560 => 'فم', 64561 => 'فى', 64562 => 'في', 64563 => 'قح', 64564 => 'قم', 64565 => 'قى', 64566 => 'قي', 64567 => 'كا', 64568 => 'كج', 64569 => 'كح', 64570 => 'كخ', 64571 => 'كل', 64572 => 'كم', 64573 => 'كى', 64574 => 'كي', 64575 => 'لج', 64576 => 'لح', 64577 => 'لخ', 64578 => 'لم', 64579 => 'لى', 64580 => 'لي', 64581 => 'مج', 64582 => 'مح', 64583 => 'مخ', 64584 => 'مم', 64585 => 'مى', 64586 => 'مي', 64587 => 'نج', 64588 => 'نح', 64589 => 'نخ', 64590 => 'نم', 64591 => 'نى', 64592 => 'ني', 64593 => 'هج', 64594 => 'هم', 64595 => 'هى', 64596 => 'هي', 64597 => 'يج', 64598 => 'يح', 64599 => 'يخ', 64600 => 'يم', 64601 => 'يى', 64602 => 'يي', 64603 => 'ذٰ', 64604 => 'رٰ', 64605 => 'ىٰ', 64612 => 'ئر', 64613 => 'ئز', 64614 => 'ئم', 64615 => 'ئن', 64616 => 'ئى', 64617 => 'ئي', 64618 => 'بر', 64619 => 'بز', 64620 => 'بم', 64621 => 'بن', 64622 => 'بى', 64623 => 'بي', 64624 => 'تر', 64625 => 'تز', 64626 => 'تم', 64627 => 'تن', 64628 => 'تى', 64629 => 'تي', 64630 => 'ثر', 64631 => 'ثز', 64632 => 'ثم', 64633 => 'ثن', 64634 => 'ثى', 64635 => 'ثي', 64636 => 'فى', 64637 => 'في', 64638 => 'قى', 64639 => 'قي', 64640 => 'كا', 64641 => 'كل', 64642 => 'كم', 64643 => 'كى', 64644 => 'كي', 64645 => 'لم', 64646 => 'لى', 64647 => 'لي', 64648 => 'ما', 64649 => 'مم', 64650 => 'نر', 64651 => 'نز', 64652 => 'نم', 64653 => 'نن', 64654 => 'نى', 64655 => 'ني', 64656 => 'ىٰ', 64657 => 'ير', 64658 => 'يز', 64659 => 'يم', 64660 => 'ين', 64661 => 'يى', 64662 => 'يي', 64663 => 'ئج', 64664 => 'ئح', 64665 => 'ئخ', 64666 => 'ئم', 64667 => 'ئه', 64668 => 'بج', 64669 => 'بح', 64670 => 'بخ', 64671 => 'بم', 64672 => 'به', 64673 => 'تج', 64674 => 'تح', 64675 => 'تخ', 64676 => 'تم', 64677 => 'ته', 64678 => 'ثم', 64679 => 'جح', 64680 => 'جم', 64681 => 'حج', 64682 => 'حم', 64683 => 'خج', 64684 => 'خم', 64685 => 'سج', 64686 => 'سح', 64687 => 'سخ', 64688 => 'سم', 64689 => 'صح', 64690 => 'صخ', 64691 => 'صم', 64692 => 'ضج', 64693 => 'ضح', 64694 => 'ضخ', 64695 => 'ضم', 64696 => 'طح', 64697 => 'ظم', 64698 => 'عج', 64699 => 'عم', 64700 => 'غج', 64701 => 'غم', 64702 => 'فج', 64703 => 'فح', 64704 => 'فخ', 64705 => 'فم', 64706 => 'قح', 64707 => 'قم', 64708 => 'كج', 64709 => 'كح', 64710 => 'كخ', 64711 => 'كل', 64712 => 'كم', 64713 => 'لج', 64714 => 'لح', 64715 => 'لخ', 64716 => 'لم', 64717 => 'له', 64718 => 'مج', 64719 => 'مح', 64720 => 'مخ', 64721 => 'مم', 64722 => 'نج', 64723 => 'نح', 64724 => 'نخ', 64725 => 'نم', 64726 => 'نه', 64727 => 'هج', 64728 => 'هم', 64729 => 'هٰ', 64730 => 'يج', 64731 => 'يح', 64732 => 'يخ', 64733 => 'يم', 64734 => 'يه', 64735 => 'ئم', 64736 => 'ئه', 64737 => 'بم', 64738 => 'به', 64739 => 'تم', 64740 => 'ته', 64741 => 'ثم', 64742 => 'ثه', 64743 => 'سم', 64744 => 'سه', 64745 => 'شم', 64746 => 'شه', 64747 => 'كل', 64748 => 'كم', 64749 => 'لم', 64750 => 'نم', 64751 => 'نه', 64752 => 'يم', 64753 => 'يه', 64754 => 'ـَّ', 64755 => 'ـُّ', 64756 => 'ـِّ', 64757 => 'طى', 64758 => 'طي', 64759 => 'عى', 64760 => 'عي', 64761 => 'غى', 64762 => 'غي', 64763 => 'سى', 64764 => 'سي', 64765 => 'شى', 64766 => 'شي', 64767 => 'حى', 64768 => 'حي', 64769 => 'جى', 64770 => 'جي', 64771 => 'خى', 64772 => 'خي', 64773 => 'صى', 64774 => 'صي', 64775 => 'ضى', 64776 => 'ضي', 64777 => 'شج', 64778 => 'شح', 64779 => 'شخ', 64780 => 'شم', 64781 => 'شر', 64782 => 'سر', 64783 => 'صر', 64784 => 'ضر', 64785 => 'طى', 64786 => 'طي', 64787 => 'عى', 64788 => 'عي', 64789 => 'غى', 64790 => 'غي', 64791 => 'سى', 64792 => 'سي', 64793 => 'شى', 64794 => 'شي', 64795 => 'حى', 64796 => 'حي', 64797 => 'جى', 64798 => 'جي', 64799 => 'خى', 64800 => 'خي', 64801 => 'صى', 64802 => 'صي', 64803 => 'ضى', 64804 => 'ضي', 64805 => 'شج', 64806 => 'شح', 64807 => 'شخ', 64808 => 'شم', 64809 => 'شر', 64810 => 'سر', 64811 => 'صر', 64812 => 'ضر', 64813 => 'شج', 64814 => 'شح', 64815 => 'شخ', 64816 => 'شم', 64817 => 'سه', 64818 => 'شه', 64819 => 'طم', 64820 => 'سج', 64821 => 'سح', 64822 => 'سخ', 64823 => 'شج', 64824 => 'شح', 64825 => 'شخ', 64826 => 'طم', 64827 => 'ظم', 64828 => 'اً', 64829 => 'اً', 64848 => 'تجم', 64849 => 'تحج', 64850 => 'تحج', 64851 => 'تحم', 64852 => 'تخم', 64853 => 'تمج', 64854 => 'تمح', 64855 => 'تمخ', 64856 => 'جمح', 64857 => 'جمح', 64858 => 'حمي', 64859 => 'حمى', 64860 => 'سحج', 64861 => 'سجح', 64862 => 'سجى', 64863 => 'سمح', 64864 => 'سمح', 64865 => 'سمج', 64866 => 'سمم', 64867 => 'سمم', 64868 => 'صحح', 64869 => 'صحح', 64870 => 'صمم', 64871 => 'شحم', 64872 => 'شحم', 64873 => 'شجي', 64874 => 'شمخ', 64875 => 'شمخ', 64876 => 'شمم', 64877 => 'شمم', 64878 => 'ضحى', 64879 => 'ضخم', 64880 => 'ضخم', 64881 => 'طمح', 64882 => 'طمح', 64883 => 'طمم', 64884 => 'طمي', 64885 => 'عجم', 64886 => 'عمم', 64887 => 'عمم', 64888 => 'عمى', 64889 => 'غمم', 64890 => 'غمي', 64891 => 'غمى', 64892 => 'فخم', 64893 => 'فخم', 64894 => 'قمح', 64895 => 'قمم', 64896 => 'لحم', 64897 => 'لحي', 64898 => 'لحى', 64899 => 'لجج', 64900 => 'لجج', 64901 => 'لخم', 64902 => 'لخم', 64903 => 'لمح', 64904 => 'لمح', 64905 => 'محج', 64906 => 'محم', 64907 => 'محي', 64908 => 'مجح', 64909 => 'مجم', 64910 => 'مخج', 64911 => 'مخم', 64914 => 'مجخ', 64915 => 'همج', 64916 => 'همم', 64917 => 'نحم', 64918 => 'نحى', 64919 => 'نجم', 64920 => 'نجم', 64921 => 'نجى', 64922 => 'نمي', 64923 => 'نمى', 64924 => 'يمم', 64925 => 'يمم', 64926 => 'بخي', 64927 => 'تجي', 64928 => 'تجى', 64929 => 'تخي', 64930 => 'تخى', 64931 => 'تمي', 64932 => 'تمى', 64933 => 'جمي', 64934 => 'جحى', 64935 => 'جمى', 64936 => 'سخى', 64937 => 'صحي', 64938 => 'شحي', 64939 => 'ضحي', 64940 => 'لجي', 64941 => 'لمي', 64942 => 'يحي', 64943 => 'يجي', 64944 => 'يمي', 64945 => 'ممي', 64946 => 'قمي', 64947 => 'نحي', 64948 => 'قمح', 64949 => 'لحم', 64950 => 'عمي', 64951 => 'كمي', 64952 => 'نجح', 64953 => 'مخي', 64954 => 'لجم', 64955 => 'كمم', 64956 => 'لجم', 64957 => 'نجح', 64958 => 'جحي', 64959 => 'حجي', 64960 => 'مجي', 64961 => 'فمي', 64962 => 'بحي', 64963 => 'كمم', 64964 => 'عجم', 64965 => 'صمم', 64966 => 'سخي', 64967 => 'نجي', 65008 => 'صلے', 65009 => 'قلے', 65010 => 'الله', 65011 => 'اكبر', 65012 => 'محمد', 65013 => 'صلعم', 65014 => 'رسول', 65015 => 'عليه', 65016 => 'وسلم', 65017 => 'صلى', 65020 => 'ریال', 65041 => '、', 65047 => '〖', 65048 => '〗', 65073 => '—', 65074 => '–', 65081 => '〔', 65082 => '〕', 65083 => '【', 65084 => '】', 65085 => '《', 65086 => '》', 65087 => '〈', 65088 => '〉', 65089 => '「', 65090 => '」', 65091 => '『', 65092 => '』', 65105 => '、', 65112 => '—', 65117 => '〔', 65118 => '〕', 65123 => '-', 65137 => 'ـً', 65143 => 'ـَ', 65145 => 'ـُ', 65147 => 'ـِ', 65149 => 'ـّ', 65151 => 'ـْ', 65152 => 'ء', 65153 => 'آ', 65154 => 'آ', 65155 => 'أ', 65156 => 'أ', 65157 => 'ؤ', 65158 => 'ؤ', 65159 => 'إ', 65160 => 'إ', 65161 => 'ئ', 65162 => 'ئ', 65163 => 'ئ', 65164 => 'ئ', 65165 => 'ا', 65166 => 'ا', 65167 => 'ب', 65168 => 'ب', 65169 => 'ب', 65170 => 'ب', 65171 => 'ة', 65172 => 'ة', 65173 => 'ت', 65174 => 'ت', 65175 => 'ت', 65176 => 'ت', 65177 => 'ث', 65178 => 'ث', 65179 => 'ث', 65180 => 'ث', 65181 => 'ج', 65182 => 'ج', 65183 => 'ج', 65184 => 'ج', 65185 => 'ح', 65186 => 'ح', 65187 => 'ح', 65188 => 'ح', 65189 => 'خ', 65190 => 'خ', 65191 => 'خ', 65192 => 'خ', 65193 => 'د', 65194 => 'د', 65195 => 'ذ', 65196 => 'ذ', 65197 => 'ر', 65198 => 'ر', 65199 => 'ز', 65200 => 'ز', 65201 => 'س', 65202 => 'س', 65203 => 'س', 65204 => 'س', 65205 => 'ش', 65206 => 'ش', 65207 => 'ش', 65208 => 'ش', 65209 => 'ص', 65210 => 'ص', 65211 => 'ص', 65212 => 'ص', 65213 => 'ض', 65214 => 'ض', 65215 => 'ض', 65216 => 'ض', 65217 => 'ط', 65218 => 'ط', 65219 => 'ط', 65220 => 'ط', 65221 => 'ظ', 65222 => 'ظ', 65223 => 'ظ', 65224 => 'ظ', 65225 => 'ع', 65226 => 'ع', 65227 => 'ع', 65228 => 'ع', 65229 => 'غ', 65230 => 'غ', 65231 => 'غ', 65232 => 'غ', 65233 => 'ف', 65234 => 'ف', 65235 => 'ف', 65236 => 'ف', 65237 => 'ق', 65238 => 'ق', 65239 => 'ق', 65240 => 'ق', 65241 => 'ك', 65242 => 'ك', 65243 => 'ك', 65244 => 'ك', 65245 => 'ل', 65246 => 'ل', 65247 => 'ل', 65248 => 'ل', 65249 => 'م', 65250 => 'م', 65251 => 'م', 65252 => 'م', 65253 => 'ن', 65254 => 'ن', 65255 => 'ن', 65256 => 'ن', 65257 => 'ه', 65258 => 'ه', 65259 => 'ه', 65260 => 'ه', 65261 => 'و', 65262 => 'و', 65263 => 'ى', 65264 => 'ى', 65265 => 'ي', 65266 => 'ي', 65267 => 'ي', 65268 => 'ي', 65269 => 'لآ', 65270 => 'لآ', 65271 => 'لأ', 65272 => 'لأ', 65273 => 'لإ', 65274 => 'لإ', 65275 => 'لا', 65276 => 'لا', 65293 => '-', 65294 => '.', 65296 => '0', 65297 => '1', 65298 => '2', 65299 => '3', 65300 => '4', 65301 => '5', 65302 => '6', 65303 => '7', 65304 => '8', 65305 => '9', 65313 => 'a', 65314 => 'b', 65315 => 'c', 65316 => 'd', 65317 => 'e', 65318 => 'f', 65319 => 'g', 65320 => 'h', 65321 => 'i', 65322 => 'j', 65323 => 'k', 65324 => 'l', 65325 => 'm', 65326 => 'n', 65327 => 'o', 65328 => 'p', 65329 => 'q', 65330 => 'r', 65331 => 's', 65332 => 't', 65333 => 'u', 65334 => 'v', 65335 => 'w', 65336 => 'x', 65337 => 'y', 65338 => 'z', 65345 => 'a', 65346 => 'b', 65347 => 'c', 65348 => 'd', 65349 => 'e', 65350 => 'f', 65351 => 'g', 65352 => 'h', 65353 => 'i', 65354 => 'j', 65355 => 'k', 65356 => 'l', 65357 => 'm', 65358 => 'n', 65359 => 'o', 65360 => 'p', 65361 => 'q', 65362 => 'r', 65363 => 's', 65364 => 't', 65365 => 'u', 65366 => 'v', 65367 => 'w', 65368 => 'x', 65369 => 'y', 65370 => 'z', 65375 => '⦅', 65376 => '⦆', 65377 => '.', 65378 => '「', 65379 => '」', 65380 => '、', 65381 => '・', 65382 => 'ヲ', 65383 => 'ァ', 65384 => 'ィ', 65385 => 'ゥ', 65386 => 'ェ', 65387 => 'ォ', 65388 => 'ャ', 65389 => 'ュ', 65390 => 'ョ', 65391 => 'ッ', 65392 => 'ー', 65393 => 'ア', 65394 => 'イ', 65395 => 'ウ', 65396 => 'エ', 65397 => 'オ', 65398 => 'カ', 65399 => 'キ', 65400 => 'ク', 65401 => 'ケ', 65402 => 'コ', 65403 => 'サ', 65404 => 'シ', 65405 => 'ス', 65406 => 'セ', 65407 => 'ソ', 65408 => 'タ', 65409 => 'チ', 65410 => 'ツ', 65411 => 'テ', 65412 => 'ト', 65413 => 'ナ', 65414 => 'ニ', 65415 => 'ヌ', 65416 => 'ネ', 65417 => 'ノ', 65418 => 'ハ', 65419 => 'ヒ', 65420 => 'フ', 65421 => 'ヘ', 65422 => 'ホ', 65423 => 'マ', 65424 => 'ミ', 65425 => 'ム', 65426 => 'メ', 65427 => 'モ', 65428 => 'ヤ', 65429 => 'ユ', 65430 => 'ヨ', 65431 => 'ラ', 65432 => 'リ', 65433 => 'ル', 65434 => 'レ', 65435 => 'ロ', 65436 => 'ワ', 65437 => 'ン', 65438 => '゙', 65439 => '゚', 65441 => 'ᄀ', 65442 => 'ᄁ', 65443 => 'ᆪ', 65444 => 'ᄂ', 65445 => 'ᆬ', 65446 => 'ᆭ', 65447 => 'ᄃ', 65448 => 'ᄄ', 65449 => 'ᄅ', 65450 => 'ᆰ', 65451 => 'ᆱ', 65452 => 'ᆲ', 65453 => 'ᆳ', 65454 => 'ᆴ', 65455 => 'ᆵ', 65456 => 'ᄚ', 65457 => 'ᄆ', 65458 => 'ᄇ', 65459 => 'ᄈ', 65460 => 'ᄡ', 65461 => 'ᄉ', 65462 => 'ᄊ', 65463 => 'ᄋ', 65464 => 'ᄌ', 65465 => 'ᄍ', 65466 => 'ᄎ', 65467 => 'ᄏ', 65468 => 'ᄐ', 65469 => 'ᄑ', 65470 => 'ᄒ', 65474 => 'ᅡ', 65475 => 'ᅢ', 65476 => 'ᅣ', 65477 => 'ᅤ', 65478 => 'ᅥ', 65479 => 'ᅦ', 65482 => 'ᅧ', 65483 => 'ᅨ', 65484 => 'ᅩ', 65485 => 'ᅪ', 65486 => 'ᅫ', 65487 => 'ᅬ', 65490 => 'ᅭ', 65491 => 'ᅮ', 65492 => 'ᅯ', 65493 => 'ᅰ', 65494 => 'ᅱ', 65495 => 'ᅲ', 65498 => 'ᅳ', 65499 => 'ᅴ', 65500 => 'ᅵ', 65504 => '¢', 65505 => '£', 65506 => '¬', 65508 => '¦', 65509 => '¥', 65510 => '₩', 65512 => '│', 65513 => '←', 65514 => '↑', 65515 => '→', 65516 => '↓', 65517 => '■', 65518 => '○', 66560 => '𐐨', 66561 => '𐐩', 66562 => '𐐪', 66563 => '𐐫', 66564 => '𐐬', 66565 => '𐐭', 66566 => '𐐮', 66567 => '𐐯', 66568 => '𐐰', 66569 => '𐐱', 66570 => '𐐲', 66571 => '𐐳', 66572 => '𐐴', 66573 => '𐐵', 66574 => '𐐶', 66575 => '𐐷', 66576 => '𐐸', 66577 => '𐐹', 66578 => '𐐺', 66579 => '𐐻', 66580 => '𐐼', 66581 => '𐐽', 66582 => '𐐾', 66583 => '𐐿', 66584 => '𐑀', 66585 => '𐑁', 66586 => '𐑂', 66587 => '𐑃', 66588 => '𐑄', 66589 => '𐑅', 66590 => '𐑆', 66591 => '𐑇', 66592 => '𐑈', 66593 => '𐑉', 66594 => '𐑊', 66595 => '𐑋', 66596 => '𐑌', 66597 => '𐑍', 66598 => '𐑎', 66599 => '𐑏', 66736 => '𐓘', 66737 => '𐓙', 66738 => '𐓚', 66739 => '𐓛', 66740 => '𐓜', 66741 => '𐓝', 66742 => '𐓞', 66743 => '𐓟', 66744 => '𐓠', 66745 => '𐓡', 66746 => '𐓢', 66747 => '𐓣', 66748 => '𐓤', 66749 => '𐓥', 66750 => '𐓦', 66751 => '𐓧', 66752 => '𐓨', 66753 => '𐓩', 66754 => '𐓪', 66755 => '𐓫', 66756 => '𐓬', 66757 => '𐓭', 66758 => '𐓮', 66759 => '𐓯', 66760 => '𐓰', 66761 => '𐓱', 66762 => '𐓲', 66763 => '𐓳', 66764 => '𐓴', 66765 => '𐓵', 66766 => '𐓶', 66767 => '𐓷', 66768 => '𐓸', 66769 => '𐓹', 66770 => '𐓺', 66771 => '𐓻', 68736 => '𐳀', 68737 => '𐳁', 68738 => '𐳂', 68739 => '𐳃', 68740 => '𐳄', 68741 => '𐳅', 68742 => '𐳆', 68743 => '𐳇', 68744 => '𐳈', 68745 => '𐳉', 68746 => '𐳊', 68747 => '𐳋', 68748 => '𐳌', 68749 => '𐳍', 68750 => '𐳎', 68751 => '𐳏', 68752 => '𐳐', 68753 => '𐳑', 68754 => '𐳒', 68755 => '𐳓', 68756 => '𐳔', 68757 => '𐳕', 68758 => '𐳖', 68759 => '𐳗', 68760 => '𐳘', 68761 => '𐳙', 68762 => '𐳚', 68763 => '𐳛', 68764 => '𐳜', 68765 => '𐳝', 68766 => '𐳞', 68767 => '𐳟', 68768 => '𐳠', 68769 => '𐳡', 68770 => '𐳢', 68771 => '𐳣', 68772 => '𐳤', 68773 => '𐳥', 68774 => '𐳦', 68775 => '𐳧', 68776 => '𐳨', 68777 => '𐳩', 68778 => '𐳪', 68779 => '𐳫', 68780 => '𐳬', 68781 => '𐳭', 68782 => '𐳮', 68783 => '𐳯', 68784 => '𐳰', 68785 => '𐳱', 68786 => '𐳲', 71840 => '𑣀', 71841 => '𑣁', 71842 => '𑣂', 71843 => '𑣃', 71844 => '𑣄', 71845 => '𑣅', 71846 => '𑣆', 71847 => '𑣇', 71848 => '𑣈', 71849 => '𑣉', 71850 => '𑣊', 71851 => '𑣋', 71852 => '𑣌', 71853 => '𑣍', 71854 => '𑣎', 71855 => '𑣏', 71856 => '𑣐', 71857 => '𑣑', 71858 => '𑣒', 71859 => '𑣓', 71860 => '𑣔', 71861 => '𑣕', 71862 => '𑣖', 71863 => '𑣗', 71864 => '𑣘', 71865 => '𑣙', 71866 => '𑣚', 71867 => '𑣛', 71868 => '𑣜', 71869 => '𑣝', 71870 => '𑣞', 71871 => '𑣟', 93760 => '𖹠', 93761 => '𖹡', 93762 => '𖹢', 93763 => '𖹣', 93764 => '𖹤', 93765 => '𖹥', 93766 => '𖹦', 93767 => '𖹧', 93768 => '𖹨', 93769 => '𖹩', 93770 => '𖹪', 93771 => '𖹫', 93772 => '𖹬', 93773 => '𖹭', 93774 => '𖹮', 93775 => '𖹯', 93776 => '𖹰', 93777 => '𖹱', 93778 => '𖹲', 93779 => '𖹳', 93780 => '𖹴', 93781 => '𖹵', 93782 => '𖹶', 93783 => '𖹷', 93784 => '𖹸', 93785 => '𖹹', 93786 => '𖹺', 93787 => '𖹻', 93788 => '𖹼', 93789 => '𖹽', 93790 => '𖹾', 93791 => '𖹿', 119134 => '𝅗𝅥', 119135 => '𝅘𝅥', 119136 => '𝅘𝅥𝅮', 119137 => '𝅘𝅥𝅯', 119138 => '𝅘𝅥𝅰', 119139 => '𝅘𝅥𝅱', 119140 => '𝅘𝅥𝅲', 119227 => '𝆹𝅥', 119228 => '𝆺𝅥', 119229 => '𝆹𝅥𝅮', 119230 => '𝆺𝅥𝅮', 119231 => '𝆹𝅥𝅯', 119232 => '𝆺𝅥𝅯', 119808 => 'a', 119809 => 'b', 119810 => 'c', 119811 => 'd', 119812 => 'e', 119813 => 'f', 119814 => 'g', 119815 => 'h', 119816 => 'i', 119817 => 'j', 119818 => 'k', 119819 => 'l', 119820 => 'm', 119821 => 'n', 119822 => 'o', 119823 => 'p', 119824 => 'q', 119825 => 'r', 119826 => 's', 119827 => 't', 119828 => 'u', 119829 => 'v', 119830 => 'w', 119831 => 'x', 119832 => 'y', 119833 => 'z', 119834 => 'a', 119835 => 'b', 119836 => 'c', 119837 => 'd', 119838 => 'e', 119839 => 'f', 119840 => 'g', 119841 => 'h', 119842 => 'i', 119843 => 'j', 119844 => 'k', 119845 => 'l', 119846 => 'm', 119847 => 'n', 119848 => 'o', 119849 => 'p', 119850 => 'q', 119851 => 'r', 119852 => 's', 119853 => 't', 119854 => 'u', 119855 => 'v', 119856 => 'w', 119857 => 'x', 119858 => 'y', 119859 => 'z', 119860 => 'a', 119861 => 'b', 119862 => 'c', 119863 => 'd', 119864 => 'e', 119865 => 'f', 119866 => 'g', 119867 => 'h', 119868 => 'i', 119869 => 'j', 119870 => 'k', 119871 => 'l', 119872 => 'm', 119873 => 'n', 119874 => 'o', 119875 => 'p', 119876 => 'q', 119877 => 'r', 119878 => 's', 119879 => 't', 119880 => 'u', 119881 => 'v', 119882 => 'w', 119883 => 'x', 119884 => 'y', 119885 => 'z', 119886 => 'a', 119887 => 'b', 119888 => 'c', 119889 => 'd', 119890 => 'e', 119891 => 'f', 119892 => 'g', 119894 => 'i', 119895 => 'j', 119896 => 'k', 119897 => 'l', 119898 => 'm', 119899 => 'n', 119900 => 'o', 119901 => 'p', 119902 => 'q', 119903 => 'r', 119904 => 's', 119905 => 't', 119906 => 'u', 119907 => 'v', 119908 => 'w', 119909 => 'x', 119910 => 'y', 119911 => 'z', 119912 => 'a', 119913 => 'b', 119914 => 'c', 119915 => 'd', 119916 => 'e', 119917 => 'f', 119918 => 'g', 119919 => 'h', 119920 => 'i', 119921 => 'j', 119922 => 'k', 119923 => 'l', 119924 => 'm', 119925 => 'n', 119926 => 'o', 119927 => 'p', 119928 => 'q', 119929 => 'r', 119930 => 's', 119931 => 't', 119932 => 'u', 119933 => 'v', 119934 => 'w', 119935 => 'x', 119936 => 'y', 119937 => 'z', 119938 => 'a', 119939 => 'b', 119940 => 'c', 119941 => 'd', 119942 => 'e', 119943 => 'f', 119944 => 'g', 119945 => 'h', 119946 => 'i', 119947 => 'j', 119948 => 'k', 119949 => 'l', 119950 => 'm', 119951 => 'n', 119952 => 'o', 119953 => 'p', 119954 => 'q', 119955 => 'r', 119956 => 's', 119957 => 't', 119958 => 'u', 119959 => 'v', 119960 => 'w', 119961 => 'x', 119962 => 'y', 119963 => 'z', 119964 => 'a', 119966 => 'c', 119967 => 'd', 119970 => 'g', 119973 => 'j', 119974 => 'k', 119977 => 'n', 119978 => 'o', 119979 => 'p', 119980 => 'q', 119982 => 's', 119983 => 't', 119984 => 'u', 119985 => 'v', 119986 => 'w', 119987 => 'x', 119988 => 'y', 119989 => 'z', 119990 => 'a', 119991 => 'b', 119992 => 'c', 119993 => 'd', 119995 => 'f', 119997 => 'h', 119998 => 'i', 119999 => 'j', 120000 => 'k', 120001 => 'l', 120002 => 'm', 120003 => 'n', 120005 => 'p', 120006 => 'q', 120007 => 'r', 120008 => 's', 120009 => 't', 120010 => 'u', 120011 => 'v', 120012 => 'w', 120013 => 'x', 120014 => 'y', 120015 => 'z', 120016 => 'a', 120017 => 'b', 120018 => 'c', 120019 => 'd', 120020 => 'e', 120021 => 'f', 120022 => 'g', 120023 => 'h', 120024 => 'i', 120025 => 'j', 120026 => 'k', 120027 => 'l', 120028 => 'm', 120029 => 'n', 120030 => 'o', 120031 => 'p', 120032 => 'q', 120033 => 'r', 120034 => 's', 120035 => 't', 120036 => 'u', 120037 => 'v', 120038 => 'w', 120039 => 'x', 120040 => 'y', 120041 => 'z', 120042 => 'a', 120043 => 'b', 120044 => 'c', 120045 => 'd', 120046 => 'e', 120047 => 'f', 120048 => 'g', 120049 => 'h', 120050 => 'i', 120051 => 'j', 120052 => 'k', 120053 => 'l', 120054 => 'm', 120055 => 'n', 120056 => 'o', 120057 => 'p', 120058 => 'q', 120059 => 'r', 120060 => 's', 120061 => 't', 120062 => 'u', 120063 => 'v', 120064 => 'w', 120065 => 'x', 120066 => 'y', 120067 => 'z', 120068 => 'a', 120069 => 'b', 120071 => 'd', 120072 => 'e', 120073 => 'f', 120074 => 'g', 120077 => 'j', 120078 => 'k', 120079 => 'l', 120080 => 'm', 120081 => 'n', 120082 => 'o', 120083 => 'p', 120084 => 'q', 120086 => 's', 120087 => 't', 120088 => 'u', 120089 => 'v', 120090 => 'w', 120091 => 'x', 120092 => 'y', 120094 => 'a', 120095 => 'b', 120096 => 'c', 120097 => 'd', 120098 => 'e', 120099 => 'f', 120100 => 'g', 120101 => 'h', 120102 => 'i', 120103 => 'j', 120104 => 'k', 120105 => 'l', 120106 => 'm', 120107 => 'n', 120108 => 'o', 120109 => 'p', 120110 => 'q', 120111 => 'r', 120112 => 's', 120113 => 't', 120114 => 'u', 120115 => 'v', 120116 => 'w', 120117 => 'x', 120118 => 'y', 120119 => 'z', 120120 => 'a', 120121 => 'b', 120123 => 'd', 120124 => 'e', 120125 => 'f', 120126 => 'g', 120128 => 'i', 120129 => 'j', 120130 => 'k', 120131 => 'l', 120132 => 'm', 120134 => 'o', 120138 => 's', 120139 => 't', 120140 => 'u', 120141 => 'v', 120142 => 'w', 120143 => 'x', 120144 => 'y', 120146 => 'a', 120147 => 'b', 120148 => 'c', 120149 => 'd', 120150 => 'e', 120151 => 'f', 120152 => 'g', 120153 => 'h', 120154 => 'i', 120155 => 'j', 120156 => 'k', 120157 => 'l', 120158 => 'm', 120159 => 'n', 120160 => 'o', 120161 => 'p', 120162 => 'q', 120163 => 'r', 120164 => 's', 120165 => 't', 120166 => 'u', 120167 => 'v', 120168 => 'w', 120169 => 'x', 120170 => 'y', 120171 => 'z', 120172 => 'a', 120173 => 'b', 120174 => 'c', 120175 => 'd', 120176 => 'e', 120177 => 'f', 120178 => 'g', 120179 => 'h', 120180 => 'i', 120181 => 'j', 120182 => 'k', 120183 => 'l', 120184 => 'm', 120185 => 'n', 120186 => 'o', 120187 => 'p', 120188 => 'q', 120189 => 'r', 120190 => 's', 120191 => 't', 120192 => 'u', 120193 => 'v', 120194 => 'w', 120195 => 'x', 120196 => 'y', 120197 => 'z', 120198 => 'a', 120199 => 'b', 120200 => 'c', 120201 => 'd', 120202 => 'e', 120203 => 'f', 120204 => 'g', 120205 => 'h', 120206 => 'i', 120207 => 'j', 120208 => 'k', 120209 => 'l', 120210 => 'm', 120211 => 'n', 120212 => 'o', 120213 => 'p', 120214 => 'q', 120215 => 'r', 120216 => 's', 120217 => 't', 120218 => 'u', 120219 => 'v', 120220 => 'w', 120221 => 'x', 120222 => 'y', 120223 => 'z', 120224 => 'a', 120225 => 'b', 120226 => 'c', 120227 => 'd', 120228 => 'e', 120229 => 'f', 120230 => 'g', 120231 => 'h', 120232 => 'i', 120233 => 'j', 120234 => 'k', 120235 => 'l', 120236 => 'm', 120237 => 'n', 120238 => 'o', 120239 => 'p', 120240 => 'q', 120241 => 'r', 120242 => 's', 120243 => 't', 120244 => 'u', 120245 => 'v', 120246 => 'w', 120247 => 'x', 120248 => 'y', 120249 => 'z', 120250 => 'a', 120251 => 'b', 120252 => 'c', 120253 => 'd', 120254 => 'e', 120255 => 'f', 120256 => 'g', 120257 => 'h', 120258 => 'i', 120259 => 'j', 120260 => 'k', 120261 => 'l', 120262 => 'm', 120263 => 'n', 120264 => 'o', 120265 => 'p', 120266 => 'q', 120267 => 'r', 120268 => 's', 120269 => 't', 120270 => 'u', 120271 => 'v', 120272 => 'w', 120273 => 'x', 120274 => 'y', 120275 => 'z', 120276 => 'a', 120277 => 'b', 120278 => 'c', 120279 => 'd', 120280 => 'e', 120281 => 'f', 120282 => 'g', 120283 => 'h', 120284 => 'i', 120285 => 'j', 120286 => 'k', 120287 => 'l', 120288 => 'm', 120289 => 'n', 120290 => 'o', 120291 => 'p', 120292 => 'q', 120293 => 'r', 120294 => 's', 120295 => 't', 120296 => 'u', 120297 => 'v', 120298 => 'w', 120299 => 'x', 120300 => 'y', 120301 => 'z', 120302 => 'a', 120303 => 'b', 120304 => 'c', 120305 => 'd', 120306 => 'e', 120307 => 'f', 120308 => 'g', 120309 => 'h', 120310 => 'i', 120311 => 'j', 120312 => 'k', 120313 => 'l', 120314 => 'm', 120315 => 'n', 120316 => 'o', 120317 => 'p', 120318 => 'q', 120319 => 'r', 120320 => 's', 120321 => 't', 120322 => 'u', 120323 => 'v', 120324 => 'w', 120325 => 'x', 120326 => 'y', 120327 => 'z', 120328 => 'a', 120329 => 'b', 120330 => 'c', 120331 => 'd', 120332 => 'e', 120333 => 'f', 120334 => 'g', 120335 => 'h', 120336 => 'i', 120337 => 'j', 120338 => 'k', 120339 => 'l', 120340 => 'm', 120341 => 'n', 120342 => 'o', 120343 => 'p', 120344 => 'q', 120345 => 'r', 120346 => 's', 120347 => 't', 120348 => 'u', 120349 => 'v', 120350 => 'w', 120351 => 'x', 120352 => 'y', 120353 => 'z', 120354 => 'a', 120355 => 'b', 120356 => 'c', 120357 => 'd', 120358 => 'e', 120359 => 'f', 120360 => 'g', 120361 => 'h', 120362 => 'i', 120363 => 'j', 120364 => 'k', 120365 => 'l', 120366 => 'm', 120367 => 'n', 120368 => 'o', 120369 => 'p', 120370 => 'q', 120371 => 'r', 120372 => 's', 120373 => 't', 120374 => 'u', 120375 => 'v', 120376 => 'w', 120377 => 'x', 120378 => 'y', 120379 => 'z', 120380 => 'a', 120381 => 'b', 120382 => 'c', 120383 => 'd', 120384 => 'e', 120385 => 'f', 120386 => 'g', 120387 => 'h', 120388 => 'i', 120389 => 'j', 120390 => 'k', 120391 => 'l', 120392 => 'm', 120393 => 'n', 120394 => 'o', 120395 => 'p', 120396 => 'q', 120397 => 'r', 120398 => 's', 120399 => 't', 120400 => 'u', 120401 => 'v', 120402 => 'w', 120403 => 'x', 120404 => 'y', 120405 => 'z', 120406 => 'a', 120407 => 'b', 120408 => 'c', 120409 => 'd', 120410 => 'e', 120411 => 'f', 120412 => 'g', 120413 => 'h', 120414 => 'i', 120415 => 'j', 120416 => 'k', 120417 => 'l', 120418 => 'm', 120419 => 'n', 120420 => 'o', 120421 => 'p', 120422 => 'q', 120423 => 'r', 120424 => 's', 120425 => 't', 120426 => 'u', 120427 => 'v', 120428 => 'w', 120429 => 'x', 120430 => 'y', 120431 => 'z', 120432 => 'a', 120433 => 'b', 120434 => 'c', 120435 => 'd', 120436 => 'e', 120437 => 'f', 120438 => 'g', 120439 => 'h', 120440 => 'i', 120441 => 'j', 120442 => 'k', 120443 => 'l', 120444 => 'm', 120445 => 'n', 120446 => 'o', 120447 => 'p', 120448 => 'q', 120449 => 'r', 120450 => 's', 120451 => 't', 120452 => 'u', 120453 => 'v', 120454 => 'w', 120455 => 'x', 120456 => 'y', 120457 => 'z', 120458 => 'a', 120459 => 'b', 120460 => 'c', 120461 => 'd', 120462 => 'e', 120463 => 'f', 120464 => 'g', 120465 => 'h', 120466 => 'i', 120467 => 'j', 120468 => 'k', 120469 => 'l', 120470 => 'm', 120471 => 'n', 120472 => 'o', 120473 => 'p', 120474 => 'q', 120475 => 'r', 120476 => 's', 120477 => 't', 120478 => 'u', 120479 => 'v', 120480 => 'w', 120481 => 'x', 120482 => 'y', 120483 => 'z', 120484 => 'ı', 120485 => 'ȷ', 120488 => 'α', 120489 => 'β', 120490 => 'γ', 120491 => 'δ', 120492 => 'ε', 120493 => 'ζ', 120494 => 'η', 120495 => 'θ', 120496 => 'ι', 120497 => 'κ', 120498 => 'λ', 120499 => 'μ', 120500 => 'ν', 120501 => 'ξ', 120502 => 'ο', 120503 => 'π', 120504 => 'ρ', 120505 => 'θ', 120506 => 'σ', 120507 => 'τ', 120508 => 'υ', 120509 => 'φ', 120510 => 'χ', 120511 => 'ψ', 120512 => 'ω', 120513 => '∇', 120514 => 'α', 120515 => 'β', 120516 => 'γ', 120517 => 'δ', 120518 => 'ε', 120519 => 'ζ', 120520 => 'η', 120521 => 'θ', 120522 => 'ι', 120523 => 'κ', 120524 => 'λ', 120525 => 'μ', 120526 => 'ν', 120527 => 'ξ', 120528 => 'ο', 120529 => 'π', 120530 => 'ρ', 120531 => 'σ', 120532 => 'σ', 120533 => 'τ', 120534 => 'υ', 120535 => 'φ', 120536 => 'χ', 120537 => 'ψ', 120538 => 'ω', 120539 => '∂', 120540 => 'ε', 120541 => 'θ', 120542 => 'κ', 120543 => 'φ', 120544 => 'ρ', 120545 => 'π', 120546 => 'α', 120547 => 'β', 120548 => 'γ', 120549 => 'δ', 120550 => 'ε', 120551 => 'ζ', 120552 => 'η', 120553 => 'θ', 120554 => 'ι', 120555 => 'κ', 120556 => 'λ', 120557 => 'μ', 120558 => 'ν', 120559 => 'ξ', 120560 => 'ο', 120561 => 'π', 120562 => 'ρ', 120563 => 'θ', 120564 => 'σ', 120565 => 'τ', 120566 => 'υ', 120567 => 'φ', 120568 => 'χ', 120569 => 'ψ', 120570 => 'ω', 120571 => '∇', 120572 => 'α', 120573 => 'β', 120574 => 'γ', 120575 => 'δ', 120576 => 'ε', 120577 => 'ζ', 120578 => 'η', 120579 => 'θ', 120580 => 'ι', 120581 => 'κ', 120582 => 'λ', 120583 => 'μ', 120584 => 'ν', 120585 => 'ξ', 120586 => 'ο', 120587 => 'π', 120588 => 'ρ', 120589 => 'σ', 120590 => 'σ', 120591 => 'τ', 120592 => 'υ', 120593 => 'φ', 120594 => 'χ', 120595 => 'ψ', 120596 => 'ω', 120597 => '∂', 120598 => 'ε', 120599 => 'θ', 120600 => 'κ', 120601 => 'φ', 120602 => 'ρ', 120603 => 'π', 120604 => 'α', 120605 => 'β', 120606 => 'γ', 120607 => 'δ', 120608 => 'ε', 120609 => 'ζ', 120610 => 'η', 120611 => 'θ', 120612 => 'ι', 120613 => 'κ', 120614 => 'λ', 120615 => 'μ', 120616 => 'ν', 120617 => 'ξ', 120618 => 'ο', 120619 => 'π', 120620 => 'ρ', 120621 => 'θ', 120622 => 'σ', 120623 => 'τ', 120624 => 'υ', 120625 => 'φ', 120626 => 'χ', 120627 => 'ψ', 120628 => 'ω', 120629 => '∇', 120630 => 'α', 120631 => 'β', 120632 => 'γ', 120633 => 'δ', 120634 => 'ε', 120635 => 'ζ', 120636 => 'η', 120637 => 'θ', 120638 => 'ι', 120639 => 'κ', 120640 => 'λ', 120641 => 'μ', 120642 => 'ν', 120643 => 'ξ', 120644 => 'ο', 120645 => 'π', 120646 => 'ρ', 120647 => 'σ', 120648 => 'σ', 120649 => 'τ', 120650 => 'υ', 120651 => 'φ', 120652 => 'χ', 120653 => 'ψ', 120654 => 'ω', 120655 => '∂', 120656 => 'ε', 120657 => 'θ', 120658 => 'κ', 120659 => 'φ', 120660 => 'ρ', 120661 => 'π', 120662 => 'α', 120663 => 'β', 120664 => 'γ', 120665 => 'δ', 120666 => 'ε', 120667 => 'ζ', 120668 => 'η', 120669 => 'θ', 120670 => 'ι', 120671 => 'κ', 120672 => 'λ', 120673 => 'μ', 120674 => 'ν', 120675 => 'ξ', 120676 => 'ο', 120677 => 'π', 120678 => 'ρ', 120679 => 'θ', 120680 => 'σ', 120681 => 'τ', 120682 => 'υ', 120683 => 'φ', 120684 => 'χ', 120685 => 'ψ', 120686 => 'ω', 120687 => '∇', 120688 => 'α', 120689 => 'β', 120690 => 'γ', 120691 => 'δ', 120692 => 'ε', 120693 => 'ζ', 120694 => 'η', 120695 => 'θ', 120696 => 'ι', 120697 => 'κ', 120698 => 'λ', 120699 => 'μ', 120700 => 'ν', 120701 => 'ξ', 120702 => 'ο', 120703 => 'π', 120704 => 'ρ', 120705 => 'σ', 120706 => 'σ', 120707 => 'τ', 120708 => 'υ', 120709 => 'φ', 120710 => 'χ', 120711 => 'ψ', 120712 => 'ω', 120713 => '∂', 120714 => 'ε', 120715 => 'θ', 120716 => 'κ', 120717 => 'φ', 120718 => 'ρ', 120719 => 'π', 120720 => 'α', 120721 => 'β', 120722 => 'γ', 120723 => 'δ', 120724 => 'ε', 120725 => 'ζ', 120726 => 'η', 120727 => 'θ', 120728 => 'ι', 120729 => 'κ', 120730 => 'λ', 120731 => 'μ', 120732 => 'ν', 120733 => 'ξ', 120734 => 'ο', 120735 => 'π', 120736 => 'ρ', 120737 => 'θ', 120738 => 'σ', 120739 => 'τ', 120740 => 'υ', 120741 => 'φ', 120742 => 'χ', 120743 => 'ψ', 120744 => 'ω', 120745 => '∇', 120746 => 'α', 120747 => 'β', 120748 => 'γ', 120749 => 'δ', 120750 => 'ε', 120751 => 'ζ', 120752 => 'η', 120753 => 'θ', 120754 => 'ι', 120755 => 'κ', 120756 => 'λ', 120757 => 'μ', 120758 => 'ν', 120759 => 'ξ', 120760 => 'ο', 120761 => 'π', 120762 => 'ρ', 120763 => 'σ', 120764 => 'σ', 120765 => 'τ', 120766 => 'υ', 120767 => 'φ', 120768 => 'χ', 120769 => 'ψ', 120770 => 'ω', 120771 => '∂', 120772 => 'ε', 120773 => 'θ', 120774 => 'κ', 120775 => 'φ', 120776 => 'ρ', 120777 => 'π', 120778 => 'ϝ', 120779 => 'ϝ', 120782 => '0', 120783 => '1', 120784 => '2', 120785 => '3', 120786 => '4', 120787 => '5', 120788 => '6', 120789 => '7', 120790 => '8', 120791 => '9', 120792 => '0', 120793 => '1', 120794 => '2', 120795 => '3', 120796 => '4', 120797 => '5', 120798 => '6', 120799 => '7', 120800 => '8', 120801 => '9', 120802 => '0', 120803 => '1', 120804 => '2', 120805 => '3', 120806 => '4', 120807 => '5', 120808 => '6', 120809 => '7', 120810 => '8', 120811 => '9', 120812 => '0', 120813 => '1', 120814 => '2', 120815 => '3', 120816 => '4', 120817 => '5', 120818 => '6', 120819 => '7', 120820 => '8', 120821 => '9', 120822 => '0', 120823 => '1', 120824 => '2', 120825 => '3', 120826 => '4', 120827 => '5', 120828 => '6', 120829 => '7', 120830 => '8', 120831 => '9', 125184 => '𞤢', 125185 => '𞤣', 125186 => '𞤤', 125187 => '𞤥', 125188 => '𞤦', 125189 => '𞤧', 125190 => '𞤨', 125191 => '𞤩', 125192 => '𞤪', 125193 => '𞤫', 125194 => '𞤬', 125195 => '𞤭', 125196 => '𞤮', 125197 => '𞤯', 125198 => '𞤰', 125199 => '𞤱', 125200 => '𞤲', 125201 => '𞤳', 125202 => '𞤴', 125203 => '𞤵', 125204 => '𞤶', 125205 => '𞤷', 125206 => '𞤸', 125207 => '𞤹', 125208 => '𞤺', 125209 => '𞤻', 125210 => '𞤼', 125211 => '𞤽', 125212 => '𞤾', 125213 => '𞤿', 125214 => '𞥀', 125215 => '𞥁', 125216 => '𞥂', 125217 => '𞥃', 126464 => 'ا', 126465 => 'ب', 126466 => 'ج', 126467 => 'د', 126469 => 'و', 126470 => 'ز', 126471 => 'ح', 126472 => 'ط', 126473 => 'ي', 126474 => 'ك', 126475 => 'ل', 126476 => 'م', 126477 => 'ن', 126478 => 'س', 126479 => 'ع', 126480 => 'ف', 126481 => 'ص', 126482 => 'ق', 126483 => 'ر', 126484 => 'ش', 126485 => 'ت', 126486 => 'ث', 126487 => 'خ', 126488 => 'ذ', 126489 => 'ض', 126490 => 'ظ', 126491 => 'غ', 126492 => 'ٮ', 126493 => 'ں', 126494 => 'ڡ', 126495 => 'ٯ', 126497 => 'ب', 126498 => 'ج', 126500 => 'ه', 126503 => 'ح', 126505 => 'ي', 126506 => 'ك', 126507 => 'ل', 126508 => 'م', 126509 => 'ن', 126510 => 'س', 126511 => 'ع', 126512 => 'ف', 126513 => 'ص', 126514 => 'ق', 126516 => 'ش', 126517 => 'ت', 126518 => 'ث', 126519 => 'خ', 126521 => 'ض', 126523 => 'غ', 126530 => 'ج', 126535 => 'ح', 126537 => 'ي', 126539 => 'ل', 126541 => 'ن', 126542 => 'س', 126543 => 'ع', 126545 => 'ص', 126546 => 'ق', 126548 => 'ش', 126551 => 'خ', 126553 => 'ض', 126555 => 'غ', 126557 => 'ں', 126559 => 'ٯ', 126561 => 'ب', 126562 => 'ج', 126564 => 'ه', 126567 => 'ح', 126568 => 'ط', 126569 => 'ي', 126570 => 'ك', 126572 => 'م', 126573 => 'ن', 126574 => 'س', 126575 => 'ع', 126576 => 'ف', 126577 => 'ص', 126578 => 'ق', 126580 => 'ش', 126581 => 'ت', 126582 => 'ث', 126583 => 'خ', 126585 => 'ض', 126586 => 'ظ', 126587 => 'غ', 126588 => 'ٮ', 126590 => 'ڡ', 126592 => 'ا', 126593 => 'ب', 126594 => 'ج', 126595 => 'د', 126596 => 'ه', 126597 => 'و', 126598 => 'ز', 126599 => 'ح', 126600 => 'ط', 126601 => 'ي', 126603 => 'ل', 126604 => 'م', 126605 => 'ن', 126606 => 'س', 126607 => 'ع', 126608 => 'ف', 126609 => 'ص', 126610 => 'ق', 126611 => 'ر', 126612 => 'ش', 126613 => 'ت', 126614 => 'ث', 126615 => 'خ', 126616 => 'ذ', 126617 => 'ض', 126618 => 'ظ', 126619 => 'غ', 126625 => 'ب', 126626 => 'ج', 126627 => 'د', 126629 => 'و', 126630 => 'ز', 126631 => 'ح', 126632 => 'ط', 126633 => 'ي', 126635 => 'ل', 126636 => 'م', 126637 => 'ن', 126638 => 'س', 126639 => 'ع', 126640 => 'ف', 126641 => 'ص', 126642 => 'ق', 126643 => 'ر', 126644 => 'ش', 126645 => 'ت', 126646 => 'ث', 126647 => 'خ', 126648 => 'ذ', 126649 => 'ض', 126650 => 'ظ', 126651 => 'غ', 127274 => '〔s〕', 127275 => 'c', 127276 => 'r', 127277 => 'cd', 127278 => 'wz', 127280 => 'a', 127281 => 'b', 127282 => 'c', 127283 => 'd', 127284 => 'e', 127285 => 'f', 127286 => 'g', 127287 => 'h', 127288 => 'i', 127289 => 'j', 127290 => 'k', 127291 => 'l', 127292 => 'm', 127293 => 'n', 127294 => 'o', 127295 => 'p', 127296 => 'q', 127297 => 'r', 127298 => 's', 127299 => 't', 127300 => 'u', 127301 => 'v', 127302 => 'w', 127303 => 'x', 127304 => 'y', 127305 => 'z', 127306 => 'hv', 127307 => 'mv', 127308 => 'sd', 127309 => 'ss', 127310 => 'ppv', 127311 => 'wc', 127338 => 'mc', 127339 => 'md', 127340 => 'mr', 127376 => 'dj', 127488 => 'ほか', 127489 => 'ココ', 127490 => 'サ', 127504 => '手', 127505 => '字', 127506 => '双', 127507 => 'デ', 127508 => '二', 127509 => '多', 127510 => '解', 127511 => '天', 127512 => '交', 127513 => '映', 127514 => '無', 127515 => '料', 127516 => '前', 127517 => '後', 127518 => '再', 127519 => '新', 127520 => '初', 127521 => '終', 127522 => '生', 127523 => '販', 127524 => '声', 127525 => '吹', 127526 => '演', 127527 => '投', 127528 => '捕', 127529 => '一', 127530 => '三', 127531 => '遊', 127532 => '左', 127533 => '中', 127534 => '右', 127535 => '指', 127536 => '走', 127537 => '打', 127538 => '禁', 127539 => '空', 127540 => '合', 127541 => '満', 127542 => '有', 127543 => '月', 127544 => '申', 127545 => '割', 127546 => '営', 127547 => '配', 127552 => '〔本〕', 127553 => '〔三〕', 127554 => '〔二〕', 127555 => '〔安〕', 127556 => '〔点〕', 127557 => '〔打〕', 127558 => '〔盗〕', 127559 => '〔勝〕', 127560 => '〔敗〕', 127568 => '得', 127569 => '可', 130032 => '0', 130033 => '1', 130034 => '2', 130035 => '3', 130036 => '4', 130037 => '5', 130038 => '6', 130039 => '7', 130040 => '8', 130041 => '9', 194560 => '丽', 194561 => '丸', 194562 => '乁', 194563 => '𠄢', 194564 => '你', 194565 => '侮', 194566 => '侻', 194567 => '倂', 194568 => '偺', 194569 => '備', 194570 => '僧', 194571 => '像', 194572 => '㒞', 194573 => '𠘺', 194574 => '免', 194575 => '兔', 194576 => '兤', 194577 => '具', 194578 => '𠔜', 194579 => '㒹', 194580 => '內', 194581 => '再', 194582 => '𠕋', 194583 => '冗', 194584 => '冤', 194585 => '仌', 194586 => '冬', 194587 => '况', 194588 => '𩇟', 194589 => '凵', 194590 => '刃', 194591 => '㓟', 194592 => '刻', 194593 => '剆', 194594 => '割', 194595 => '剷', 194596 => '㔕', 194597 => '勇', 194598 => '勉', 194599 => '勤', 194600 => '勺', 194601 => '包', 194602 => '匆', 194603 => '北', 194604 => '卉', 194605 => '卑', 194606 => '博', 194607 => '即', 194608 => '卽', 194609 => '卿', 194610 => '卿', 194611 => '卿', 194612 => '𠨬', 194613 => '灰', 194614 => '及', 194615 => '叟', 194616 => '𠭣', 194617 => '叫', 194618 => '叱', 194619 => '吆', 194620 => '咞', 194621 => '吸', 194622 => '呈', 194623 => '周', 194624 => '咢', 194625 => '哶', 194626 => '唐', 194627 => '啓', 194628 => '啣', 194629 => '善', 194630 => '善', 194631 => '喙', 194632 => '喫', 194633 => '喳', 194634 => '嗂', 194635 => '圖', 194636 => '嘆', 194637 => '圗', 194638 => '噑', 194639 => '噴', 194640 => '切', 194641 => '壮', 194642 => '城', 194643 => '埴', 194644 => '堍', 194645 => '型', 194646 => '堲', 194647 => '報', 194648 => '墬', 194649 => '𡓤', 194650 => '売', 194651 => '壷', 194652 => '夆', 194653 => '多', 194654 => '夢', 194655 => '奢', 194656 => '𡚨', 194657 => '𡛪', 194658 => '姬', 194659 => '娛', 194660 => '娧', 194661 => '姘', 194662 => '婦', 194663 => '㛮', 194665 => '嬈', 194666 => '嬾', 194667 => '嬾', 194668 => '𡧈', 194669 => '寃', 194670 => '寘', 194671 => '寧', 194672 => '寳', 194673 => '𡬘', 194674 => '寿', 194675 => '将', 194677 => '尢', 194678 => '㞁', 194679 => '屠', 194680 => '屮', 194681 => '峀', 194682 => '岍', 194683 => '𡷤', 194684 => '嵃', 194685 => '𡷦', 194686 => '嵮', 194687 => '嵫', 194688 => '嵼', 194689 => '巡', 194690 => '巢', 194691 => '㠯', 194692 => '巽', 194693 => '帨', 194694 => '帽', 194695 => '幩', 194696 => '㡢', 194697 => '𢆃', 194698 => '㡼', 194699 => '庰', 194700 => '庳', 194701 => '庶', 194702 => '廊', 194703 => '𪎒', 194704 => '廾', 194705 => '𢌱', 194706 => '𢌱', 194707 => '舁', 194708 => '弢', 194709 => '弢', 194710 => '㣇', 194711 => '𣊸', 194712 => '𦇚', 194713 => '形', 194714 => '彫', 194715 => '㣣', 194716 => '徚', 194717 => '忍', 194718 => '志', 194719 => '忹', 194720 => '悁', 194721 => '㤺', 194722 => '㤜', 194723 => '悔', 194724 => '𢛔', 194725 => '惇', 194726 => '慈', 194727 => '慌', 194728 => '慎', 194729 => '慌', 194730 => '慺', 194731 => '憎', 194732 => '憲', 194733 => '憤', 194734 => '憯', 194735 => '懞', 194736 => '懲', 194737 => '懶', 194738 => '成', 194739 => '戛', 194740 => '扝', 194741 => '抱', 194742 => '拔', 194743 => '捐', 194744 => '𢬌', 194745 => '挽', 194746 => '拼', 194747 => '捨', 194748 => '掃', 194749 => '揤', 194750 => '𢯱', 194751 => '搢', 194752 => '揅', 194753 => '掩', 194754 => '㨮', 194755 => '摩', 194756 => '摾', 194757 => '撝', 194758 => '摷', 194759 => '㩬', 194760 => '敏', 194761 => '敬', 194762 => '𣀊', 194763 => '旣', 194764 => '書', 194765 => '晉', 194766 => '㬙', 194767 => '暑', 194768 => '㬈', 194769 => '㫤', 194770 => '冒', 194771 => '冕', 194772 => '最', 194773 => '暜', 194774 => '肭', 194775 => '䏙', 194776 => '朗', 194777 => '望', 194778 => '朡', 194779 => '杞', 194780 => '杓', 194781 => '𣏃', 194782 => '㭉', 194783 => '柺', 194784 => '枅', 194785 => '桒', 194786 => '梅', 194787 => '𣑭', 194788 => '梎', 194789 => '栟', 194790 => '椔', 194791 => '㮝', 194792 => '楂', 194793 => '榣', 194794 => '槪', 194795 => '檨', 194796 => '𣚣', 194797 => '櫛', 194798 => '㰘', 194799 => '次', 194800 => '𣢧', 194801 => '歔', 194802 => '㱎', 194803 => '歲', 194804 => '殟', 194805 => '殺', 194806 => '殻', 194807 => '𣪍', 194808 => '𡴋', 194809 => '𣫺', 194810 => '汎', 194811 => '𣲼', 194812 => '沿', 194813 => '泍', 194814 => '汧', 194815 => '洖', 194816 => '派', 194817 => '海', 194818 => '流', 194819 => '浩', 194820 => '浸', 194821 => '涅', 194822 => '𣴞', 194823 => '洴', 194824 => '港', 194825 => '湮', 194826 => '㴳', 194827 => '滋', 194828 => '滇', 194829 => '𣻑', 194830 => '淹', 194831 => '潮', 194832 => '𣽞', 194833 => '𣾎', 194834 => '濆', 194835 => '瀹', 194836 => '瀞', 194837 => '瀛', 194838 => '㶖', 194839 => '灊', 194840 => '災', 194841 => '灷', 194842 => '炭', 194843 => '𠔥', 194844 => '煅', 194845 => '𤉣', 194846 => '熜', 194848 => '爨', 194849 => '爵', 194850 => '牐', 194851 => '𤘈', 194852 => '犀', 194853 => '犕', 194854 => '𤜵', 194855 => '𤠔', 194856 => '獺', 194857 => '王', 194858 => '㺬', 194859 => '玥', 194860 => '㺸', 194861 => '㺸', 194862 => '瑇', 194863 => '瑜', 194864 => '瑱', 194865 => '璅', 194866 => '瓊', 194867 => '㼛', 194868 => '甤', 194869 => '𤰶', 194870 => '甾', 194871 => '𤲒', 194872 => '異', 194873 => '𢆟', 194874 => '瘐', 194875 => '𤾡', 194876 => '𤾸', 194877 => '𥁄', 194878 => '㿼', 194879 => '䀈', 194880 => '直', 194881 => '𥃳', 194882 => '𥃲', 194883 => '𥄙', 194884 => '𥄳', 194885 => '眞', 194886 => '真', 194887 => '真', 194888 => '睊', 194889 => '䀹', 194890 => '瞋', 194891 => '䁆', 194892 => '䂖', 194893 => '𥐝', 194894 => '硎', 194895 => '碌', 194896 => '磌', 194897 => '䃣', 194898 => '𥘦', 194899 => '祖', 194900 => '𥚚', 194901 => '𥛅', 194902 => '福', 194903 => '秫', 194904 => '䄯', 194905 => '穀', 194906 => '穊', 194907 => '穏', 194908 => '𥥼', 194909 => '𥪧', 194910 => '𥪧', 194912 => '䈂', 194913 => '𥮫', 194914 => '篆', 194915 => '築', 194916 => '䈧', 194917 => '𥲀', 194918 => '糒', 194919 => '䊠', 194920 => '糨', 194921 => '糣', 194922 => '紀', 194923 => '𥾆', 194924 => '絣', 194925 => '䌁', 194926 => '緇', 194927 => '縂', 194928 => '繅', 194929 => '䌴', 194930 => '𦈨', 194931 => '𦉇', 194932 => '䍙', 194933 => '𦋙', 194934 => '罺', 194935 => '𦌾', 194936 => '羕', 194937 => '翺', 194938 => '者', 194939 => '𦓚', 194940 => '𦔣', 194941 => '聠', 194942 => '𦖨', 194943 => '聰', 194944 => '𣍟', 194945 => '䏕', 194946 => '育', 194947 => '脃', 194948 => '䐋', 194949 => '脾', 194950 => '媵', 194951 => '𦞧', 194952 => '𦞵', 194953 => '𣎓', 194954 => '𣎜', 194955 => '舁', 194956 => '舄', 194957 => '辞', 194958 => '䑫', 194959 => '芑', 194960 => '芋', 194961 => '芝', 194962 => '劳', 194963 => '花', 194964 => '芳', 194965 => '芽', 194966 => '苦', 194967 => '𦬼', 194968 => '若', 194969 => '茝', 194970 => '荣', 194971 => '莭', 194972 => '茣', 194973 => '莽', 194974 => '菧', 194975 => '著', 194976 => '荓', 194977 => '菊', 194978 => '菌', 194979 => '菜', 194980 => '𦰶', 194981 => '𦵫', 194982 => '𦳕', 194983 => '䔫', 194984 => '蓱', 194985 => '蓳', 194986 => '蔖', 194987 => '𧏊', 194988 => '蕤', 194989 => '𦼬', 194990 => '䕝', 194991 => '䕡', 194992 => '𦾱', 194993 => '𧃒', 194994 => '䕫', 194995 => '虐', 194996 => '虜', 194997 => '虧', 194998 => '虩', 194999 => '蚩', 195000 => '蚈', 195001 => '蜎', 195002 => '蛢', 195003 => '蝹', 195004 => '蜨', 195005 => '蝫', 195006 => '螆', 195008 => '蟡', 195009 => '蠁', 195010 => '䗹', 195011 => '衠', 195012 => '衣', 195013 => '𧙧', 195014 => '裗', 195015 => '裞', 195016 => '䘵', 195017 => '裺', 195018 => '㒻', 195019 => '𧢮', 195020 => '𧥦', 195021 => '䚾', 195022 => '䛇', 195023 => '誠', 195024 => '諭', 195025 => '變', 195026 => '豕', 195027 => '𧲨', 195028 => '貫', 195029 => '賁', 195030 => '贛', 195031 => '起', 195032 => '𧼯', 195033 => '𠠄', 195034 => '跋', 195035 => '趼', 195036 => '跰', 195037 => '𠣞', 195038 => '軔', 195039 => '輸', 195040 => '𨗒', 195041 => '𨗭', 195042 => '邔', 195043 => '郱', 195044 => '鄑', 195045 => '𨜮', 195046 => '鄛', 195047 => '鈸', 195048 => '鋗', 195049 => '鋘', 195050 => '鉼', 195051 => '鏹', 195052 => '鐕', 195053 => '𨯺', 195054 => '開', 195055 => '䦕', 195056 => '閷', 195057 => '𨵷', 195058 => '䧦', 195059 => '雃', 195060 => '嶲', 195061 => '霣', 195062 => '𩅅', 195063 => '𩈚', 195064 => '䩮', 195065 => '䩶', 195066 => '韠', 195067 => '𩐊', 195068 => '䪲', 195069 => '𩒖', 195070 => '頋', 195071 => '頋', 195072 => '頩', 195073 => '𩖶', 195074 => '飢', 195075 => '䬳', 195076 => '餩', 195077 => '馧', 195078 => '駂', 195079 => '駾', 195080 => '䯎', 195081 => '𩬰', 195082 => '鬒', 195083 => '鱀', 195084 => '鳽', 195085 => '䳎', 195086 => '䳭', 195087 => '鵧', 195088 => '𪃎', 195089 => '䳸', 195090 => '𪄅', 195091 => '𪈎', 195092 => '𪊑', 195093 => '麻', 195094 => '䵖', 195095 => '黹', 195096 => '黾', 195097 => '鼅', 195098 => '鼏', 195099 => '鼖', 195100 => '鼻', 195101 => '𪘀', ); true, 847 => true, 6155 => true, 6156 => true, 6157 => true, 8203 => true, 8288 => true, 8292 => true, 65024 => true, 65025 => true, 65026 => true, 65027 => true, 65028 => true, 65029 => true, 65030 => true, 65031 => true, 65032 => true, 65033 => true, 65034 => true, 65035 => true, 65036 => true, 65037 => true, 65038 => true, 65039 => true, 65279 => true, 113824 => true, 113825 => true, 113826 => true, 113827 => true, 917760 => true, 917761 => true, 917762 => true, 917763 => true, 917764 => true, 917765 => true, 917766 => true, 917767 => true, 917768 => true, 917769 => true, 917770 => true, 917771 => true, 917772 => true, 917773 => true, 917774 => true, 917775 => true, 917776 => true, 917777 => true, 917778 => true, 917779 => true, 917780 => true, 917781 => true, 917782 => true, 917783 => true, 917784 => true, 917785 => true, 917786 => true, 917787 => true, 917788 => true, 917789 => true, 917790 => true, 917791 => true, 917792 => true, 917793 => true, 917794 => true, 917795 => true, 917796 => true, 917797 => true, 917798 => true, 917799 => true, 917800 => true, 917801 => true, 917802 => true, 917803 => true, 917804 => true, 917805 => true, 917806 => true, 917807 => true, 917808 => true, 917809 => true, 917810 => true, 917811 => true, 917812 => true, 917813 => true, 917814 => true, 917815 => true, 917816 => true, 917817 => true, 917818 => true, 917819 => true, 917820 => true, 917821 => true, 917822 => true, 917823 => true, 917824 => true, 917825 => true, 917826 => true, 917827 => true, 917828 => true, 917829 => true, 917830 => true, 917831 => true, 917832 => true, 917833 => true, 917834 => true, 917835 => true, 917836 => true, 917837 => true, 917838 => true, 917839 => true, 917840 => true, 917841 => true, 917842 => true, 917843 => true, 917844 => true, 917845 => true, 917846 => true, 917847 => true, 917848 => true, 917849 => true, 917850 => true, 917851 => true, 917852 => true, 917853 => true, 917854 => true, 917855 => true, 917856 => true, 917857 => true, 917858 => true, 917859 => true, 917860 => true, 917861 => true, 917862 => true, 917863 => true, 917864 => true, 917865 => true, 917866 => true, 917867 => true, 917868 => true, 917869 => true, 917870 => true, 917871 => true, 917872 => true, 917873 => true, 917874 => true, 917875 => true, 917876 => true, 917877 => true, 917878 => true, 917879 => true, 917880 => true, 917881 => true, 917882 => true, 917883 => true, 917884 => true, 917885 => true, 917886 => true, 917887 => true, 917888 => true, 917889 => true, 917890 => true, 917891 => true, 917892 => true, 917893 => true, 917894 => true, 917895 => true, 917896 => true, 917897 => true, 917898 => true, 917899 => true, 917900 => true, 917901 => true, 917902 => true, 917903 => true, 917904 => true, 917905 => true, 917906 => true, 917907 => true, 917908 => true, 917909 => true, 917910 => true, 917911 => true, 917912 => true, 917913 => true, 917914 => true, 917915 => true, 917916 => true, 917917 => true, 917918 => true, 917919 => true, 917920 => true, 917921 => true, 917922 => true, 917923 => true, 917924 => true, 917925 => true, 917926 => true, 917927 => true, 917928 => true, 917929 => true, 917930 => true, 917931 => true, 917932 => true, 917933 => true, 917934 => true, 917935 => true, 917936 => true, 917937 => true, 917938 => true, 917939 => true, 917940 => true, 917941 => true, 917942 => true, 917943 => true, 917944 => true, 917945 => true, 917946 => true, 917947 => true, 917948 => true, 917949 => true, 917950 => true, 917951 => true, 917952 => true, 917953 => true, 917954 => true, 917955 => true, 917956 => true, 917957 => true, 917958 => true, 917959 => true, 917960 => true, 917961 => true, 917962 => true, 917963 => true, 917964 => true, 917965 => true, 917966 => true, 917967 => true, 917968 => true, 917969 => true, 917970 => true, 917971 => true, 917972 => true, 917973 => true, 917974 => true, 917975 => true, 917976 => true, 917977 => true, 917978 => true, 917979 => true, 917980 => true, 917981 => true, 917982 => true, 917983 => true, 917984 => true, 917985 => true, 917986 => true, 917987 => true, 917988 => true, 917989 => true, 917990 => true, 917991 => true, 917992 => true, 917993 => true, 917994 => true, 917995 => true, 917996 => true, 917997 => true, 917998 => true, 917999 => true, ); 'ss', 962 => 'σ', 8204 => '', 8205 => '', ); tokens[] = $token; return $this; } public function freeze(): self { return $this; } public function getNext(): Token { if ($this->peeking) { $this->peeking = false; $this->used[] = $this->peeked; return $this->peeked; } if (!isset($this->tokens[$this->cursor])) { throw new InternalErrorException('Unexpected token stream end.'); } return $this->tokens[$this->cursor++]; } public function getPeek(): Token { if (!$this->peeking) { $this->peeked = $this->getNext(); $this->peeking = true; } return $this->peeked; } public function getUsed(): array { return $this->used; } public function getNextIdentifier(): string { $next = $this->getNext(); if (!$next->isIdentifier()) { throw SyntaxErrorException::unexpectedToken('identifier', $next); } return $next->getValue(); } public function getNextIdentifierOrStar(): ?string { $next = $this->getNext(); if ($next->isIdentifier()) { return $next->getValue(); } if ($next->isDelimiter(['*'])) { return null; } throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); } public function skipWhitespace() { $peek = $this->getPeek(); if ($peek->isWhitespace()) { $this->getNext(); } } } handlers = [ new Handler\WhitespaceHandler(), new Handler\IdentifierHandler($patterns, $escaping), new Handler\HashHandler($patterns, $escaping), new Handler\StringHandler($patterns, $escaping), new Handler\NumberHandler($patterns), new Handler\CommentHandler(), ]; } public function tokenize(Reader $reader): TokenStream { $stream = new TokenStream(); while (!$reader->isEOF()) { foreach ($this->handlers as $handler) { if ($handler->handle($reader, $stream)) { continue 2; } } $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); $reader->moveForward(1); } return $stream ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) ->freeze(); } } patterns = $patterns; } public function escapeUnicode(string $value): string { $value = $this->replaceUnicodeSequences($value); return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); } public function escapeUnicodeAndNewLine(string $value): string { $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); return $this->escapeUnicode($value); } private function replaceUnicodeSequences(string $value): string { return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { $c = hexdec($match[1]); if (0x80 > $c %= 0x200000) { return \chr($c); } if (0x800 > $c) { return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); } return ''; }, $value); } } unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; $this->simpleEscapePattern = '\\\\(.)'; $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; $this->nonAsciiPattern = '[^\x00-\x7F]'; $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { return '~^'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { return '~^'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { return '~^'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string { return '~^'.$this->identifierPattern.'~i'; } public function getHashPattern(): string { return '~^'.$this->hashPattern.'~i'; } public function getNumberPattern(): string { return '~^'.$this->numberPattern.'~'; } public function getQuotedStringPattern(string $quote): string { return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; } } source = $source; $this->length = \strlen($source); } public function isEOF(): bool { return $this->position >= $this->length; } public function getPosition(): int { return $this->position; } public function getRemainingLength(): int { return $this->length - $this->position; } public function getSubstring(int $length, int $offset = 0): string { return substr($this->source, $this->position + $offset, $length); } public function getOffset(string $string) { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } public function findPattern(string $pattern) { $source = substr($this->source, $this->position); if (preg_match($pattern, $source, $matches)) { return $matches; } return false; } public function moveForward(int $length) { $this->position += $length; } public function moveToEnd() { $this->position = $this->length; } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $quote = $reader->getSubstring(1); if (!\in_array($quote, ["'", '"'])) { return false; } $reader->moveForward(1); $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); if (!$match) { throw new InternalErrorException(sprintf('Should have found at least an empty match at %d.', $reader->getPosition())); } if (\strlen($match[0]) === $reader->getRemainingLength()) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } if ($quote !== $reader->getSubstring(1, \strlen($match[0]))) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); $reader->moveForward(\strlen($match[0]) + 1); return true; } } patterns = $patterns; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getNumberPattern()); if (!$match) { return false; } $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[0]); $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } findPattern('~^[ \t\r\n\f]+~'); if (false === $match) { return false; } $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } getSubstring(2)) { return false; } $offset = $reader->getOffset('*/'); if (false === $offset) { $reader->moveToEnd(); } else { $reader->moveForward($offset + 2); } return true; } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getHashPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[1]); $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } type = $type; $this->value = $value; $this->position = $position; } public function getType(): ?int { return $this->type; } public function getValue(): ?string { return $this->value; } public function getPosition(): ?int { return $this->position; } public function isFileEnd(): bool { return self::TYPE_FILE_END === $this->type; } public function isDelimiter(array $values = []): bool { if (self::TYPE_DELIMITER !== $this->type) { return false; } if (empty($values)) { return true; } return \in_array($this->value, $values); } public function isWhitespace(): bool { return self::TYPE_WHITESPACE === $this->type; } public function isIdentifier(): bool { return self::TYPE_IDENTIFIER === $this->type; } public function isHash(): bool { return self::TYPE_HASH === $this->type; } public function isNumber(): bool { return self::TYPE_NUMBER === $this->type; } public function isString(): bool { return self::TYPE_STRING === $this->type; } public function __toString(): string { if ($this->value) { return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); } return sprintf('<%s at %s>', $this->type, $this->position); } } tokenizer = $tokenizer ?? new Tokenizer(); } public function parse(string $source): array { $reader = new Reader($source); $stream = $this->tokenizer->tokenize($reader); return $this->parseSelectorList($stream); } public static function parseSeries(array $tokens): array { foreach ($tokens as $token) { if ($token->isString()) { throw SyntaxErrorException::stringAsFunctionArgument(); } } $joined = trim(implode('', array_map(function (Token $token) { return $token->getValue(); }, $tokens))); $int = function ($string) { if (!is_numeric($string)) { throw SyntaxErrorException::stringAsFunctionArgument(); } return (int) $string; }; switch (true) { case 'odd' === $joined: return [2, 1]; case 'even' === $joined: return [2, 0]; case 'n' === $joined: return [1, 0]; case !str_contains($joined, 'n'): return [0, $int($joined)]; } $split = explode('n', $joined); $first = $split[0] ?? null; return [ $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, isset($split[1]) && $split[1] ? $int($split[1]) : 0, ]; } private function parseSelectorList(TokenStream $stream): array { $stream->skipWhitespace(); $selectors = []; while (true) { $selectors[] = $this->parserSelectorNode($stream); if ($stream->getPeek()->isDelimiter([','])) { $stream->getNext(); $stream->skipWhitespace(); } else { break; } } return $selectors; } private function parserSelectorNode(TokenStream $stream): Node\SelectorNode { [$result, $pseudoElement] = $this->parseSimpleSelector($stream); while (true) { $stream->skipWhitespace(); $peek = $stream->getPeek(); if ($peek->isFileEnd() || $peek->isDelimiter([','])) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isDelimiter(['+', '>', '~'])) { $combinator = $stream->getNext()->getValue(); $stream->skipWhitespace(); } else { $combinator = ' '; } [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream); $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); } return new Node\SelectorNode($result, $pseudoElement); } private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array { $stream->skipWhitespace(); $selectorStart = \count($stream->getUsed()); $result = $this->parseElementNode($stream); $pseudoElement = null; while (true) { $peek = $stream->getPeek(); if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter([',', '+', '>', '~']) || ($insideNegation && $peek->isDelimiter([')'])) ) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isHash()) { $result = new Node\HashNode($result, $stream->getNext()->getValue()); } elseif ($peek->isDelimiter(['.'])) { $stream->getNext(); $result = new Node\ClassNode($result, $stream->getNextIdentifier()); } elseif ($peek->isDelimiter(['['])) { $stream->getNext(); $result = $this->parseAttributeNode($result, $stream); } elseif ($peek->isDelimiter([':'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter([':'])) { $stream->getNext(); $pseudoElement = $stream->getNextIdentifier(); continue; } $identifier = $stream->getNextIdentifier(); if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) { $pseudoElement = $identifier; continue; } if (!$stream->getPeek()->isDelimiter(['('])) { $result = new Node\PseudoNode($result, $identifier); continue; } $stream->getNext(); $stream->skipWhitespace(); if ('not' === strtolower($identifier)) { if ($insideNegation) { throw SyntaxErrorException::nestedNot(); } [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true); $next = $stream->getNext(); if (null !== $argumentPseudoElement) { throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); } if (!$next->isDelimiter([')'])) { throw SyntaxErrorException::unexpectedToken('")"', $next); } $result = new Node\NegationNode($result, $argument); } else { $arguments = []; $next = null; while (true) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isIdentifier() || $next->isString() || $next->isNumber() || $next->isDelimiter(['+', '-']) ) { $arguments[] = $next; } elseif ($next->isDelimiter([')'])) { break; } else { throw SyntaxErrorException::unexpectedToken('an argument', $next); } } if (empty($arguments)) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } $result = new Node\FunctionNode($result, $identifier, $arguments); } } else { throw SyntaxErrorException::unexpectedToken('selector', $peek); } } if (\count($stream->getUsed()) === $selectorStart) { throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); } return [$result, $pseudoElement]; } private function parseElementNode(TokenStream $stream): Node\ElementNode { $peek = $stream->getPeek(); if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) { if ($peek->isIdentifier()) { $namespace = $stream->getNext()->getValue(); } else { $stream->getNext(); $namespace = null; } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); $element = $stream->getNextIdentifierOrStar(); } else { $element = $namespace; $namespace = null; } } else { $element = $namespace = null; } return new Node\ElementNode($namespace, $element); } private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream): Node\AttributeNode { $stream->skipWhitespace(); $attribute = $stream->getNextIdentifierOrStar(); if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) { throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter(['='])) { $namespace = null; $stream->getNext(); $operator = '|='; } else { $namespace = $attribute; $attribute = $stream->getNextIdentifier(); $operator = null; } } else { $namespace = $operator = null; } if (null === $operator) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isDelimiter([']'])) { return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); } elseif ($next->isDelimiter(['='])) { $operator = '='; } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!']) && $stream->getPeek()->isDelimiter(['=']) ) { $operator = $next->getValue().'='; $stream->getNext(); } else { throw SyntaxErrorException::unexpectedToken('operator', $next); } } $stream->skipWhitespace(); $value = $stream->getNext(); if ($value->isNumber()) { $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); } if (!($value->isIdentifier() || $value->isString())) { throw SyntaxErrorException::unexpectedToken('string or identifier', $value); } $stream->skipWhitespace(); $next = $stream->getNext(); if (!$next->isDelimiter([']'])) { throw SyntaxErrorException::unexpectedToken('"]"', $next); } return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); } } translator = new Translator(); if ($html) { $this->translator->registerExtension(new HtmlExtension($this->translator)); } $this->translator ->registerParserShortcut(new EmptyStringParser()) ->registerParserShortcut(new ElementParser()) ->registerParserShortcut(new ClassParser()) ->registerParserShortcut(new HashParser()) ; } public function toXPath($cssExpr, $prefix = 'descendant-or-self::') { return $this->translator->cssToXPath($cssExpr, $prefix); } } selector = $selector; $this->name = strtolower($name); $this->arguments = $arguments; } public function getSelector(): NodeInterface { return $this->selector; } public function getName(): string { return $this->name; } public function getArguments(): array { return $this->arguments; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { $arguments = implode(', ', array_map(function (Token $token) { return "'".$token->getValue()."'"; }, $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } } tree = $tree; $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; } public function getTree(): NodeInterface { return $this->tree; } public function getPseudoElement(): ?string { return $this->pseudoElement; } public function getSpecificity(): Specificity { return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); } public function __toString(): string { return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); } } selector = $selector; $this->name = $name; } public function getSelector(): NodeInterface { return $this->selector; } public function getName(): string { return $this->name; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); } } namespace = $namespace; $this->element = $element; } public function getNamespace(): ?string { return $this->namespace; } public function getElement(): ?string { return $this->element; } public function getSpecificity(): Specificity { return new Specificity(0, 0, $this->element ? 1 : 0); } public function __toString(): string { $element = $this->element ?: '*'; return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); } } selector = $selector; $this->namespace = $namespace; $this->attribute = $attribute; $this->operator = $operator; $this->value = $value; } public function getSelector(): NodeInterface { return $this->selector; } public function getNamespace(): ?string { return $this->namespace; } public function getAttribute(): string { return $this->attribute; } public function getOperator(): string { return $this->operator; } public function getValue(): ?string { return $this->value; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; return 'exists' === $this->operator ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); } } a = $a; $this->b = $b; $this->c = $c; } public function plus(self $specificity): self { return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); } public function getValue(): int { return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; } public function compareTo(self $specificity): int { if ($this->a !== $specificity->a) { return $this->a > $specificity->a ? 1 : -1; } if ($this->b !== $specificity->b) { return $this->b > $specificity->b ? 1 : -1; } if ($this->c !== $specificity->c) { return $this->c > $specificity->c ? 1 : -1; } return 0; } } selector = $selector; $this->subSelector = $subSelector; } public function getSelector(): NodeInterface { return $this->selector; } public function getSubSelector(): NodeInterface { return $this->subSelector; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString(): string { return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); } } nodeName) { $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); } return $this->nodeName; } } selector = $selector; $this->combinator = $combinator; $this->subSelector = $subSelector; } public function getSelector(): NodeInterface { return $this->selector; } public function getCombinator(): string { return $this->combinator; } public function getSubSelector(): NodeInterface { return $this->subSelector; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString(): string { $combinator = ' ' === $this->combinator ? '' : $this->combinator; return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); } } selector = $selector; $this->identifier = strtolower($identifier); } public function getSelector(): NodeInterface { return $this->selector; } public function getIdentifier(): string { return $this->identifier; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); } } selector = $selector; $this->id = $id; } public function getSelector(): NodeInterface { return $this->selector; } public function getId(): string { return $this->id; } public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); } public function __toString(): string { return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); } } mainParser = $parser ?? new Parser(); $this ->registerExtension(new Extension\NodeExtension()) ->registerExtension(new Extension\CombinationExtension()) ->registerExtension(new Extension\FunctionExtension()) ->registerExtension(new Extension\PseudoClassExtension()) ->registerExtension(new Extension\AttributeMatchingExtension()) ; } public static function getXpathLiteral(string $element): string { if (!str_contains($element, "'")) { return "'".$element."'"; } if (!str_contains($element, '"')) { return '"'.$element.'"'; } $string = $element; $parts = []; while (true) { if (false !== $pos = strpos($string, "'")) { $parts[] = sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { $parts[] = "'$string'"; break; } } return sprintf('concat(%s)', implode(', ', $parts)); } public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { $selectors = $this->parseSelectors($cssExpr); foreach ($selectors as $index => $selector) { if (null !== $selector->getPseudoElement()) { throw new ExpressionErrorException('Pseudo-elements are not supported.'); } $selectors[$index] = $this->selectorToXPath($selector, $prefix); } return implode(' | ', $selectors); } public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string { return ($prefix ?: '').$this->nodeToXPath($selector); } public function registerExtension(Extension\ExtensionInterface $extension): self { $this->extensions[$extension->getName()] = $extension; $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); return $this; } public function getExtension(string $name): Extension\ExtensionInterface { if (!isset($this->extensions[$name])) { throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); } return $this->extensions[$name]; } public function registerParserShortcut(ParserInterface $shortcut): self { $this->shortcutParsers[] = $shortcut; return $this; } public function nodeToXPath(NodeInterface $node): XPathExpr { if (!isset($this->nodeTranslators[$node->getNodeName()])) { throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); } return $this->nodeTranslators[$node->getNodeName()]($node, $this); } public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr { if (!isset($this->combinationTranslators[$combiner])) { throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); } return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); } public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr { if (!isset($this->functionTranslators[$function->getName()])) { throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); } return $this->functionTranslators[$function->getName()]($xpath, $function); } public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr { if (!isset($this->pseudoClassTranslators[$pseudoClass])) { throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); } return $this->pseudoClassTranslators[$pseudoClass]($xpath); } public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, $value): XPathExpr { if (!isset($this->attributeMatchingTranslators[$operator])) { throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); } return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value); } private function parseSelectors(string $css): array { foreach ($this->shortcutParsers as $shortcut) { $tokens = $shortcut->parse($css); if (!empty($tokens)) { return $tokens; } } return $this->mainParser->parse($css); } } [$this, 'translateExists'], '=' => [$this, 'translateEquals'], '~=' => [$this, 'translateIncludes'], '|=' => [$this, 'translateDashMatch'], '^=' => [$this, 'translatePrefixMatch'], '$=' => [$this, 'translateSuffixMatch'], '*=' => [$this, 'translateSubstringMatch'], '!=' => [$this, 'translateDifferent'], ]; } public function translateExists(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($attribute); } public function translateEquals(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); } public function translateIncludes(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', $attribute, Translator::getXpathLiteral(' '.$value.' ') ) : '0'); } public function translateDashMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf( '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', $attribute, Translator::getXpathLiteral($value), Translator::getXpathLiteral($value.'-') )); } public function translatePrefixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and starts-with(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateSuffixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', $attribute, \strlen($value) - 1, Translator::getXpathLiteral($value) ) : '0'); } public function translateSubstringMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and contains(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateDifferent(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf( $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', $attribute, Translator::getXpathLiteral($value) )); } public function getName(): string { return 'attribute-matching'; } } [$this, 'translateNthChild'], 'nth-last-child' => [$this, 'translateNthLastChild'], 'nth-of-type' => [$this, 'translateNthOfType'], 'nth-last-of-type' => [$this, 'translateNthLastOfType'], 'contains' => [$this, 'translateContains'], 'lang' => [$this, 'translateLang'], ]; } public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr { try { [$a, $b] = Parser::parseSeries($function->getArguments()); } catch (SyntaxErrorException $e) { throw new ExpressionErrorException(sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e); } $xpath->addStarPrefix(); if ($addNameTest) { $xpath->addNameTest(); } if (0 === $a) { return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); } if ($a < 0) { if ($b < 1) { return $xpath->addCondition('false()'); } $sign = '<='; } else { $sign = '>='; } $expr = 'position()'; if ($last) { $expr = 'last() - '.$expr; --$b; } if (0 !== $b) { $expr .= ' - '.$b; } $conditions = [sprintf('%s %s 0', $expr, $sign)]; if (1 !== $a && -1 !== $a) { $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); } return $xpath->addCondition(implode(' and ', $conditions)); } public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr { return $this->translateNthChild($xpath, $function, true); } public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr { return $this->translateNthChild($xpath, $function, false, false); } public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); } return $this->translateNthChild($xpath, $function, true, false); } public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'contains(string(.), %s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'lang(%s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } public function getName(): string { return 'function'; } } getExtension('node') ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); } public function getPseudoClassTranslators(): array { return [ 'checked' => [$this, 'translateChecked'], 'link' => [$this, 'translateLink'], 'disabled' => [$this, 'translateDisabled'], 'enabled' => [$this, 'translateEnabled'], 'selected' => [$this, 'translateSelected'], 'invalid' => [$this, 'translateInvalid'], 'hover' => [$this, 'translateHover'], 'visited' => [$this, 'translateVisited'], ]; } public function getFunctionTranslators(): array { return [ 'lang' => [$this, 'translateLang'], ]; } public function translateChecked(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(@checked ' ."and (name(.) = 'input' or name(.) = 'command')" ."and (@type = 'checkbox' or @type = 'radio'))" ); } public function translateLink(XPathExpr $xpath): XPathExpr { return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); } public function translateDisabled(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(' .'@disabled and' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" ." or name(.) = 'option'" .')' .') or (' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" .')' .' and ancestor::fieldset[@disabled]' ); } public function translateEnabled(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(' .'@href and (' ."name(.) = 'a'" ." or name(.) = 'link'" ." or name(.) = 'area'" .')' .') or (' .'(' ."name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" .')' .' and not(@disabled)' .') or (' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'keygen'" .')' .' and not (@disabled or ancestor::fieldset[@disabled])' .') or (' ."name(.) = 'option' and not(" .'@disabled or ancestor::optgroup[@disabled]' .')' .')' ); } public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'ancestor-or-self::*[@lang][1][starts-with(concat(' ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" .', %s)]', 'lang', Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') )); } public function translateSelected(XPathExpr $xpath): XPathExpr { return $xpath->addCondition("(@selected and name(.) = 'option')"); } public function translateInvalid(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } public function translateHover(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } public function translateVisited(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } public function getName(): string { return 'html'; } } flags = $flags; } public function setFlag(int $flag, bool $on): self { if ($on && !$this->hasFlag($flag)) { $this->flags += $flag; } if (!$on && $this->hasFlag($flag)) { $this->flags -= $flag; } return $this; } public function hasFlag(int $flag): bool { return (bool) ($this->flags & $flag); } public function getNodeTranslators(): array { return [ 'Selector' => [$this, 'translateSelector'], 'CombinedSelector' => [$this, 'translateCombinedSelector'], 'Negation' => [$this, 'translateNegation'], 'Function' => [$this, 'translateFunction'], 'Pseudo' => [$this, 'translatePseudo'], 'Attribute' => [$this, 'translateAttribute'], 'Class' => [$this, 'translateClass'], 'Hash' => [$this, 'translateHash'], 'Element' => [$this, 'translateElement'], ]; } public function translateSelector(Node\SelectorNode $node, Translator $translator): XPathExpr { return $translator->nodeToXPath($node->getTree()); } public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator): XPathExpr { return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); } public function translateNegation(Node\NegationNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); $subXpath = $translator->nodeToXPath($node->getSubSelector()); $subXpath->addNameTest(); if ($subXpath->getCondition()) { return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); } return $xpath->addCondition('0'); } public function translateFunction(Node\FunctionNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addFunction($xpath, $node); } public function translatePseudo(Node\PseudoNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addPseudoClass($xpath, $node->getIdentifier()); } public function translateAttribute(Node\AttributeNode $node, Translator $translator): XPathExpr { $name = $node->getAttribute(); $safe = $this->isSafeName($name); if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { $name = strtolower($name); } if ($node->getNamespace()) { $name = sprintf('%s:%s', $node->getNamespace(), $name); $safe = $safe && $this->isSafeName($node->getNamespace()); } $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); $value = $node->getValue(); $xpath = $translator->nodeToXPath($node->getSelector()); if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { $value = strtolower($value); } return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); } public function translateClass(Node\ClassNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); } public function translateHash(Node\HashNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); } public function translateElement(Node\ElementNode $node): XPathExpr { $element = $node->getElement(); if ($element && $this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { $element = strtolower($element); } if ($element) { $safe = $this->isSafeName($element); } else { $element = '*'; $safe = true; } if ($node->getNamespace()) { $element = sprintf('%s:%s', $node->getNamespace(), $element); $safe = $safe && $this->isSafeName($node->getNamespace()); } $xpath = new XPathExpr('', $element); if (!$safe) { $xpath->addNameTest(); } return $xpath; } public function getName(): string { return 'node'; } private function isSafeName(string $name): bool { return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); } } [$this, 'translateDescendant'], '>' => [$this, 'translateChild'], '+' => [$this, 'translateDirectAdjacent'], '~' => [$this, 'translateIndirectAdjacent'], ]; } public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/descendant-or-self::*/', $combinedXpath); } public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/', $combinedXpath); } public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath ->join('/following-sibling::', $combinedXpath) ->addNameTest() ->addCondition('position() = 1'); } public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/following-sibling::', $combinedXpath); } public function getName(): string { return 'combination'; } } [$this, 'translateRoot'], 'first-child' => [$this, 'translateFirstChild'], 'last-child' => [$this, 'translateLastChild'], 'first-of-type' => [$this, 'translateFirstOfType'], 'last-of-type' => [$this, 'translateLastOfType'], 'only-child' => [$this, 'translateOnlyChild'], 'only-of-type' => [$this, 'translateOnlyOfType'], 'empty' => [$this, 'translateEmpty'], ]; } public function translateRoot(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('not(parent::*)'); } public function translateFirstChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = 1'); } public function translateLastChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = last()'); } public function translateFirstOfType(XPathExpr $xpath): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = 1'); } public function translateLastOfType(XPathExpr $xpath): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = last()'); } public function translateOnlyChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('last() = 1'); } public function translateOnlyOfType(XPathExpr $xpath): XPathExpr { $element = $xpath->getElement(); return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element)); } public function translateEmpty(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('not(*) and not(string-length())'); } public function getName(): string { return 'pseudo-class'; } } path = $path; $this->element = $element; $this->condition = $condition; if ($starPrefix) { $this->addStarPrefix(); } } public function getElement(): string { return $this->element; } public function addCondition(string $condition): self { $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; return $this; } public function getCondition(): string { return $this->condition; } public function addNameTest(): self { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); $this->element = '*'; } return $this; } public function addStarPrefix(): self { $this->path .= '*/'; return $this; } public function join(string $combiner, self $expr): self { $path = $this->__toString().$combiner; if ('*/' !== $expr->path) { $path .= $expr->path; } $this->path = $path; $this->element = $expr->element; $this->condition = $expr->condition; return $this; } public function __toString(): string { $path = $this->path.$this->element; $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; return $path.$condition; } } = 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); } } 255) { return (string) $int; } if (\PHP_VERSION_ID >= 80100) { @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); } if ($int < 0) { $int += 256; } return \chr($int); } } = 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)); } } setServerParameters($server); $this->history = $history ?? new History(); $this->cookieJar = $cookieJar ?? new CookieJar(); } public function followRedirects($followRedirect = true) { $this->followRedirects = (bool) $followRedirect; } public function followMetaRefresh(bool $followMetaRefresh = true) { $this->followMetaRefresh = $followMetaRefresh; } public function isFollowingRedirects() { return $this->followRedirects; } public function setMaxRedirects($maxRedirects) { $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; $this->followRedirects = -1 != $this->maxRedirects; } public function getMaxRedirects() { return $this->maxRedirects; } public function insulate($insulated = true) { if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) { throw new \LogicException('Unable to isolate requests as the Symfony Process Component is not installed.'); } $this->insulated = (bool) $insulated; } public function setServerParameters(array $server) { $this->server = array_merge([ 'HTTP_USER_AGENT' => 'Symfony BrowserKit', ], $server); } public function setServerParameter($key, $value) { $this->server[$key] = $value; } public function getServerParameter($key, $default = '') { return $this->server[$key] ?? $default; } public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true): Crawler { $this->setServerParameter('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'); try { return $this->request($method, $uri, $parameters, $files, $server, $content, $changeHistory); } finally { unset($this->server['HTTP_X_REQUESTED_WITH']); } } public function getHistory() { return $this->history; } public function getCookieJar() { return $this->cookieJar; } public function getCrawler() { if (null === $this->crawler) { @trigger_error(sprintf('Calling the "%s()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } return $this->crawler; } public function getInternalResponse() { if (null === $this->internalResponse) { @trigger_error(sprintf('Calling the "%s()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } return $this->internalResponse; } public function getResponse() { if (null === $this->response) { @trigger_error(sprintf('Calling the "%s()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } return $this->response; } public function getInternalRequest() { if (null === $this->internalRequest) { @trigger_error(sprintf('Calling the "%s()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } return $this->internalRequest; } public function getRequest() { if (null === $this->request) { @trigger_error(sprintf('Calling the "%s()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } return $this->request; } public function click(Link $link) { if ($link instanceof Form) { return $this->submit($link); } return $this->request($link->getMethod(), $link->getUri()); } public function clickLink(string $linkText): Crawler { if (null === $this->crawler) { throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__)); } return $this->click($this->crawler->selectLink($linkText)->link()); } public function submit(Form $form, array $values = []) { if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { @trigger_error(sprintf('The "%s()" method will have a new "array $serverParameters = []" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', static::class.'::'.__FUNCTION__), \E_USER_DEPRECATED); } $form->setValues($values); $serverParameters = 2 < \func_num_args() ? func_get_arg(2) : []; return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles(), $serverParameters); } public function submitForm(string $button, array $fieldValues = [], string $method = 'POST', array $serverParameters = []): Crawler { if (null === $this->crawler) { throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__)); } $buttonNode = $this->crawler->selectButton($button); $form = $buttonNode->form($fieldValues, $method); return $this->submit($form, [], $serverParameters); } public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true) { if ($this->isMainRequest) { $this->redirectCount = 0; } else { ++$this->redirectCount; } $originalUri = $uri; $uri = $this->getAbsoluteUri($uri); $server = array_merge($this->server, $server); if (!empty($server['HTTP_HOST']) && null === parse_url($originalUri, \PHP_URL_HOST)) { $uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri); } if (isset($server['HTTPS']) && null === parse_url($originalUri, \PHP_URL_SCHEME)) { $uri = preg_replace('{^'.parse_url($uri, \PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri); } if (!isset($server['HTTP_REFERER']) && !$this->history->isEmpty()) { $server['HTTP_REFERER'] = $this->history->current()->getUri(); } if (empty($server['HTTP_HOST'])) { $server['HTTP_HOST'] = $this->extractHost($uri); } $server['HTTPS'] = 'https' == parse_url($uri, \PHP_URL_SCHEME); $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); $this->request = $this->filterRequest($this->internalRequest); if (true === $changeHistory) { $this->history->add($this->internalRequest); } if ($this->insulated) { $this->response = $this->doRequestInProcess($this->request); } else { $this->response = $this->doRequest($this->request); } $this->internalResponse = $this->filterResponse($this->response); $this->cookieJar->updateFromResponse($this->internalResponse, $uri); $status = $this->internalResponse->getStatusCode(); if ($status >= 300 && $status < 400) { $this->redirect = $this->internalResponse->getHeader('Location'); } else { $this->redirect = null; } if ($this->followRedirects && $this->redirect) { $this->redirects[serialize($this->history->current())] = true; return $this->crawler = $this->followRedirect(); } $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) { $this->redirect = $redirect; $this->redirects[serialize($this->history->current())] = true; $this->crawler = $this->followRedirect(); } return $this->crawler; } protected function doRequestInProcess($request) { $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec'); putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile); $_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile; $process = new PhpProcess($this->getScript($request), null, null); $process->run(); if (file_exists($deprecationsFile)) { $deprecations = file_get_contents($deprecationsFile); unlink($deprecationsFile); foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) { if ($deprecation[0]) { trigger_error($deprecation[1], \E_USER_DEPRECATED); } else { @trigger_error($deprecation[1], \E_USER_DEPRECATED); } } } if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) { throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s.', $process->getOutput(), $process->getErrorOutput())); } return unserialize($process->getOutput()); } abstract protected function doRequest($request); protected function getScript($request) { throw new \LogicException('To insulate requests, you need to override the getScript() method.'); } protected function filterRequest(Request $request) { return $request; } protected function filterResponse($response) { return $response; } protected function createCrawlerFromContent($uri, $content, $type) { if (!class_exists(Crawler::class)) { return null; } $crawler = new Crawler(null, $uri); $crawler->addContent($content, $type); return $crawler; } public function back() { do { $request = $this->history->back(); } while (\array_key_exists(serialize($request), $this->redirects)); return $this->requestFromRequest($request, false); } public function forward() { do { $request = $this->history->forward(); } while (\array_key_exists(serialize($request), $this->redirects)); return $this->requestFromRequest($request, false); } public function reload() { return $this->requestFromRequest($this->history->current(), false); } public function followRedirect() { if (empty($this->redirect)) { throw new \LogicException('The request was not redirected.'); } if (-1 !== $this->maxRedirects) { if ($this->redirectCount > $this->maxRedirects) { $this->redirectCount = 0; throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); } } $request = $this->internalRequest; if (\in_array($this->internalResponse->getStatusCode(), [301, 302, 303])) { $method = 'GET'; $files = []; $content = null; } else { $method = $request->getMethod(); $files = $request->getFiles(); $content = $request->getContent(); } if ('GET' === strtoupper($method)) { $parameters = []; } else { $parameters = $request->getParameters(); } $server = $request->getServer(); $server = $this->updateServerFromUri($server, $this->redirect); $this->isMainRequest = false; $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content); $this->isMainRequest = true; return $response; } private function getMetaRefreshUrl(): ?string { $metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]'); foreach ($metaRefresh->extract(['content']) as $content) { if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) { return str_replace("\t\r\n", '', rtrim($m[1])); } } return null; } public function restart() { $this->cookieJar->clear(); $this->history->clear(); } protected function getAbsoluteUri($uri) { if (str_starts_with($uri, 'http://') || str_starts_with($uri, 'https://')) { return $uri; } if (!$this->history->isEmpty()) { $currentUri = $this->history->current()->getUri(); } else { $currentUri = sprintf('http%s://%s/', isset($this->server['HTTPS']) ? 's' : '', $this->server['HTTP_HOST'] ?? 'localhost' ); } if ('' !== trim($uri, '/') && str_starts_with($uri, '//')) { return parse_url($currentUri, \PHP_URL_SCHEME).':'.$uri; } if (!$uri || '#' == $uri[0] || '?' == $uri[0]) { return preg_replace('/[#?].*?$/', '', $currentUri).$uri; } if ('/' !== $uri[0]) { $path = parse_url($currentUri, \PHP_URL_PATH); if (!str_ends_with($path, '/')) { $path = substr($path, 0, strrpos($path, '/') + 1); } $uri = $path.$uri; } return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; } protected function requestFromRequest(Request $request, $changeHistory = true) { return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory); } private function updateServerFromUri(array $server, string $uri): array { $server['HTTP_HOST'] = $this->extractHost($uri); $scheme = parse_url($uri, \PHP_URL_SCHEME); $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme; unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']); return $server; } private function extractHost(string $uri): ?string { $host = parse_url($uri, \PHP_URL_HOST); if ($port = parse_url($uri, \PHP_URL_PORT)) { return $host.':'.$port; } return $host; } } name = $name; $this->path = $path; $this->domain = $domain; $this->value = $value; $this->raw = $raw; } public function toString(): string { $str = sprintf('has cookie "%s"', $this->name); if ('/' !== $this->path) { $str .= sprintf(' with path "%s"', $this->path); } if ($this->domain) { $str .= sprintf(' for domain "%s"', $this->domain); } $str .= sprintf(' with %svalue "%s"', $this->raw ? 'raw ' : '', $this->value); return $str; } protected function matches($browser): bool { $cookie = $browser->getCookieJar()->get($this->name, $this->path, $this->domain); if (!$cookie) { return false; } return $this->value === ($this->raw ? $cookie->getRawValue() : $cookie->getValue()); } protected function failureDescription($browser): string { return 'the Browser '.$this->toString(); } } name = $name; $this->path = $path; $this->domain = $domain; } public function toString(): string { $str = sprintf('has cookie "%s"', $this->name); if ('/' !== $this->path) { $str .= sprintf(' with path "%s"', $this->path); } if ($this->domain) { $str .= sprintf(' for domain "%s"', $this->domain); } return $str; } protected function matches($browser): bool { return null !== $browser->getCookieJar()->get($this->name, $this->path, $this->domain); } protected function failureDescription($browser): string { return 'the Browser '.$this->toString(); } } cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; } public function get($name, $path = '/', $domain = null) { $this->flushExpiredCookies(); foreach ($this->cookieJar as $cookieDomain => $pathCookies) { if ($cookieDomain && $domain) { $cookieDomain = '.'.ltrim($cookieDomain, '.'); if (!str_ends_with('.'.$domain, $cookieDomain)) { continue; } } foreach ($pathCookies as $cookiePath => $namedCookies) { if (!str_starts_with($path, $cookiePath)) { continue; } if (isset($namedCookies[$name])) { return $namedCookies[$name]; } } } return null; } public function expire($name, $path = '/', $domain = null) { if (null === $path) { $path = '/'; } if (empty($domain)) { $domains = array_keys($this->cookieJar); } else { $domains = [$domain]; } foreach ($domains as $domain) { unset($this->cookieJar[$domain][$path][$name]); if (empty($this->cookieJar[$domain][$path])) { unset($this->cookieJar[$domain][$path]); if (empty($this->cookieJar[$domain])) { unset($this->cookieJar[$domain]); } } } } public function clear() { $this->cookieJar = []; } public function updateFromSetCookie(array $setCookies, $uri = null) { $cookies = []; foreach ($setCookies as $cookie) { foreach (explode(',', $cookie) as $i => $part) { if (0 === $i || preg_match('/^(?P\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) { $cookies[] = ltrim($part); } else { $cookies[\count($cookies) - 1] .= ','.$part; } } } foreach ($cookies as $cookie) { try { $this->set(Cookie::fromString($cookie, $uri)); } catch (\InvalidArgumentException $e) { } } } public function updateFromResponse(Response $response, $uri = null) { $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); } public function all() { $this->flushExpiredCookies(); $flattenedCookies = []; foreach ($this->cookieJar as $path) { foreach ($path as $cookies) { foreach ($cookies as $cookie) { $flattenedCookies[] = $cookie; } } } return $flattenedCookies; } public function allValues($uri, $returnsRawValue = false) { $this->flushExpiredCookies(); $parts = array_replace(['path' => '/'], parse_url($uri)); $cookies = []; foreach ($this->cookieJar as $domain => $pathCookies) { if ($domain) { $domain = '.'.ltrim($domain, '.'); if ($domain != substr('.'.$parts['host'], -\strlen($domain))) { continue; } } foreach ($pathCookies as $path => $namedCookies) { if ($path != substr($parts['path'], 0, \strlen($path))) { continue; } foreach ($namedCookies as $cookie) { if ($cookie->isSecure() && 'https' != $parts['scheme']) { continue; } $cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue(); } } } return $cookies; } public function allRawValues($uri) { return $this->allValues($uri, true); } public function flushExpiredCookies() { foreach ($this->cookieJar as $domain => $pathCookies) { foreach ($pathCookies as $path => $namedCookies) { foreach ($namedCookies as $name => $cookie) { if ($cookie->isExpired()) { unset($this->cookieJar[$domain][$path][$name]); } } } } } } value = urldecode($value); $this->rawValue = $value; } else { $this->value = $value; $this->rawValue = rawurlencode($value ?? ''); } $this->name = $name; $this->path = empty($path) ? '/' : $path; $this->domain = $domain; $this->secure = $secure; $this->httponly = $httponly; $this->samesite = $samesite; if (null !== $expires) { $timestampAsDateTime = \DateTime::createFromFormat('U', $expires); if (false === $timestampAsDateTime) { throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires)); } $this->expires = $timestampAsDateTime->format('U'); } } public function __toString() { $cookie = sprintf('%s=%s', $this->name, $this->rawValue); if (null !== $this->expires) { $dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT')); $cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::DATE_FORMATS[0])); } if ('' !== $this->domain) { $cookie .= '; domain='.$this->domain; } if ($this->path) { $cookie .= '; path='.$this->path; } if ($this->secure) { $cookie .= '; secure'; } if ($this->httponly) { $cookie .= '; httponly'; } if (null !== $this->samesite) { $cookie .= '; samesite='.$this->samesite; } return $cookie; } public static function fromString($cookie, $url = null) { $parts = explode(';', $cookie); if (!str_contains($parts[0], '=')) { throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0])); } [$name, $value] = explode('=', array_shift($parts), 2); $values = [ 'name' => trim($name), 'value' => trim($value), 'expires' => null, 'path' => '/', 'domain' => '', 'secure' => false, 'httponly' => false, 'passedRawValue' => true, 'samesite' => null, ]; if (null !== $url) { if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) { throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); } $values['domain'] = $urlParts['host']; $values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : ''; } foreach ($parts as $part) { $part = trim($part); if ('secure' === strtolower($part)) { if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { continue; } $values['secure'] = true; continue; } if ('httponly' === strtolower($part)) { $values['httponly'] = true; continue; } if (2 === \count($elements = explode('=', $part, 2))) { if ('expires' === strtolower($elements[0])) { $elements[1] = self::parseDate($elements[1]); } $values[strtolower($elements[0])] = $elements[1]; } } return new static( $values['name'], $values['value'], $values['expires'], $values['path'], $values['domain'], $values['secure'], $values['httponly'], $values['passedRawValue'], $values['samesite'] ); } private static function parseDate(string $dateValue): ?string { if (($length = \strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) { $dateValue = substr($dateValue, 1, -1); } foreach (self::DATE_FORMATS as $dateFormat) { if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { return $date->format('U'); } } if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) { return $date->format('U'); } return null; } public function getName() { return $this->name; } public function getValue() { return $this->value; } public function getRawValue() { return $this->rawValue; } public function getExpiresTime() { return $this->expires; } public function getPath() { return $this->path; } public function getDomain() { return $this->domain; } public function isSecure() { return $this->secure; } public function isHttpOnly() { return $this->httponly; } public function isExpired() { return null !== $this->expires && 0 != $this->expires && $this->expires <= time(); } public function getSameSite(): ?string { return $this->samesite; } } uri = $uri; $this->method = $method; $this->parameters = $parameters; $this->files = $files; $this->cookies = $cookies; $this->server = $server; $this->content = $content; } public function getUri() { return $this->uri; } public function getMethod() { return $this->method; } public function getParameters() { return $this->parameters; } public function getFiles() { return $this->files; } public function getCookies() { return $this->cookies; } public function getServer() { return $this->server; } public function getContent() { return $this->content; } } client = $client ?? HttpClient::create(); parent::__construct([], $history, $cookieJar); } protected function doRequest($request): Response { $headers = $this->getHeaders($request); [$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers); $response = $this->client->request($request->getMethod(), $request->getUri(), [ 'headers' => array_merge($headers, $extraHeaders), 'body' => $body, 'max_redirects' => 0, ]); return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false)); } private function getBodyAndExtraHeaders(Request $request, array $headers): array { if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { return ['', []]; } if (!class_exists(AbstractPart::class)) { throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".'); } if (null !== $content = $request->getContent()) { if (isset($headers['content-type'])) { return [$content, []]; } $part = new TextPart($content, 'utf-8', 'plain', '8bit'); return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()]; } $fields = $request->getParameters(); if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) { $part = new FormDataPart(array_replace_recursive($fields, $uploadedFiles)); return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()]; } if (empty($fields)) { return ['', []]; } return [http_build_query($fields, '', '&', \PHP_QUERY_RFC1738), ['Content-Type' => 'application/x-www-form-urlencoded']]; } private function getHeaders(Request $request): array { $headers = []; foreach ($request->getServer() as $key => $value) { $key = strtolower(str_replace('_', '-', $key)); $contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true]; if (str_starts_with($key, 'http-')) { $headers[substr($key, 5)] = $value; } elseif (isset($contentHeaders[$key])) { $headers[$key] = $value; } } $cookies = []; foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) { $cookies[] = $name.'='.$value; } if ($cookies) { $headers['cookie'] = implode('; ', $cookies); } return $headers; } private function getUploadedFiles(array $files): array { $uploadedFiles = []; foreach ($files as $name => $file) { if (!\is_array($file)) { return $uploadedFiles; } if (!isset($file['tmp_name'])) { $uploadedFiles[$name] = $this->getUploadedFiles($file); } if (isset($file['tmp_name'])) { $uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); } } return $uploadedFiles; } } stack = []; $this->position = -1; } public function add(Request $request) { $this->stack = \array_slice($this->stack, 0, $this->position + 1); $this->stack[] = clone $request; $this->position = \count($this->stack) - 1; } public function isEmpty() { return 0 == \count($this->stack); } public function back() { if ($this->position < 1) { throw new \LogicException('You are already on the first page.'); } return clone $this->stack[--$this->position]; } public function forward() { if ($this->position > \count($this->stack) - 2) { throw new \LogicException('You are already on the last page.'); } return clone $this->stack[++$this->position]; } public function current() { if (-1 == $this->position) { throw new \LogicException('The page history is empty.'); } return clone $this->stack[$this->position]; } } content = $content; $this->status = $status; $this->headers = $headers; } public function __toString() { $headers = ''; foreach ($this->headers as $name => $value) { if (\is_string($value)) { $headers .= sprintf("%s: %s\n", $name, $value); } else { foreach ($value as $headerValue) { $headers .= sprintf("%s: %s\n", $name, $headerValue); } } } return $headers."\n".$this->content; } protected function buildHeader($name, $value) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.3.', __METHOD__), \E_USER_DEPRECATED); return sprintf("%s: %s\n", $name, $value); } public function getContent() { return $this->content; } public function getStatus() { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.3, use getStatusCode() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->status; } public function getStatusCode(): int { return $this->status; } public function getHeaders() { return $this->headers; } public function getHeader($header, $first = true) { $normalizedHeader = str_replace('-', '_', strtolower($header)); foreach ($this->headers as $key => $value) { if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) { if ($first) { return \is_array($value) ? (\count($value) ? $value[0] : '') : $value; } return \is_array($value) ? $value : [$value]; } } return $first ? null : []; } } = 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); } } id = $id; $this->text = $text; $this->line = $line; $this->pos = $position; } public function getTokenName(): ?string { if ('UNKNOWN' === $name = token_name($this->id)) { $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; } return $name; } public function is($kind): bool { foreach ((array) $kind as $value) { if (\in_array($value, [$this->id, $this->text], true)) { return true; } } return false; } public function isIgnorable(): bool { return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); } public function __toString(): string { return (string) $this->text; } public static function tokenize(string $code, int $flags = 0): array { $line = 1; $position = 0; $tokens = token_get_all($code, $flags); foreach ($tokens as $index => $token) { if (\is_string($token)) { $id = \ord($token); $text = $token; } else { [$id, $text, $line] = $token; } $tokens[$index] = new static($id, $text, $line, $position); $position += \strlen($text); } return $tokens; } } flags = $flags; } } = 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_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 ''; } $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) { $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; } 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; } case 'ASCII': case 'UTF8': case 'UTF-8': } } self::$encodingList = $encodingList; return true; } public static function mb_strlen($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return \strlen($s); } return @\iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strpos($haystack, $needle, $offset); } $needle = (string) $needle; if ('' === $needle) { if (80000 > \PHP_VERSION_ID) { trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); return false; } return 0; } return \iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strrpos($haystack, $needle, $offset); } if ($offset != (int) $offset) { $offset = 0; } elseif ($offset = (int) $offset) { if ($offset < 0) { if (0 > $offset += self::mb_strlen($needle)) { $haystack = self::mb_substr($haystack, 0, $offset, $encoding); } $offset = 0; } else { $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); } } $pos = '' !== $needle || 80000 > \PHP_VERSION_ID ? \iconv_strrpos($haystack, $needle, $encoding) : self::mb_strlen($haystack, $encoding); return false !== $pos ? $offset + $pos : false; } public static function mb_str_split($string, $split_length = 1, $encoding = null) { if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); return null; } if (1 > $split_length = (int) $split_length) { if (80000 > \PHP_VERSION_ID) { trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); return false; } throw new \ValueError('Argument #2 ($length) must be greater than 0'); } if (null === $encoding) { $encoding = mb_internal_encoding(); } if ('UTF-8' === $encoding = self::getEncoding($encoding)) { $rx = '/('; while (65535 < $split_length) { $rx .= '.{65535}'; $split_length -= 65535; } $rx .= '.{'.$split_length.'})/us'; return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } $result = []; $length = mb_strlen($string, $encoding); for ($i = 0; $i < $length; $i += $split_length) { $result[] = mb_substr($string, $i, $split_length, $encoding); } return $result; } public static function mb_strtolower($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { if (null === $c) { return 'none'; } if (0 === strcasecmp($c, 'none')) { return true; } if (80000 > \PHP_VERSION_ID) { return false; } if (\is_int($c) || 'long' === $c || 'entity' === $c) { return false; } throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); } public static function mb_substr($s, $start, $length = null, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return (string) substr($s, $start, null === $length ? 2147483647 : $length); } if ($start < 0) { $start = \iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } } if (null === $length) { $length = 2147483647; } elseif ($length < 0) { $length = \iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } return (string) \iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { $haystack = 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; } } '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', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𐒰' => '𐓘', '𐒱' => '𐓙', '𐒲' => '𐓚', '𐒳' => '𐓛', '𐒴' => '𐓜', '𐒵' => '𐓝', '𐒶' => '𐓞', '𐒷' => '𐓟', '𐒸' => '𐓠', '𐒹' => '𐓡', '𐒺' => '𐓢', '𐒻' => '𐓣', '𐒼' => '𐓤', '𐒽' => '𐓥', '𐒾' => '𐓦', '𐒿' => '𐓧', '𐓀' => '𐓨', '𐓁' => '𐓩', '𐓂' => '𐓪', '𐓃' => '𐓫', '𐓄' => '𐓬', '𐓅' => '𐓭', '𐓆' => '𐓮', '𐓇' => '𐓯', '𐓈' => '𐓰', '𐓉' => '𐓱', '𐓊' => '𐓲', '𐓋' => '𐓳', '𐓌' => '𐓴', '𐓍' => '𐓵', '𐓎' => '𐓶', '𐓏' => '𐓷', '𐓐' => '𐓸', '𐓑' => '𐓹', '𐓒' => '𐓺', '𐓓' => '𐓻', '𐲀' => '𐳀', '𐲁' => '𐳁', '𐲂' => '𐳂', '𐲃' => '𐳃', '𐲄' => '𐳄', '𐲅' => '𐳅', '𐲆' => '𐳆', '𐲇' => '𐳇', '𐲈' => '𐳈', '𐲉' => '𐳉', '𐲊' => '𐳊', '𐲋' => '𐳋', '𐲌' => '𐳌', '𐲍' => '𐳍', '𐲎' => '𐳎', '𐲏' => '𐳏', '𐲐' => '𐳐', '𐲑' => '𐳑', '𐲒' => '𐳒', '𐲓' => '𐳓', '𐲔' => '𐳔', '𐲕' => '𐳕', '𐲖' => '𐳖', '𐲗' => '𐳗', '𐲘' => '𐳘', '𐲙' => '𐳙', '𐲚' => '𐳚', '𐲛' => '𐳛', '𐲜' => '𐳜', '𐲝' => '𐳝', '𐲞' => '𐳞', '𐲟' => '𐳟', '𐲠' => '𐳠', '𐲡' => '𐳡', '𐲢' => '𐳢', '𐲣' => '𐳣', '𐲤' => '𐳤', '𐲥' => '𐳥', '𐲦' => '𐳦', '𐲧' => '𐳧', '𐲨' => '𐳨', '𐲩' => '𐳩', '𐲪' => '𐳪', '𐲫' => '𐳫', '𐲬' => '𐳬', '𐲭' => '𐳭', '𐲮' => '𐳮', '𐲯' => '𐳯', '𐲰' => '𐳰', '𐲱' => '𐳱', '𐲲' => '𐳲', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', '𖹀' => '𖹠', '𖹁' => '𖹡', '𖹂' => '𖹢', '𖹃' => '𖹣', '𖹄' => '𖹤', '𖹅' => '𖹥', '𖹆' => '𖹦', '𖹇' => '𖹧', '𖹈' => '𖹨', '𖹉' => '𖹩', '𖹊' => '𖹪', '𖹋' => '𖹫', '𖹌' => '𖹬', '𖹍' => '𖹭', '𖹎' => '𖹮', '𖹏' => '𖹯', '𖹐' => '𖹰', '𖹑' => '𖹱', '𖹒' => '𖹲', '𖹓' => '𖹳', '𖹔' => '𖹴', '𖹕' => '𖹵', '𖹖' => '𖹶', '𖹗' => '𖹷', '𖹘' => '𖹸', '𖹙' => '𖹹', '𖹚' => '𖹺', '𖹛' => '𖹻', '𖹜' => '𖹼', '𖹝' => '𖹽', '𖹞' => '𖹾', '𖹟' => '𖹿', '𞤀' => '𞤢', '𞤁' => '𞤣', '𞤂' => '𞤤', '𞤃' => '𞤥', '𞤄' => '𞤦', '𞤅' => '𞤧', '𞤆' => '𞤨', '𞤇' => '𞤩', '𞤈' => '𞤪', '𞤉' => '𞤫', '𞤊' => '𞤬', '𞤋' => '𞤭', '𞤌' => '𞤮', '𞤍' => '𞤯', '𞤎' => '𞤰', '𞤏' => '𞤱', '𞤐' => '𞤲', '𞤑' => '𞤳', '𞤒' => '𞤴', '𞤓' => '𞤵', '𞤔' => '𞤶', '𞤕' => '𞤷', '𞤖' => '𞤸', '𞤗' => '𞤹', '𞤘' => '𞤺', '𞤙' => '𞤻', '𞤚' => '𞤼', '𞤛' => '𞤽', '𞤜' => '𞤾', '𞤝' => '𞤿', '𞤞' => '𞥀', '𞤟' => '𞥁', '𞤠' => '𞥂', '𞤡' => '𞥃', ); '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ʾ', 'ὐ' => 'Υ̓', 'ὒ' => 'Υ̓̀', 'ὔ' => 'Υ̓́', 'ὖ' => 'Υ̓͂', 'ᾶ' => 'Α͂', 'ῆ' => 'Η͂', 'ῒ' => 'Ϊ̀', 'ΐ' => 'Ϊ́', 'ῖ' => 'Ι͂', 'ῗ' => 'Ϊ͂', 'ῢ' => 'Ϋ̀', 'ΰ' => 'Ϋ́', 'ῤ' => 'Ρ̓', 'ῦ' => 'Υ͂', 'ῧ' => 'Ϋ͂', 'ῶ' => 'Ω͂', 'ᾈ' => 'ἈΙ', 'ᾉ' => 'ἉΙ', 'ᾊ' => 'ἊΙ', 'ᾋ' => 'ἋΙ', 'ᾌ' => 'ἌΙ', 'ᾍ' => 'ἍΙ', 'ᾎ' => 'ἎΙ', 'ᾏ' => 'ἏΙ', 'ᾘ' => 'ἨΙ', 'ᾙ' => 'ἩΙ', 'ᾚ' => 'ἪΙ', 'ᾛ' => 'ἫΙ', 'ᾜ' => 'ἬΙ', 'ᾝ' => 'ἭΙ', 'ᾞ' => 'ἮΙ', 'ᾟ' => 'ἯΙ', 'ᾨ' => 'ὨΙ', 'ᾩ' => 'ὩΙ', 'ᾪ' => 'ὪΙ', 'ᾫ' => 'ὫΙ', 'ᾬ' => 'ὬΙ', 'ᾭ' => 'ὭΙ', 'ᾮ' => 'ὮΙ', 'ᾯ' => 'ὯΙ', 'ᾼ' => 'ΑΙ', 'ῌ' => 'ΗΙ', 'ῼ' => 'ΩΙ', 'ᾲ' => 'ᾺΙ', 'ᾴ' => 'ΆΙ', 'ῂ' => 'ῊΙ', 'ῄ' => 'ΉΙ', 'ῲ' => 'ῺΙ', 'ῴ' => 'ΏΙ', 'ᾷ' => 'Α͂Ι', 'ῇ' => 'Η͂Ι', 'ῷ' => 'Ω͂Ι', ); suffixes = $suffixes; } public function addSuffix($suffix) { $this->suffixes[] = $suffix; } public function find($name, $default = null, array $extraDirs = []) { if (\ini_get('open_basedir')) { $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); $dirs = []; foreach ($searchPath as $path) { 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; } } 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', 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)', 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', 157 => 'Pollable event', 159 => 'Bad syscall', ]; public function __construct($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.'); } if (!\is_array($command)) { @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), \E_USER_DEPRECATED); } $this->commandline = $command; $this->cwd = $cwd; 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; } 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; } public function __sleep() { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { $this->stop(0); } public function __clone() { $this->resetProcessData(); } public function run(callable $callback = null, array $env = []): int { $this->start($callback, $env); return $this->wait(); } public function mustRun(callable $callback = null, array $env = []): self { if (0 !== $this->run($callback, $env)) { throw new ProcessFailedException($this); } return $this; } 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) { $commandline = 'exec '.$commandline; } } else { $commandline = $this->replacePlaceholders($commandline, $env); } $options = ['suppress_errors' => true]; if ('\\' === \DIRECTORY_SEPARATOR) { $options['bypass_shell'] = true; $commandline = $this->prepareWindowsCommandLine($commandline, $env); } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { $descriptors[3] = ['pipe', 'w']; $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; $ptsWorkaround = fopen(__FILE__, 'r'); } $envPairs = []; foreach ($env as $k => $v) { if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { $envPairs[] = $k.'='.$v; } } if (!is_dir($this->cwd)) { throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); } $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $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(); } 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; } 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; } 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); } } public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } public function signal($signal) { $this->doSignal($signal, true); return $this; } public function disableOutput() { if ($this->isRunning()) { throw new RuntimeException('Disabling output while the process is running is not possible.'); } if (null !== $this->idleTimeout) { throw new LogicException('Output can not be disabled while an idle timeout is set.'); } $this->outputDisabled = true; return $this; } public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = false; return $this; } public function isOutputDisabled() { return $this->outputDisabled; } public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { return ''; } return $ret; } 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; } #[\ReturnTypeWillChange] public function getIterator($flags = 0) { $this->readPipesForOutput(__FUNCTION__, false); $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); $blocking = !(self::ITER_NON_BLOCKING & $flags); $yieldOut = !(self::ITER_SKIP_OUT & $flags); $yieldErr = !(self::ITER_SKIP_ERR & $flags); while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { if ($yieldOut) { $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); if (isset($out[0])) { if ($clearOutput) { $this->clearOutput(); } else { $this->incrementalOutputOffset = ftell($this->stdout); } yield self::OUT => $out; } } if ($yieldErr) { $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); if (isset($err[0])) { if ($clearOutput) { $this->clearErrorOutput(); } else { $this->incrementalErrorOutputOffset = ftell($this->stderr); } yield self::ERR => $err; } } if (!$blocking && !isset($out[0]) && !isset($err[0])) { yield self::OUT => ''; } $this->checkTimeout(); $this->readPipesForOutput(__FUNCTION__, $blocking); } } public function clearOutput() { ftruncate($this->stdout, 0); fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { return ''; } return $ret; } 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; } public function clearErrorOutput() { ftruncate($this->stderr, 0); fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } public function getExitCode() { $this->updateStatus(false); return $this->exitcode; } public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { return null; } return self::$exitCodes[$exitcode] ?? 'Unknown error'; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['signaled']; } 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 can not be retrieved.'); } return $this->processInformation['termsig']; } public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return false; } $this->updateStatus(false); return $this->processInformation['running']; } public function isStarted() { return self::STATUS_READY != $this->status; } public function isTerminated() { $this->updateStatus(false); return self::STATUS_TERMINATED == $this->status; } public function getStatus() { $this->updateStatus(false); return $this->status; } public function stop($timeout = 10, $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning()) { $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; } public function addOutput(string $line) { $this->lastOutputTime = microtime(true); fseek($this->stdout, 0, \SEEK_END); fwrite($this->stdout, $line); fseek($this->stdout, $this->incrementalOutputOffset); } public function addErrorOutput(string $line) { $this->lastOutputTime = microtime(true); fseek($this->stderr, 0, \SEEK_END); fwrite($this->stderr, $line); fseek($this->stderr, $this->incrementalErrorOutputOffset); } public function getLastOutputTime(): ?float { return $this->lastOutputTime; } public function getCommandLine() { return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; } public function setCommandLine($commandline) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); $this->commandline = $commandline; return $this; } public function getTimeout() { return $this->timeout; } public function getIdleTimeout() { return $this->idleTimeout; } public function setTimeout($timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } public function setIdleTimeout($timeout) { if (null !== $timeout && $this->outputDisabled) { throw new LogicException('Idle timeout can not be set while the output is disabled.'); } $this->idleTimeout = $this->validateTimeout($timeout); return $this; } public function setTty($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 = (bool) $tty; return $this; } public function isTty() { return $this->tty; } public function setPty($bool) { $this->pty = (bool) $bool; return $this; } public function isPty() { return $this->pty; } public function getWorkingDirectory() { if (null === $this->cwd) { return getcwd() ?: null; } return $this->cwd; } public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } public function getEnv() { return $this->env; } public function setEnv(array $env) { $this->env = $env; return $this; } public function getInput() { return $this->input; } public function setInput($input) { if ($this->isRunning()) { throw new LogicException('Input can not be set while the process is running.'); } $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function inheritEnvironmentVariables($inheritEnv = true) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, env variables are always inherited.', __METHOD__), \E_USER_DEPRECATED); if (!$inheritEnv) { throw new InvalidArgumentException('Not inheriting environment variables is not supported.'); } return $this; } 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); } } 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; } 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); } 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(); } 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); }; } protected function updateStatus($blocking) { if (self::STATUS_STARTED !== $this->status) { return; } $this->processInformation = proc_get_status($this->process); $running = $this->processInformation['running']; $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->isSigchildEnabled()) { $this->processInformation = $this->fallbackStatus + $this->processInformation; } if (!$running) { $this->close(); } } 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'); } private function readPipesForOutput(string $caller, bool $blocking = false) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus($blocking); } 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; } 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; } } } 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']) { $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->isSigchildEnabled()) { $this->processInformation['signaled'] = true; $this->processInformation['termsig'] = -1; } } $this->callback = null; return $this->exitcode; } 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; } private function doSignal(int $signal, bool $throwException): bool { if (null === $pid = $this->getPid()) { if ($throwException) { throw new LogicException('Can not send signal on a non running process.'); } return false; } if ('\\' === \DIRECTORY_SEPARATOR) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); if ($exitCode && $this->isRunning()) { if ($throwException) { throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); } return false; } } else { if (!$this->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; } private function requireProcessIsStarted(string $functionName) { if (!$this->isStarted()) { throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); } } private function requireProcessIsTerminated(string $functionName) { if (!$this->isTerminated()) { throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); } } 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); } } onEmpty = $onEmpty; } 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); } public function close() { $this->open = false; } public function isClosed() { return !$this->open; } #[\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)); } } } } 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; } } 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; } } 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)); } } } 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(); } } executableFinder = new ExecutableFinder(); } public function find($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; } } if (@is_dir($php)) { return false; } return $php; } $args = $this->findArguments(); $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; 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) || @is_dir($php)) { return false; } return $php; } if ($php = getenv('PHP_PEAR_PHP_BIN')) { if (@is_executable($php) && !@is_dir($php)) { return $php; } } if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { return $php; } $dirs = [\PHP_BINDIR]; if ('\\' === \DIRECTORY_SEPARATOR) { $dirs[] = 'C:\xampp\php\\'; } return $this->executableFinder->find('php', false, $dirs); } public function findArguments() { $arguments = []; if ('phpdbg' === \PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } 0, Process::STDERR => 0, ]; private $haveReadSupport; public function __construct($input, bool $haveReadSupport) { $this->haveReadSupport = $haveReadSupport; if ($this->haveReadSupport) { $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() { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { $this->close(); } public function getDescriptors(): array { if (!$this->haveReadSupport) { $nullstream = fopen('NUL', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } return [ ['pipe', 'r'], ['file', 'NUL', 'w'], ['file', 'NUL', 'w'], ]; } public function getFiles(): array { return $this->files; } public function readAndWrite(bool $blocking, bool $close = false): array { $this->unblock(); $w = $this->write(); $read = $r = $e = []; if ($blocking) { if ($w) { @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); } elseif ($this->fileHandles) { usleep(Process::TIMEOUT_PRECISION * 1E6); } } foreach ($this->fileHandles as $type => $fileHandle) { $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); if (isset($data[0])) { $this->readBytes[$type] += \strlen($data); $read[$type] = $data; } if ($close) { ftruncate($fileHandle, 0); fclose($fileHandle); flock($this->lockHandles[$type], \LOCK_UN); fclose($this->lockHandles[$type]); unset($this->fileHandles[$type], $this->lockHandles[$type]); } } return $read; } public function haveReadSupport(): bool { return $this->haveReadSupport; } public function areOpen(): bool { return $this->pipes && $this->fileHandles; } public function close() { 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 = []; } } ttyMode = $ttyMode; $this->ptyMode = $ptyMode; $this->haveReadSupport = $haveReadSupport; parent::__construct($input); } public function __sleep() { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { $this->close(); } public function getDescriptors(): array { if (!$this->haveReadSupport) { $nullstream = fopen('/dev/null', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } if ($this->ttyMode) { return [ ['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w'], ]; } if ($this->ptyMode && Process::isPtySupported()) { return [ ['pty'], ['pty'], ['pty'], ]; } return [ ['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w'], ]; } public function getFiles(): array { return []; } public function readAndWrite(bool $blocking, bool $close = false): array { $this->unblock(); $w = $this->write(); $read = $e = []; $r = $this->pipes; unset($r[0]); 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 (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = []; } return $read; } restore_error_handler(); foreach ($r as $pipe) { $read[$type = array_search($pipe, $this->pipes, true)] = ''; do { $data = @fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); if (!isset($read[$type][0])) { unset($read[$type]); } if ($close && feof($pipe)) { fclose($pipe); unset($this->pipes[$type]); } } return $read; } public function haveReadSupport(): bool { return $this->haveReadSupport; } public function areOpen(): bool { return (bool) $this->pipes; } } input = $input; } elseif (\is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } public function close() { foreach ($this->pipes as $pipe) { if (\is_resource($pipe)) { fclose($pipe); } } $this->pipes = []; } protected function hasSystemCallBeenInterrupted(): bool { $lastError = $this->lastError; $this->lastError = null; return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); } 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; } 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_class($this->input), \gettype($input))); } $input = (string) $input; } $this->inputBuffer = $input; $this->input->next(); $input = null; } else { $input = null; } } $r = $e = []; $w = [$this->pipes[0]]; if (false === @stream_select($r, $w, $e, 0, 0)) { return null; } foreach ($w as $stdin) { if (isset($this->inputBuffer[0])) { $written = fwrite($stdin, $this->inputBuffer); $this->inputBuffer = substr($this->inputBuffer, $written); if (isset($this->inputBuffer[0])) { return [$this->pipes[0]]; } } if ($input) { while (true) { $data = fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { break; } $written = fwrite($stdin, $data); $data = substr($data, $written); if (isset($data[0])) { $this->inputBuffer = $data; return [$this->pipes[0]]; } } if (feof($input)) { if ($this->input instanceof \Iterator) { $this->input->next(); } else { $this->input = null; } } } } 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; } public function handleError(int $type, string $msg) { $this->lastError = $msg; } } find(false); $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); } if ('phpdbg' === \PHP_SAPI) { $file = tempnam(sys_get_temp_dir(), 'dbg'); file_put_contents($file, $script); register_shutdown_function('unlink', $file); $php[] = $file; $script = null; } parent::__construct($php, $cwd, $env, $script, $timeout); } public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) { throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); } public function setPhpBinary($php) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), \E_USER_DEPRECATED); $this->setCommandLine($php); } 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); } } $line) { $line = preg_replace('~(?relativePath = $relativePath; $this->relativePathname = $relativePathname; } public function getRelativePath() { return $this->relativePath; } public function getRelativePathname() { return $this->relativePathname; } public function getFilenameWithoutExtension(): string { $filename = $this->getFilename(); return pathinfo($filename, \PATHINFO_FILENAME); } public function getContents() { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); $content = file_get_contents($this->getPathname()); restore_error_handler(); if (false === $content) { throw new \RuntimeException($error); } return $content; } } ]=?|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 = '<'; } $this->setOperator($operator); $this->setTarget($target); } } target; } public function setTarget($target) { $this->target = $target; } public function getOperator() { return $this->operator; } public function setOperator($operator) { if (!$operator) { $operator = '=='; } if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } public function test($test) { switch ($this->operator) { case '>': return $test > $this->target; case '>=': return $test >= $this->target; case '<': return $test < $this->target; case '<=': return $test <= $this->target; case '!=': return $test != $this->target; } return $test == $this->target; } } ]=?)?\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])) { switch (strtolower($matches[3])) { case 'k': $target *= 1000; break; case 'ki': $target *= 1024; break; case 'm': $target *= 1000000; break; case 'mi': $target *= 1024 * 1024; break; case 'g': $target *= 1000000000; break; case 'gi': $target *= 1024 * 1024 * 1024; break; } } $this->setTarget($target); $this->setOperator($matches[1] ?? '=='); } } ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } public static function create() { return new static(); } public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } public function depth($levels) { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); } return $this; } public function date($dates) { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); } return $this; } public function name($patterns) { $this->names = array_merge($this->names, (array) $patterns); return $this; } public function notName($patterns) { $this->notNames = array_merge($this->notNames, (array) $patterns); return $this; } public function contains($patterns) { $this->contains = array_merge($this->contains, (array) $patterns); return $this; } public function notContains($patterns) { $this->notContains = array_merge($this->notContains, (array) $patterns); return $this; } public function path($patterns) { $this->paths = array_merge($this->paths, (array) $patterns); return $this; } public function notPath($patterns) { $this->notPaths = array_merge($this->notPaths, (array) $patterns); return $this; } public function size($sizes) { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); } return $this; } public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } public function ignoreDotFiles($ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } public function ignoreVCS($ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } 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; } public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } public function sort(\Closure $closure) { $this->sort = $closure; return $this; } public function sortByName() { if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { @trigger_error(sprintf('The "%s()" method will have a new "bool $useNaturalSort = false" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); } $useNaturalSort = 0 < \func_num_args() && func_get_arg(0); $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } public function reverseSorting() { $this->reverseSorting = true; return $this; } public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } public function followLinks() { $this->followLinks = true; return $this; } public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } public function in($dirs) { $resolvedDirs = []; foreach ((array) $dirs as $dir) { if (is_dir($dir)) { $resolvedDirs[] = $this->normalizeDir($dir); } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); $resolvedDirs = array_merge($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; } #[\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; } public function append($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; } public function hasResults() { foreach ($this->getIterator() as $_) { return true; } return false; } #[\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[] = '#(^|/)\..+(/|$)#'; } if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { $gitignoreFilePath = sprintf('%s/.gitignore', $dir); if (!is_readable($gitignoreFilePath)) { throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath)); } $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]); } $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); } return $iterator; } 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; } } comparators = $comparators; parent::__construct($iterator); } #[\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; } } ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = $path; if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = \DIRECTORY_SEPARATOR; } } #[\ReturnTypeWillChange] public function current() { 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); } #[\ReturnTypeWillChange] public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { return new \RecursiveArrayIterator([]); } else { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } } #[\ReturnTypeWillChange] public function rewind() { if (false === $this->isRewindable()) { return; } parent::rewind(); } 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; } } mode = $mode; parent::__construct($iterator); } #[\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; } } 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); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } 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); } #[\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; } #[\ReturnTypeWillChange] public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } #[\ReturnTypeWillChange] public function getChildren() { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; $children->excludedPattern = $this->excludedPattern; return $children; } } comparators = $comparators; parent::__construct($iterator); } #[\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; } } isAccepted($this->current()->getFilename()); } protected function toRegex($str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } minDepth = $minDepth; $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } filters = $filters; parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === $filter($fileinfo)) { return false; } } return true; } } matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } protected function isAccepted($string) { foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $string)) { return false; } } if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $string)) { return true; } } return false; } return true; } protected function isRegex($str) { $availableModifiers = 'imsxuADU'; if (\PHP_VERSION_ID >= 80200) { $availableModifiers .= 'n'; } if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); if ($start === $end) { return !preg_match('/[*?[:alnum:] \\\\]/', $start); } foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { if ($start === $delimiters[0] && $end === $delimiters[1]) { return true; } } } return false; } abstract protected function toRegex($str); } iteratorFactory = $iteratorFactory; } public function getIterator(): \Traversable { yield from ($this->iteratorFactory)(); } } current()->getRelativePathname(); if ('\\' === \DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } iterator = $iterator; $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { $this->sort = static function ($a, $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 ($a, $b) use ($order) { return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function ($a, $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 ($a, $b) use ($order) { return $order * ($a->getATime() - $b->getATime()); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = static function ($a, $b) use ($order) { return $order * ($a->getCTime() - $b->getCTime()); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = static function ($a, $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 ($a, $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.'); } } #[\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); } } selector = $selector; $this->expectedText = $expectedText; } public function toString(): string { if ($this->hasNode) { return sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText); } return sprintf('the Crawler has a node matching selector "%s"', $this->selector); } protected function matches($crawler): bool { $crawler = $crawler->filter($this->selector); if (!\count($crawler)) { $this->hasNode = false; return false; } $this->hasNode = true; $this->nodeText = $crawler->text(null, true); return false !== mb_strpos($this->nodeText, $this->expectedText); } protected function failureDescription($crawler): string { return $this->toString(); } } selector = $selector; $this->attribute = $attribute; $this->expectedText = $expectedText; } public function toString(): string { return sprintf('has a node matching selector "%s" with attribute "%s" of value "%s"', $this->selector, $this->attribute, $this->expectedText); } protected function matches($crawler): bool { $crawler = $crawler->filter($this->selector); if (!\count($crawler)) { return false; } return $this->expectedText === trim($crawler->attr($this->attribute) ?? ''); } protected function failureDescription($crawler): string { return 'the Crawler '.$this->toString(); } } selector = $selector; $this->expectedText = $expectedText; } public function toString(): string { return sprintf('has a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); } protected function matches($crawler): bool { $crawler = $crawler->filter($this->selector); if (!\count($crawler)) { return false; } return $this->expectedText === trim($crawler->text(null, true)); } protected function failureDescription($crawler): string { return 'the Crawler '.$this->toString(); } } selector = $selector; } public function toString(): string { return sprintf('matches selector "%s"', $this->selector); } protected function matches($crawler): bool { return 0 < \count($crawler->filter($this->selector)); } protected function failureDescription($crawler): string { return 'the Crawler '.$this->toString(); } } baseHref = $baseHref; $this->initialize(); } public function getFormNode() { return $this->node; } public function setValues(array $values) { foreach ($values as $name => $value) { $this->fields->set($name, $value); } return $this; } public function getValues() { $values = []; foreach ($this->fields->all() as $name => $field) { if ($field->isDisabled()) { continue; } if (!$field instanceof Field\FileFormField && $field->hasValue()) { $values[$name] = $field->getValue(); } } return $values; } public function getFiles() { if (!\in_array($this->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) { return []; } $files = []; foreach ($this->fields->all() as $name => $field) { if ($field->isDisabled()) { continue; } if ($field instanceof Field\FileFormField) { $files[$name] = $field->getValue(); } } return $files; } public function getPhpValues() { $values = []; foreach ($this->getValues() as $name => $value) { $qs = http_build_query([$name => $value], '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); $varName = substr($name, 0, \strlen(key($expandedValue))); $values = array_replace_recursive($values, [$varName => current($expandedValue)]); } } return $values; } public function getPhpFiles() { $values = []; foreach ($this->getFiles() as $name => $value) { $qs = http_build_query([$name => $value], '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); $varName = substr($name, 0, \strlen(key($expandedValue))); array_walk_recursive( $expandedValue, function (&$value, $key) { if (ctype_digit($value) && ('size' === $key || 'error' === $key)) { $value = (int) $value; } } ); reset($expandedValue); $values = array_replace_recursive($values, [$varName => current($expandedValue)]); } } return $values; } public function getUri() { $uri = parent::getUri(); if (!\in_array($this->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) { $query = parse_url($uri, \PHP_URL_QUERY); $currentParameters = []; if ($query) { parse_str($query, $currentParameters); } $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&'); $pos = strpos($uri, '?'); $base = false === $pos ? $uri : substr($uri, 0, $pos); $uri = rtrim($base.'?'.$queryString, '?'); } return $uri; } protected function getRawUri() { if ($this->button !== $this->node && $this->button->getAttribute('formaction')) { return $this->button->getAttribute('formaction'); } return $this->node->getAttribute('action'); } public function getMethod() { if (null !== $this->method) { return $this->method; } if ($this->button !== $this->node && $this->button->getAttribute('formmethod')) { return strtoupper($this->button->getAttribute('formmethod')); } return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; } public function getName(): string { return $this->node->getAttribute('name'); } public function has($name) { return $this->fields->has($name); } public function remove($name) { $this->fields->remove($name); } public function get($name) { return $this->fields->get($name); } public function set(FormField $field) { $this->fields->add($field); } public function all() { return $this->fields->all(); } #[\ReturnTypeWillChange] public function offsetExists($name) { return $this->has($name); } #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->fields->get($name); } #[\ReturnTypeWillChange] public function offsetSet($name, $value) { $this->fields->set($name, $value); } #[\ReturnTypeWillChange] public function offsetUnset($name) { $this->fields->remove($name); } public function disableValidation() { foreach ($this->fields->all() as $field) { if ($field instanceof Field\ChoiceFormField) { $field->disableValidation(); } } return $this; } protected function setNode(\DOMElement $node) { $this->button = $node; if ('button' === $node->nodeName || ('input' === $node->nodeName && \in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image']))) { if ($node->hasAttribute('form')) { $formId = $node->getAttribute('form'); $form = $node->ownerDocument->getElementById($formId); if (null === $form) { throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); } $this->node = $form; return; } do { if (null === $node = $node->parentNode) { throw new \LogicException('The selected node does not have a form ancestor.'); } } while ('form' !== $node->nodeName); } elseif ('form' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); } $this->node = $node; } private function initialize() { $this->fields = new FormFieldRegistry(); $xpath = new \DOMXPath($this->node->ownerDocument); if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { $name = $this->button->getAttribute('name'); $this->button->setAttribute('value', '0'); $this->button->setAttribute('name', $name.'.x'); $this->set(new Field\InputFormField($this->button)); $this->button->setAttribute('name', $name.'.y'); $this->set(new Field\InputFormField($this->button)); $this->button->setAttribute('name', $name); } else { $this->set(new Field\InputFormField($this->button)); } } if ($this->node->hasAttribute('id')) { $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[not(ancestor::template)]', $formId)); foreach ($fieldNodes as $node) { $this->addField($node); } } else { $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[not(ancestor::template)]', $this->node); foreach ($fieldNodes as $node) { $this->addField($node); } } if ($this->baseHref && '' !== $this->node->getAttribute('action')) { $this->currentUri = $this->baseHref; } } private function addField(\DOMElement $node) { if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { return; } $nodeName = $node->nodeName; if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { $this->set(new Field\ChoiceFormField($node)); } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) { $this->get($node->getAttribute('name'))->addChoice($node); } else { $this->set(new Field\ChoiceFormField($node)); } } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { $this->set(new Field\FileFormField($node)); } elseif ('input' == $nodeName && !\in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image'])) { $this->set(new Field\InputFormField($node)); } elseif ('textarea' == $nodeName) { $this->set(new Field\TextareaFormField($node)); } } } node->nodeName) { throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); } $this->value = ''; foreach ($this->node->childNodes as $node) { $this->value .= $node->wholeText; } } } type, ['checkbox', 'radio']) && null === $this->value) { return false; } return true; } public function isDisabled() { if (parent::isDisabled() && 'select' === $this->type) { return true; } foreach ($this->options as $option) { if ($option['value'] == $this->value && $option['disabled']) { return true; } } return false; } public function select($value) { $this->setValue($value); } public function tick() { if ('checkbox' !== $this->type) { throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(true); } public function untick() { if ('checkbox' !== $this->type) { throw new \LogicException(sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(false); } public function setValue($value) { if ('checkbox' === $this->type && false === $value) { $this->value = null; } elseif ('checkbox' === $this->type && true === $value) { $this->value = $this->options[0]['value']; } else { if (\is_array($value)) { if (!$this->multiple) { throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); } foreach ($value as $v) { if (!$this->containsOption($v, $this->options)) { throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $v, implode('", "', $this->availableOptionValues()))); } } } elseif (!$this->containsOption($value, $this->options)) { throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $value, implode('", "', $this->availableOptionValues()))); } if ($this->multiple) { $value = (array) $value; } if (\is_array($value)) { $this->value = $value; } else { parent::setValue($value); } } } public function addChoice(\DOMElement $node) { if (!$this->multiple && 'radio' !== $this->type) { throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); } $option = $this->buildOptionValue($node); $this->options[] = $option; if ($node->hasAttribute('checked')) { $this->value = $option['value']; } } public function getType() { return $this->type; } public function isMultiple() { return $this->multiple; } protected function initialize() { if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); } if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is "%s").', $this->node->getAttribute('type'))); } $this->value = null; $this->options = []; $this->multiple = false; if ('input' == $this->node->nodeName) { $this->type = strtolower($this->node->getAttribute('type')); $optionValue = $this->buildOptionValue($this->node); $this->options[] = $optionValue; if ($this->node->hasAttribute('checked')) { $this->value = $optionValue['value']; } } else { $this->type = 'select'; if ($this->node->hasAttribute('multiple')) { $this->multiple = true; $this->value = []; $this->name = str_replace('[]', '', $this->name); } $found = false; foreach ($this->xpath->query('descendant::option', $this->node) as $option) { $optionValue = $this->buildOptionValue($option); $this->options[] = $optionValue; if ($option->hasAttribute('selected')) { $found = true; if ($this->multiple) { $this->value[] = $optionValue['value']; } else { $this->value = $optionValue['value']; } } } if (!$found && !$this->multiple && !empty($this->options)) { $this->value = $this->options[0]['value']; } } } private function buildOptionValue(\DOMElement $node): array { $option = []; $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on'; $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : $defaultDefaultValue; $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; $option['disabled'] = $node->hasAttribute('disabled'); return $option; } public function containsOption($optionValue, $options) { if ($this->validationDisabled) { return true; } foreach ($options as $option) { if ($option['value'] == $optionValue) { return true; } } return false; } public function availableOptionValues() { $values = []; foreach ($this->options as $option) { $values[] = $option['value']; } return $values; } public function disableValidation() { $this->validationDisabled = true; return $this; } } node = $node; $this->name = $node->getAttribute('name'); $this->xpath = new \DOMXPath($node->ownerDocument); $this->initialize(); } public function getLabel() { $xpath = new \DOMXPath($this->node->ownerDocument); if ($this->node->hasAttribute('id')) { $labels = $xpath->query(sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id'))); if ($labels->length > 0) { return $labels->item(0); } } $labels = $xpath->query('ancestor::label[1]', $this->node); return $labels->length > 0 ? $labels->item(0) : null; } public function getName() { return $this->name; } public function getValue() { return $this->value; } public function setValue($value) { $this->value = (string) $value; } public function hasValue() { return true; } public function isDisabled() { return $this->node->hasAttribute('disabled'); } abstract protected function initialize(); } node->nodeName && 'button' !== $this->node->nodeName) { throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); } $type = strtolower($this->node->getAttribute('type')); if ('checkbox' === $type) { throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); } if ('file' === $type) { throw new \LogicException('File inputs should be instances of FileFormField.'); } $this->value = $this->node->getAttribute('value'); } } value = ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0]; } public function upload($value) { $this->setValue($value); } public function setValue($value) { if (null !== $value && is_readable($value)) { $error = \UPLOAD_ERR_OK; $size = filesize($value); $info = pathinfo($value); $name = $info['basename']; $tmp = sys_get_temp_dir().'/'.strtr(substr(base64_encode(hash('sha256', uniqid(mt_rand(), true), true)), 0, 7), '/', '_'); if (\array_key_exists('extension', $info)) { $tmp .= '.'.$info['extension']; } if (is_file($tmp)) { unlink($tmp); } copy($value, $tmp); $value = $tmp; } else { $error = \UPLOAD_ERR_NO_FILE; $size = 0; $name = ''; $value = ''; } $this->value = ['name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size]; } public function setFilePath($path) { parent::setValue($path); } protected function initialize() { if ('input' !== $this->node->nodeName) { throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); } if ('file' !== strtolower($this->node->getAttribute('type'))) { throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is "%s").', $this->node->getAttribute('type'))); } $this->setValue(null); } } node->getAttribute('href'); } protected function setNode(\DOMElement $node) { if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); } $this->node = $node; } } uri = $uri; $this->baseHref = $baseHref ?: $uri; $this->html5Parser = class_exists(HTML5::class) ? new HTML5(['disable_html_ns' => true]) : null; $this->add($node); } public function getUri() { return $this->uri; } public function getBaseHref() { return $this->baseHref; } public function clear() { $this->nodes = []; $this->document = null; } public function add($node) { if ($node instanceof \DOMNodeList) { $this->addNodeList($node); } elseif ($node instanceof \DOMNode) { $this->addNode($node); } elseif (\is_array($node)) { $this->addNodes($node); } elseif (\is_string($node)) { $this->addContent($node); } elseif (null !== $node) { throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', \is_object($node) ? \get_class($node) : \gettype($node))); } } public function addContent($content, $type = null) { if (empty($type)) { $type = str_starts_with($content, 'convertToHtmlEntities('charset=', $m[2])) { $charset = $m[2]; } return $m[1].$charset; }, $content, 1); if ('x' === $xmlMatches[1]) { $this->addXmlContent($content, $charset); } else { $this->addHtmlContent($content, $charset); } } public function addHtmlContent($content, $charset = 'UTF-8') { $dom = $this->parseHtmlString($content, $charset); $this->addDocument($dom); $base = $this->filterRelativeXPath('descendant-or-self::base')->extract(['href']); $baseHref = current($base); if (\count($base) && !empty($baseHref)) { if ($this->baseHref) { $linkNode = $dom->createElement('a'); $linkNode->setAttribute('href', $baseHref); $link = new Link($linkNode, $this->baseHref); $this->baseHref = $link->getUri(); } else { $this->baseHref = $baseHref; } } } public function addXmlContent($content, $charset = 'UTF-8', $options = \LIBXML_NONET) { if (!preg_match('/xmlns:/', $content)) { $content = str_replace('xmlns', 'ns', $content); } $internalErrors = libxml_use_internal_errors(true); if (\LIBXML_VERSION < 20900) { $disableEntities = libxml_disable_entity_loader(true); } $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; if ('' !== trim($content)) { @$dom->loadXML($content, $options); } libxml_use_internal_errors($internalErrors); if (\LIBXML_VERSION < 20900) { libxml_disable_entity_loader($disableEntities); } $this->addDocument($dom); $this->isHtml = false; } public function addDocument(\DOMDocument $dom) { if ($dom->documentElement) { $this->addNode($dom->documentElement); } } public function addNodeList(\DOMNodeList $nodes) { foreach ($nodes as $node) { if ($node instanceof \DOMNode) { $this->addNode($node); } } } public function addNodes(array $nodes) { foreach ($nodes as $node) { $this->add($node); } } public function addNode(\DOMNode $node) { if ($node instanceof \DOMDocument) { $node = $node->documentElement; } if (null !== $this->document && $this->document !== $node->ownerDocument) { throw new \InvalidArgumentException('Attaching DOM nodes from multiple documents in the same crawler is forbidden.'); } if (null === $this->document) { $this->document = $node->ownerDocument; } if (\in_array($node, $this->nodes, true)) { return; } $this->nodes[] = $node; } public function eq($position) { if (isset($this->nodes[$position])) { return $this->createSubCrawler($this->nodes[$position]); } return $this->createSubCrawler(null); } public function each(\Closure $closure) { $data = []; foreach ($this->nodes as $i => $node) { $data[] = $closure($this->createSubCrawler($node), $i); } return $data; } public function slice($offset = 0, $length = null) { return $this->createSubCrawler(\array_slice($this->nodes, $offset, $length)); } public function reduce(\Closure $closure) { $nodes = []; foreach ($this->nodes as $i => $node) { if (false !== $closure($this->createSubCrawler($node), $i)) { $nodes[] = $node; } } return $this->createSubCrawler($nodes); } public function first() { return $this->eq(0); } public function last() { return $this->eq(\count($this->nodes) - 1); } public function siblings() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild)); } public function matches(string $selector): bool { if (!$this->nodes) { return false; } $converter = $this->createCssSelectorConverter(); $xpath = $converter->toXPath($selector, 'self::'); return 0 !== $this->filterRelativeXPath($xpath)->count(); } public function closest(string $selector): ?self { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $domNode = $this->getNode(0); while (\XML_ELEMENT_NODE === $domNode->nodeType) { $node = $this->createSubCrawler($domNode); if ($node->matches($selector)) { return $node; } $domNode = $node->getNode(0)->parentNode; } return null; } public function nextAll() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0))); } public function previousAll() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling')); } public function parents() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); $nodes = []; while ($node = $node->parentNode) { if (\XML_ELEMENT_NODE === $node->nodeType) { $nodes[] = $node; } } return $this->createSubCrawler($nodes); } public function children() { if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { @trigger_error(sprintf('The "%s()" method will have a new "string $selector = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); } $selector = 0 < \func_num_args() ? func_get_arg(0) : null; if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } if (null !== $selector) { $converter = $this->createCssSelectorConverter(); $xpath = $converter->toXPath($selector, 'child::'); return $this->filterRelativeXPath($xpath); } $node = $this->getNode(0)->firstChild; return $this->createSubCrawler($node ? $this->sibling($node) : []); } public function attr($attribute) { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; } public function nodeName() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->getNode(0)->nodeName; } public function text() { if (!$this->nodes) { if (0 < \func_num_args() && null !== func_get_arg(0)) { return (string) func_get_arg(0); } throw new \InvalidArgumentException('The current node list is empty.'); } $text = $this->getNode(0)->nodeValue; if (\func_num_args() <= 1) { if (trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $text)) !== $text) { @trigger_error(sprintf('"%s()" will normalize whitespaces by default in Symfony 5.0, set the second "$normalizeWhitespace" argument to false to retrieve the non-normalized version of the text.', __METHOD__), \E_USER_DEPRECATED); } return $text; } if (\func_num_args() > 1 && func_get_arg(1)) { return trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $text)); } return $text; } public function html() { if (!$this->nodes) { if (0 < \func_num_args() && null !== func_get_arg(0)) { return (string) func_get_arg(0); } throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); $owner = $node->ownerDocument; if (null !== $this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { $owner = $this->html5Parser; } $html = ''; foreach ($node->childNodes as $child) { $html .= $owner->saveHTML($child); } return $html; } public function outerHtml(): string { if (!\count($this)) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); $owner = $node->ownerDocument; if (null !== $this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { $owner = $this->html5Parser; } return $owner->saveHTML($node); } public function evaluate($xpath) { if (null === $this->document) { throw new \LogicException('Cannot evaluate the expression on an uninitialized crawler.'); } $data = []; $domxpath = $this->createDOMXPath($this->document, $this->findNamespacePrefixes($xpath)); foreach ($this->nodes as $node) { $data[] = $domxpath->evaluate($xpath, $node); } if (isset($data[0]) && $data[0] instanceof \DOMNodeList) { return $this->createSubCrawler($data); } return $data; } public function extract($attributes) { $attributes = (array) $attributes; $count = \count($attributes); $data = []; foreach ($this->nodes as $node) { $elements = []; foreach ($attributes as $attribute) { if ('_text' === $attribute) { $elements[] = $node->nodeValue; } elseif ('_name' === $attribute) { $elements[] = $node->nodeName; } else { $elements[] = $node->getAttribute($attribute); } } $data[] = 1 === $count ? $elements[0] : $elements; } return $data; } public function filterXPath($xpath) { $xpath = $this->relativize($xpath); if ('' === $xpath) { return $this->createSubCrawler(null); } return $this->filterRelativeXPath($xpath); } public function filter($selector) { $converter = $this->createCssSelectorConverter(); return $this->filterRelativeXPath($converter->toXPath($selector)); } public function selectLink($value) { return $this->filterRelativeXPath( sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %1$s) or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %1$s)]]', static::xpathLiteral(' '.$value.' ')) ); } public function selectImage($value) { $xpath = sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', static::xpathLiteral($value)); return $this->filterRelativeXPath($xpath); } public function selectButton($value) { return $this->filterRelativeXPath( sprintf('descendant-or-self::input[((contains(%1$s, "submit") or contains(%1$s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %2$s)) or (contains(%1$s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %2$s)) or @id=%3$s or @name=%3$s] | descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %2$s) or @id=%3$s or @name=%3$s]', 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value)) ); } public function link($method = 'get') { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', \get_class($node))); } return new Link($node, $this->baseHref, $method); } public function links() { $links = []; foreach ($this->nodes as $node) { if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', \get_class($node))); } $links[] = new Link($node, $this->baseHref, 'get'); } return $links; } public function image() { if (!\count($this)) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', \get_class($node))); } return new Image($node, $this->baseHref); } public function images() { $images = []; foreach ($this as $node) { if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', \get_class($node))); } $images[] = new Image($node, $this->baseHref); } return $images; } public function form(array $values = null, $method = null) { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', \get_class($node))); } $form = new Form($node, $this->uri, $method, $this->baseHref); if (null !== $values) { $form->setValues($values); } return $form; } public function setDefaultNamespacePrefix($prefix) { $this->defaultNamespacePrefix = $prefix; } public function registerNamespace($prefix, $namespace) { $this->namespaces[$prefix] = $namespace; } public static function xpathLiteral($s) { if (!str_contains($s, "'")) { return sprintf("'%s'", $s); } if (!str_contains($s, '"')) { return sprintf('"%s"', $s); } $string = $s; $parts = []; while (true) { if (false !== $pos = strpos($string, "'")) { $parts[] = sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { $parts[] = "'$string'"; break; } } return sprintf('concat(%s)', implode(', ', $parts)); } private function filterRelativeXPath(string $xpath) { $prefixes = $this->findNamespacePrefixes($xpath); $crawler = $this->createSubCrawler(null); foreach ($this->nodes as $node) { $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); $crawler->add($domxpath->query($xpath, $node)); } return $crawler; } private function relativize(string $xpath): string { $expressions = []; $nonMatchingExpression = 'a[name() = "b"]'; $xpathLen = \strlen($xpath); $openedBrackets = 0; $startPosition = strspn($xpath, " \t\n\r\0\x0B"); for ($i = $startPosition; $i <= $xpathLen; ++$i) { $i += strcspn($xpath, '"\'[]|', $i); if ($i < $xpathLen) { switch ($xpath[$i]) { case '"': case "'": if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) { return $xpath; } continue 2; case '[': ++$openedBrackets; continue 2; case ']': --$openedBrackets; continue 2; } } if ($openedBrackets) { continue; } if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) { $j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1); $parenthesis = substr($xpath, $startPosition, $j); $startPosition += $j; } else { $parenthesis = ''; } $expression = rtrim(substr($xpath, $startPosition, $i - $startPosition)); if (str_starts_with($expression, 'self::*/')) { $expression = './'.substr($expression, 8); } if ('' === $expression) { $expression = $nonMatchingExpression; } elseif (str_starts_with($expression, '//')) { $expression = 'descendant-or-self::'.substr($expression, 2); } elseif (str_starts_with($expression, './/')) { $expression = 'descendant-or-self::'.substr($expression, 3); } elseif (str_starts_with($expression, './')) { $expression = 'self::'.substr($expression, 2); } elseif (str_starts_with($expression, 'child::')) { $expression = 'self::'.substr($expression, 7); } elseif ('/' === $expression[0] || '.' === $expression[0] || str_starts_with($expression, 'self::')) { $expression = $nonMatchingExpression; } elseif (str_starts_with($expression, 'descendant::')) { $expression = 'descendant-or-self::'.substr($expression, 12); } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { $expression = $nonMatchingExpression; } elseif (!str_starts_with($expression, 'descendant-or-self::')) { $expression = 'self::'.$expression; } $expressions[] = $parenthesis.$expression; if ($i === $xpathLen) { return implode(' | ', $expressions); } $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1); $startPosition = $i + 1; } return $xpath; } public function getNode($position) { return $this->nodes[$position] ?? null; } #[\ReturnTypeWillChange] public function count() { return \count($this->nodes); } #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->nodes); } protected function sibling($node, $siblingDir = 'nextSibling') { $nodes = []; $currentNode = $this->getNode(0); do { if ($node !== $currentNode && \XML_ELEMENT_NODE === $node->nodeType) { $nodes[] = $node; } } while ($node = $node->$siblingDir); return $nodes; } private function parseHtml5(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument { return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset)); } private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument { $htmlContent = $this->convertToHtmlEntities($htmlContent, $charset); $internalErrors = libxml_use_internal_errors(true); if (\LIBXML_VERSION < 20900) { $disableEntities = libxml_disable_entity_loader(true); } $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; if ('' !== trim($htmlContent)) { @$dom->loadHTML($htmlContent); } libxml_use_internal_errors($internalErrors); if (\LIBXML_VERSION < 20900) { libxml_disable_entity_loader($disableEntities); } return $dom; } private function convertToHtmlEntities(string $htmlContent, string $charset = 'UTF-8'): string { set_error_handler(function () { throw new \Exception(); }); try { return mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], $charset); } catch (\Exception|\ValueError $e) { try { $htmlContent = iconv($charset, 'UTF-8', $htmlContent); $htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); } catch (\Exception|\ValueError $e) { } return $htmlContent; } finally { restore_error_handler(); } } private function createDOMXPath(\DOMDocument $document, array $prefixes = []): \DOMXPath { $domxpath = new \DOMXPath($document); foreach ($prefixes as $prefix) { $namespace = $this->discoverNamespace($domxpath, $prefix); if (null !== $namespace) { $domxpath->registerNamespace($prefix, $namespace); } } return $domxpath; } private function discoverNamespace(\DOMXPath $domxpath, string $prefix): ?string { if (isset($this->namespaces[$prefix])) { return $this->namespaces[$prefix]; } $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); return ($node = $namespaces->item(0)) ? $node->nodeValue : null; } private function findNamespacePrefixes(string $xpath): array { if (preg_match_all('/(?P[a-z_][a-z_0-9\-\.]*+):[^"\/:]/i', $xpath, $matches)) { return array_unique($matches['prefix']); } return []; } private function createSubCrawler($nodes) { $crawler = new static($nodes, $this->uri, $this->baseHref); $crawler->isHtml = $this->isHtml; $crawler->document = $this->document; $crawler->namespaces = $this->namespaces; $crawler->html5Parser = $this->html5Parser; return $crawler; } private function createCssSelectorConverter(): CssSelectorConverter { if (!class_exists(CssSelectorConverter::class)) { throw new \LogicException('To filter with a CSS selector, install the CssSelector component ("composer require symfony/css-selector"). Or use filterXpath instead.'); } return new CssSelectorConverter($this->isHtml); } private function parseHtmlString(string $content, string $charset): \DOMDocument { if ($this->canParseHtml5String($content)) { return $this->parseHtml5($content, $charset); } return $this->parseXhtml($content, $charset); } private function canParseHtml5String(string $content): bool { if (null === $this->html5Parser) { return false; } if (false === ($pos = stripos($content, ''))) { return false; } $header = substr($content, 0, $pos); return '' === $header || $this->isValidHtml5Heading($header); } private function isValidHtml5Heading(string $heading): bool { return 1 === preg_match('/^\x{FEFF}?\s*(\s*)*$/u', $heading); } } node->getAttribute('src'); } protected function setNode(\DOMElement $node) { if ('img' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to visualize a "%s" tag.', $node->nodeName)); } $this->node = $node; } } getSegments($field->getName()); $target = &$this->fields; while ($segments) { if (!\is_array($target)) { $target = []; } $path = array_shift($segments); if ('' === $path) { $target = &$target[]; } else { $target = &$target[$path]; } } $target = $field; } public function remove(string $name) { $segments = $this->getSegments($name); $target = &$this->fields; while (\count($segments) > 1) { $path = array_shift($segments); if (!\is_array($target) || !\array_key_exists($path, $target)) { return; } $target = &$target[$path]; } unset($target[array_shift($segments)]); } public function &get(string $name) { $segments = $this->getSegments($name); $target = &$this->fields; while ($segments) { $path = array_shift($segments); if (!\is_array($target) || !\array_key_exists($path, $target)) { throw new \InvalidArgumentException(sprintf('Unreachable field "%s".', $path)); } $target = &$target[$path]; } return $target; } public function has(string $name): bool { try { $this->get($name); return true; } catch (\InvalidArgumentException $e) { return false; } } public function set(string $name, $value) { $target = &$this->get($name); if ((!\is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { $target->setValue($value); } elseif (\is_array($value)) { $registry = new static(); $registry->base = $name; $registry->fields = $value; foreach ($registry->all() as $k => $v) { $this->set($k, $v); } } else { throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); } } public function all(): array { return $this->walk($this->fields, $this->base); } private function walk(array $array, ?string $base = '', array &$output = []): array { foreach ($array as $k => $v) { $path = empty($base) ? $k : sprintf('%s[%s]', $base, $k); if (\is_array($v)) { $this->walk($v, $path, $output); } else { $output[$path] = $v; } } return $output; } private function getSegments(string $name): array { if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { $segments = [$m['base']]; while (!empty($m['extra'])) { $extra = $m['extra']; if (preg_match('/^\[(?P.*?)\](?P.*)$/', $extra, $m)) { $segments[] = $m['segment']; } else { $segments[] = $extra; } } return $segments; } return [$name]; } } setNode($node); $this->method = $method ? strtoupper($method) : null; $this->currentUri = $currentUri; $elementUriIsRelative = null === parse_url(trim($this->getRawUri()), \PHP_URL_SCHEME); $baseUriIsAbsolute = null !== $this->currentUri && \in_array(strtolower(substr($this->currentUri, 0, 4)), ['http', 'file']); if ($elementUriIsRelative && !$baseUriIsAbsolute) { throw new \InvalidArgumentException(sprintf('The URL of the element is relative, so you must define its base URI passing an absolute URL to the constructor of the "%s" class ("%s" was passed).', __CLASS__, $this->currentUri)); } } public function getNode() { return $this->node; } public function getMethod() { return $this->method ?? 'GET'; } public function getUri() { $uri = trim($this->getRawUri()); if (null !== parse_url($uri, \PHP_URL_SCHEME)) { return $uri; } if (!$uri) { return $this->currentUri; } if ('#' === $uri[0]) { return $this->cleanupAnchor($this->currentUri).$uri; } $baseUri = $this->cleanupUri($this->currentUri); if ('?' === $uri[0]) { return $baseUri.$uri; } if (str_starts_with($uri, '//')) { return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri; } $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri); if ('/' === $uri[0]) { return $baseUri.$uri; } $path = parse_url(substr($this->currentUri, \strlen($baseUri)), \PHP_URL_PATH); $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; } abstract protected function getRawUri(); protected function canonicalizePath($path) { if ('' === $path || '/' === $path) { return $path; } if (str_ends_with($path, '.')) { $path .= '/'; } $output = []; foreach (explode('/', $path) as $segment) { if ('..' === $segment) { array_pop($output); } elseif ('.' !== $segment) { $output[] = $segment; } } return implode('/', $output); } abstract protected function setNode(\DOMElement $node); private function cleanupUri(string $uri): string { return $this->cleanupQuery($this->cleanupAnchor($uri)); } private function cleanupQuery(string $uri): string { if (false !== $pos = strpos($uri, '?')) { return substr($uri, 0, $pos); } return $uri; } private function cleanupAnchor(string $uri): string { if (false !== $pos = strpos($uri, '#')) { return substr($uri, 0, $pos); } return $uri; } } 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); } } commandShouldRun = false; } public function enableCommand() { return $this->commandShouldRun = true; } public function commandShouldRun() { return $this->commandShouldRun; } } setExitCode($exitCode); } public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } public function getExitCode() { return $this->exitCode; } } command = $command; $this->input = $input; $this->output = $output; } public function getCommand() { return $this->command; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } } openOutputStream(), $verbosity, $decorated, $formatter); if (null === $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()); } } public function section(): ConsoleSectionOutput { return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); } public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } public function getErrorOutput() { return $this->stderr; } public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } protected function hasStderrSupport() { return false === $this->isRunningOS400(); } private function isRunningOS400(): bool { $checks = [ \function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), \PHP_OS, ]; return false !== stripos(implode(';', $checks), 'OS400'); } private function openOutputStream() { if (!$this->hasStdoutSupport()) { return fopen('php://output', 'w'); } return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); } private function openErrorStream() { if (!$this->hasStderrSupport()) { return fopen('php://output', 'w'); } return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); } } buffer; $this->buffer = ''; return $content; } protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } } } maxLength = $maxLength; } public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } $this->buffer = substr($this->buffer, 0 - $this->maxLength); } } verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?? new OutputFormatter(); $this->formatter->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } public function getFormatter() { return $this->formatter; } public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } public function isDecorated() { return $this->formatter->isDecorated(); } public function setVerbosity($level) { $this->verbosity = (int) $level; } public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } public function writeln($messages, $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } public function write($messages, $newline = false, $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); } } abstract protected function doWrite($message, $newline); } stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } public function getStream() { return $this->stream; } protected function doWrite($message, $newline) { if ($newline) { $message .= \PHP_EOL; } @fwrite($this->stream, $message); fflush($this->stream); } protected function hasColorSupport() { 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'); } if (\function_exists('stream_isatty')) { return @stream_isatty($this->stream); } if (\function_exists('posix_isatty')) { return @posix_isatty($this->stream); } $stat = @fstat($this->stream); return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } sections = &$sections; $this->terminal = new Terminal(); } public function clear(int $lines = null) { if (empty($this->content) || !$this->isDecorated()) { return; } if ($lines) { array_splice($this->content, -($lines * 2)); } else { $lines = $this->lines; $this->content = []; } $this->lines -= $lines; parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); } public function overwrite($message) { $this->clear(); $this->writeln($message); } public function getContent(): string { return implode('', $this->content); } 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; } } protected function doWrite($message, $newline) { if (!$this->isDecorated()) { parent::doWrite($message, $newline); return; } $erasedContent = $this->popStreamContentUntilCurrentSection(); $this->addContent($message); parent::doWrite($message, true); parent::doWrite($erasedContent, false); } 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) { parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); parent::doWrite("\x1b[0J", false); } return implode('', array_reverse($erasedContent)); } private function getDisplayLength(string $text): string { return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text)); } } &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)) { self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { self::initDimensionsUsingStty(); } elseif (null !== $dimensions = self::getConsoleMode()) { self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } } else { self::initDimensionsUsingStty(); } } private static function hasVt100Support(): bool { return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); } private static function initDimensionsUsingStty() { if ($sttyString = self::getSttyColumns()) { if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } } } private static function getConsoleMode(): ?array { $info = self::readFromProcess('mode CON'); if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { return null; } return [(int) $matches[2], (int) $matches[1]]; } private static function getSttyColumns(): ?string { return self::readFromProcess('stty -a | grep columns'); } private static function readFromProcess(string $command): ?string { if (!\function_exists('proc_open')) { return null; } $descriptorspec = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $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; } } input = $input; } } output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } public static function setStyleDefinition($name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } public static function getStyleDefinition($name) { if (!self::$styles) { self::$styles = self::initStyles(); } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } public function getStyle() { return $this->style; } public function setColumnStyle($columnIndex, $name) { $columnIndex = (int) $columnIndex; $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } public function getColumnStyle($columnIndex) { return $this->columnStyles[$columnIndex] ?? $this->getStyle(); } public function setColumnWidth($columnIndex, $width) { $this->columnWidths[(int) $columnIndex] = (int) $width; return $this; } public function setColumnWidths(array $widths) { $this->columnWidths = []; foreach ($widths as $index => $width) { $this->setColumnWidth($index, $width); } 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_class($this->output->getFormatter()))); } $this->columnMaxWidths[$columnIndex] = $width; return $this; } public function setHeaders(array $headers) { $headers = array_values($headers); if (!empty($headers) && !\is_array($headers[0])) { $headers = [$headers]; } $this->headers = $headers; return $this; } public function setRows(array $rows) { $this->rows = []; return $this->addRows($rows); } public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } public function addRow($row) { if ($row instanceof TableSeparator) { $this->rows[] = $row; return $this; } if (!\is_array($row)) { throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } $this->rows[] = array_values($row); return $this; } public function 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; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } public function setHeaderTitle(?string $title): self { $this->headerTitle = $title; return $this; } public function setFooterTitle(?string $title): self { $this->footerTitle = $title; return $this; } public function setHorizontal(bool $horizontal = true): self { $this->horizontal = $horizontal; return $this; } 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) { } else { $rows[$i][] = null; } } } } else { $rows = array_merge($this->headers, [$divider], $this->rows); } $this->calculateNumberOfColumns($rows); $rowGroups = $this->buildTableRows($rows); $this->calculateColumnsWidth($rowGroups); $isHeader = !$this->horizontal; $isFirstRow = $this->horizontal; $hasTitle = (bool) $this->headerTitle; foreach ($rowGroups as $rowGroup) { $isHeaderSeparatorRendered = false; foreach ($rowGroup as $row) { if ($divider === $row) { $isHeader = false; $isFirstRow = true; continue; } if ($row instanceof TableSeparator) { $this->renderRowSeparator(); continue; } if (!$row) { continue; } if ($isHeader && !$isHeaderSeparatorRendered) { $this->renderRowSeparator( $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null ); $hasTitle = false; $isHeaderSeparatorRendered = true; } if ($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; } 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::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)); $markupLength = Helper::strlen($markup); if ($titleLength > $limit = $markupLength - 4) { $titleLength = $limit; $formatLength = Helper::strlenWithoutDecoration($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)); } 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]); } 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); } private function renderCell(array $row, int $column, string $cellFormat): string { $cell = $row[$column] ?? ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; } } 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::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); $content = sprintf($style->getCellRowContentFormat(), $cell); return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); } 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 { $formatter = $this->output->getFormatter(); $unmergedRows = []; for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); foreach ($rows[$rowKey] as $column => $cell) { $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($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", "\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) { $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; if (isset($unmergedRows[$rowKey])) { foreach ($unmergedRows[$rowKey] as $row) { $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); } } yield $rowGroup; } }); } private function calculateRowCount(): int { $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); if ($this->headers) { ++$numberOfRows; } if (\count($this->rows) > 0) { ++$numberOfRows; } return $numberOfRows; } 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.', \gettype($cell))); } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = [$cell]; if (strstr($cell, "\n")) { $lines = explode("\n", str_replace("\n", "\n", $cell)); $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); unset($lines[0]); } $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()]); if ($nbLines === $unmergedRowKey - $line) { break; } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { 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; } 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) { $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; } private function getNumberOfColumns(array $row): int { $columns = \count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } private function getRowColumns(array $row): array { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } private function calculateColumnsWidth(iterable $groups) { for ($column = 0; $column < $this->numberOfColumns; ++$column) { $lengths = []; foreach ($groups as $group) { foreach ($group as $row) { if ($row instanceof TableSeparator) { continue; } foreach ($row as $i => $cell) { if ($cell instanceof TableCell) { $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); $textLength = Helper::strlen($textContent); if ($textLength > 0) { $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); foreach ($contentColumns as $position => $content) { $row[$i + $position] = $content; } } } } $lengths[] = $this->getCellWidth($row, $column); } } $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; } } private function getColumnSeparatorWidth(): int { return Helper::strlen(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::strlenWithoutDecoration($this->output->getFormatter(), $cell); } $columnWidth = $this->columnWidths[$column] ?? 0; $cellWidth = max($cellWidth, $columnWidth); return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; } private function cleanup() { $this->effectiveColumnWidths = []; $this->numberOfColumns = null; } 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)); } } register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } public function describe(OutputInterface $output, $object, array $options = []) { $options = array_merge([ 'raw_text' => false, 'format' => 'txt', ], $options); if (!isset($this->descriptors[$options['format']])) { throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } public function getName() { return 'descriptor'; } } '; 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 static $formatters; private static $formats; public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0.1) { 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()) { $this->overwrite = false; $this->redrawFreq = null; } $this->startTime = time(); } public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition(string $name): ?callable { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return self::$formatters[$name] ?? null; } public static function setFormatDefinition(string $name, string $format): void { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } public static function getFormatDefinition(string $name): ?string { if (!self::$formats) { self::$formats = self::initFormats(); } return self::$formats[$name] ?? null; } 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(): int { 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 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 { if (null === $this->barChar) { return $this->max ? '=' : $this->emptyBarChar; } return $this->barChar; } 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; } 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; } 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(); } public function start(int $max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } public function advance(int $step = 1) { $this->setProgress($this->step + $step); } 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; if ($this->max === $step) { $this->display(); return; } if ($timeInterval < $this->minSecondsBetweenRedraws) { return; } 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::strlen((string) $this->max) : 4; } public function finish(): void { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { return; } $this->setProgress($this->max); } 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()); } 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) { 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"); } 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::strlenWithoutDecoration($this->output->getFormatter(), $messageLine); if ($messageLineLength > $this->terminal->getWidth()) { $lineCount += floor($messageLineLength / $this->terminal->getWidth()); } } $this->output->clear($lineCount); } else { if ($this->formatLineCount > 0) { $message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message; } $message = "\x0D\x1B[2K$message"; } } } 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()) { case OutputInterface::VERBOSITY_VERBOSE: return $this->max ? 'verbose' : 'verbose_nomax'; case OutputInterface::VERBOSITY_VERY_VERBOSE: return $this->max ? 'very_verbose' : 'very_verbose_nomax'; case OutputInterface::VERBOSITY_DEBUG: return $this->max ? 'debug' : 'debug_nomax'; default: return $this->max ? 'normal' : 'normal_nomax'; } } private static function initPlaceholderFormatters(): 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::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); } return $display; }, 'elapsed' => function (self $bar) { return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $remaining = 0; } else { $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); } return Helper::formatTime($remaining); }, 'estimated' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $estimated = 0; } else { $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } return Helper::formatTime($estimated); }, 'memory' => function (self $bar) { return Helper::formatMemory(memory_get_usage(true)); }, 'current' => function (self $bar) { return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); }, 'max' => function (self $bar) { return $bar->getMaxSteps(); }, 'percent' => function (self $bar) { return floor($bar->getProgressPercent() * 100); }, ]; } private static function initFormats(): array { return [ 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', 'normal_nomax' => ' %current% [%bar%]', 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', ]; } 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); $linesLength = array_map(function ($subLine) { return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r")); }, explode("\n", $line)); $linesWidth = max($linesLength); $terminalWidth = $this->terminal->getWidth(); if ($linesWidth <= $terminalWidth) { return $line; } $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); return preg_replace_callback($regex, $callback, $this->format); } } 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); } } generator = $generator; } public function getIterator(): \Traversable { $g = $this->generator; return $g(); } } $helper) { $this->set($helper, \is_int($alias) ? null : $alias); } } public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } public function has($name) { return isset($this->helpers[$name]); } public function get($name) { if (!$this->has($name)) { throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } public function setCommand(Command $command = null) { $this->command = $command; } public function getCommand() { return $this->command; } #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->helpers); } } helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public static function strlen($string) { $string = (string) $string; if (false === $encoding = mb_detect_encoding($string, null, true)) { return \strlen($string); } return mb_strwidth($string, $encoding); } public static function substr($string, $from, $length = null) { $string = (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($memory) { if ($memory >= 1024 * 1024 * 1024) { return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { return sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { return sprintf('%d KiB', $memory / 1024); } return sprintf('%d B', $memory); } public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) { return self::strlen(self::removeDecoration($formatter, $string)); } public static function removeDecoration(OutputFormatterInterface $formatter, $string) { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); $string = $formatter->format($string); $string = preg_replace("/\033\[[^m]*m/", '', $string); $formatter->setDecorated($isDecorated); return $string; } } [%s] %s', $style, $section, $style, $message); } public function formatBlock($messages, $style, $large = false) { if (!\is_array($messages)) { $messages = [$messages]; } $len = 0; $lines = []; foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max(self::strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? [str_repeat(' ', $len)] : []; for ($i = 0; isset($lines[$i]); ++$i) { $messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i])); } if ($large) { $messages[] = str_repeat(' ', $len); } for ($i = 0; isset($messages[$i]); ++$i) { $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); } return implode("\n", $messages); } public function truncate($message, $length, $suffix = '...') { $computedLength = $length - self::strlen($suffix); if ($computedLength > self::strlen($message)) { return $message; } return self::substr($message, 0, $length).$suffix; } public function getName() { return 'formatter'; } } started[$id] = ['border' => ++$this->count % \count($this->colors)]; return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') { $message = ''; if ($error) { if (isset($this->started[$id]['out'])) { $message .= "\n"; unset($this->started[$id]['out']); } if (!isset($this->started[$id]['err'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %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 %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } public function stop($id, $message, $successful, $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s %s %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(' ', $this->colors[$this->started[$id]['border']]); } public function getName() { return 'debug_formatter'; } } 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(); } public function setMessage($message) { $this->message = $message; $this->display(); } public function start($message) { if ($this->started) { throw new LogicException('Progress indicator already started.'); } $this->message = $message; $this->started = true; $this->startTime = time(); $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; $this->indicatorCurrent = 0; $this->display(); } 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(); } public function finish($message) { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } $this->message = $message; $this->display(); $this->output->writeln(''); $this->started = false; } public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return self::$formats[$name] ?? null; } public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition($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()) { 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'; } } 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)); }, ]; } private static function initFormats(): array { return [ 'normal' => ' %indicator% %message%', 'normal_no_ansi' => ' %message%', 'verbose' => ' %indicator% %message% (%elapsed:6s%)', 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ]; } } 1, 'colspan' => 1, ]; public function __construct(string $value = '', array $options = []) { $this->value = $value; if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); } $this->options = array_merge($this->options, $options); } public function __toString() { return $this->value; } public function getColspan() { return (int) $this->options['colspan']; } public function getRowspan() { return (int) $this->options['rowspan']; } } %s '; private $footerTitleFormat = ' %s '; private $cellHeaderFormat = '%s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = \STR_PAD_RIGHT; public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty.'); } $this->paddingChar = $paddingChar; return $this; } public function getPaddingChar() { return $this->paddingChar; } public function setHorizontalBorderChars(string $outside, string $inside = null): self { $this->horizontalOutsideBorderChar = $outside; $this->horizontalInsideBorderChar = $inside ?? $outside; return $this; } public function setHorizontalBorderChar($horizontalBorderChar) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setHorizontalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->setHorizontalBorderChars($horizontalBorderChar, $horizontalBorderChar); } public function getHorizontalBorderChar() { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->horizontalOutsideBorderChar; } public function setVerticalBorderChars(string $outside, string $inside = null): self { $this->verticalOutsideBorderChar = $outside; $this->verticalInsideBorderChar = $inside ?? $outside; return $this; } public function setVerticalBorderChar($verticalBorderChar) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setVerticalBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->setVerticalBorderChars($verticalBorderChar, $verticalBorderChar); } public function getVerticalBorderChar() { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->verticalOutsideBorderChar; } public function getBorderChars(): array { return [ $this->horizontalOutsideBorderChar, $this->verticalOutsideBorderChar, $this->horizontalInsideBorderChar, $this->verticalInsideBorderChar, ]; } 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; } public function setDefaultCrossingChar(string $char): self { return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); } public function setCrossingChar($crossingChar) { @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use setDefaultCrossingChar() instead.', __METHOD__), \E_USER_DEPRECATED); return $this->setDefaultCrossingChar($crossingChar); } public function getCrossingChar() { return $this->crossingChar; } 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, ]; } public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } public function getCellHeaderFormat() { return $this->cellHeaderFormat; } public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } public function getCellRowFormat() { return $this->cellRowFormat; } public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } public function getCellRowContentFormat() { return $this->cellRowContentFormat; } public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } public function getBorderFormat() { return $this->borderFormat; } public function setPadType($padType) { if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); } $this->padType = $padType; return $this; } public function getPadType() { return $this->padType; } public function getHeaderTitleFormat(): string { return $this->headerTitleFormat; } public function setHeaderTitleFormat(string $format): self { $this->headerTitleFormat = $format; return $this; } public function getFooterTitleFormat(): string { return $this->footerTitleFormat; } public function setFooterTitleFormat(string $format): self { $this->footerTitleFormat = $format; return $this; } } 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; } } public function getName() { return 'question'; } public static function disableStty() { self::$stty = false; } 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) { $cp = $this->setIOCodepage(); $ret = fgets($inputStream, 4096); $ret = $this->resetIOCodepage($cp, $ret); 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; } 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; } 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); } protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag) { $messages = []; $maxWidth = max(array_map([__CLASS__, 'strlen'], array_keys($choices = $question->getChoices()))); foreach ($choices as $key => $value) { $padding = str_repeat(' ', $maxWidth - self::strlen($key)); $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); } return $messages; } 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->getMessage().''; } $output->writeln($message); } private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string { $fullChoice = ''; $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); $sttyMode = shell_exec('stty -g'); shell_exec('stty -icanon -echo'); $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); while (!feof($inputStream)) { $c = fread($inputStream, 1); if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { shell_exec(sprintf('stty %s', $sttyMode)); throw new MissingInputException('Aborted.'); } elseif ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; $fullChoice = self::substr($fullChoice, 0, $i); $output->write("\033[1D"); } if (0 === $i) { $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); } else { $numMatches = 0; } $ret = self::substr($ret, 0, $i); } elseif ("\033" === $c) { $c .= fread($inputStream, 2); 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]; $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 (str_starts_with($value, $tempRet)) { $matches[$numMatches++] = $value; } } } $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { $output->write("\0337"); $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); $output->write("\0338"); } } shell_exec(sprintf('stty %s', $sttyMode)); return $fullChoice; } private function mostRecentlyEnteredValue(string $entered): string { if (!str_contains($entered, ',')) { return $entered; } $choices = explode(',', $entered); if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { return $lastChoice; } return $entered; } private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string { if ('\\' === \DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; 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; } 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; } 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; } 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; } } getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if ($cmd instanceof Process) { $cmd = [$cmd]; } if (!\is_array($cmd)) { @trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), \E_USER_DEPRECATED); $cmd = [method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($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('%s', $this->escapeString($error))); } return $process; } public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); return function ($type, $buffer) use ($output, $process, $callback, $formatter) { $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { $callback($type, $buffer); } }; } private function escapeString(string $str): string { return str_replace('<', '\\<', $str); } public function getName() { return 'process'; } } getQuestion()); $default = $question->getDefault(); switch (true) { case null === $default: $text = sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' %s (yes/no) [%s]:', $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(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); break; default: $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); } $output->writeln($text); $prompt = ' > '; if ($question instanceof ChoiceQuestion) { $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); $prompt = $question->getPrompt(); } $output->write($prompt); } protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } } '; private $errorMessage = 'Value "%s" is invalid'; 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); } public function getChoices() { return $this->choices; } public function setMultiselect($multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } public function isMultiselect() { return $this->multiselect; } public function getPrompt() { return $this->prompt; } public function setPrompt($prompt) { $this->prompt = $prompt; return $this; } public function setErrorMessage($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) { if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = explode(',', (string) $selected); } else { $selectedChoices = [$selected]; } if ($this->isTrimmable()) { foreach ($selectedChoices as $k => $v) { $selectedChoices[$k] = trim((string) $v); } } $multiselectChoices = []; foreach ($selectedChoices as $value) { $results = []; foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (\count($results) > 1) { throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); } $result = array_search($value, $choices); if (!$isAssoc) { if (false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (false === $result && isset($choices[$value])) { $result = $value; } if (false === $result) { throw new InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = (string) $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } 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; }; } } question = $question; $this->default = $default; } public function getQuestion() { return $this->question; } public function getDefault() { return $this->default; } public function isHidden() { return $this->hidden; } public function setHidden($hidden) { if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = (bool) $hidden; return $this; } public function isHiddenFallback() { return $this->hiddenFallback; } public function setHiddenFallback($fallback) { $this->hiddenFallback = (bool) $fallback; return $this; } public function getAutocompleterValues() { $callback = $this->getAutocompleterCallback(); return $callback ? $callback('') : null; } public function setAutocompleterValues($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); }; } elseif (null === $values) { $callback = null; } else { throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.'); } return $this->setAutocompleterCallback($callback); } public function getAutocompleterCallback(): ?callable { return $this->autocompleterCallback; } 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; } public function setValidator(callable $validator = null) { $this->validator = $validator; return $this; } public function getValidator() { return $this->validator; } public function setMaxAttempts($attempts) { if (null !== $attempts) { $attempts = (int) $attempts; if ($attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } } $this->attempts = $attempts; return $this; } public function getMaxAttempts() { return $this->attempts; } public function setNormalizer(callable $normalizer) { $this->normalizer = $normalizer; return $this; } public function getNormalizer() { return $this->normalizer; } protected function isAssoc($array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); } public function isTrimmable(): bool { return $this->trimmable; } public function setTrimmable(bool $trimmable): self { $this->trimmable = $trimmable; return $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; } } name = $name; $this->version = $version; $this->terminal = new Terminal(); $this->defaultCommand = 'list'; } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } public function setCommandLoader(CommandLoaderInterface $commandLoader) { $this->commandLoader = $commandLoader; } 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 && !$phpHandler[0] instanceof LegacyErrorHandler)) { $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 ($exitCode <= 0) { $exitCode = 1; } } else { $exitCode = 1; } } finally { 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; } public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(['--version', '-V'], true)) { $output->writeln($this->getLongVersion()); return 0; } try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { } $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; $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); } $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } public function reset() { } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { if (!$this->helperSet) { $this->helperSet = $this->getDefaultHelperSet(); } return $this->helperSet; } public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } public function getDefinition() { if (!$this->definition) { $this->definition = $this->getDefaultInputDefinition(); } if ($this->singleCommand) { $inputDefinition = $this->definition; $inputDefinition->setArguments(); return $inputDefinition; } return $this->definition; } public function getHelp() { return $this->getLongVersion(); } public function areExceptionsCaught() { return $this->catchExceptions; } public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } public function isAutoExitEnabled() { return $this->autoExit; } public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getVersion() { return $this->version; } public function setVersion($version) { $this->version = $version; } public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return sprintf('%s %s', $this->getName(), $this->getVersion()); } return $this->getName(); } return 'Console Tool'; } public function register($name) { return $this->add(new Command($name)); } public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } public function add(Command $command) { $this->init(); $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return null; } $command->getDefinition(); if (!$command->getName()) { throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } public function get($name) { $this->init(); if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); } 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; } public function has($name) { $this->init(); return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); } public function getNamespaces() { $namespaces = []; foreach ($this->all() as $command) { if ($command->isHidden()) { continue; } $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); foreach ($command->getAliases() as $alias) { $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); } } return array_values(array_unique(array_filter($namespaces))); } public function findNamespace($namespace) { $allNamespaces = $this->getNamespaces(); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new 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); } public function find($name) { $this->init(); $aliases = []; foreach ($this->commands as $command) { foreach ($command->getAliases() as $alias) { if (!$this->has($alias)) { $this->commands[$alias] = $command; } } } if ($this->has($name)) { return $this->get($name); } $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); if (empty($commands)) { $commands = preg_grep('{^'.$expr.'}i', $allCommands); } if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { $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)); } 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::strlen($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::strlen($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()) { @trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), \E_USER_DEPRECATED); } return $command; } public function all($namespace = null) { $this->init(); if (null === $namespace) { if (!$this->commandLoader) { return $this->commands; } $commands = $this->commands; foreach ($this->commandLoader->getNames() as $name) { if (!isset($commands[$name]) && $this->has($name)) { $commands[$name] = $this->get($name); } } return $commands; } $commands = []; foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($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; } public static function getAbbreviations($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 renderException(\Exception $e, OutputInterface $output) { @trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); $output->writeln('', OutputInterface::VERBOSITY_QUIET); $this->doRenderException($e, $output); $this->finishRenderThrowableOrException($output); } public function renderThrowable(\Throwable $e, OutputInterface $output): void { if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'renderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'renderException'))->getDeclaringClass()->getName()) { @trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); if (!$e instanceof \Exception) { $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } $this->renderException($e, $output); return; } $output->writeln('', OutputInterface::VERBOSITY_QUIET); $this->doRenderThrowable($e, $output); $this->finishRenderThrowableOrException($output); } private function finishRenderThrowableOrException(OutputInterface $output): void { if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } protected function doRenderException(\Exception $e, OutputInterface $output) { @trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); $this->doActuallyRenderThrowable($e, $output); } protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void { if (__CLASS__ !== static::class && __CLASS__ === (new \ReflectionMethod($this, 'doRenderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'doRenderException'))->getDeclaringClass()->getName()) { @trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), \E_USER_DEPRECATED); if (!$e instanceof \Exception) { $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } $this->doRenderException($e, $output); return; } $this->doActuallyRenderThrowable($e, $output); } private function doActuallyRenderThrowable(\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::strlen($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) { $lineLength = Helper::strlen($line) + 4; $lines[] = [$line, $lineLength]; $len = max($lineLength, $len); } } $messages = []; if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); } $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); } foreach ($lines as $line) { $messages[] = sprintf(' %s %s', 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('Exception trace:', OutputInterface::VERBOSITY_QUIET); $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 %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); } 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; } protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if (null === $this->dispatcher) { return $command->run($input, $output); } try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { } $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(); } protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } protected function getDefaultInputDefinition() { return new InputDefinition([ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), ]); } protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand()]; } protected function getDefaultHelperSet() { return new HelperSet([ new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), ]); } private function getAbbreviationSuggestions(array $abbrevs): string { return ' '.implode("\n ", $abbrevs); } public function extractNamespace($name, $limit = null) { $parts = explode(':', $name, -1); return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); } 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); } public function setDefaultCommand($commandName, $isSingleCommand = false) { $this->defaultCommand = $commandName; if ($isSingleCommand) { $this->find($commandName); $this->singleCommand = true; } return $this; } public function isSingleCommand(): bool { return $this->singleCommand; } private function splitStringByWidth(string $string, int $width): array { 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) { if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } $lines[] = str_pad($line, $width); $line = $char; } } $lines[] = \count($lines) ? str_pad($line, $width) : $line; mb_convert_variables($encoding, 'utf8', $lines); return $lines; } private function extractAllNamespaces(string $name): array { $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); } } } input = $input; $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); $this->newLine(); } public function title($message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), ]); $this->newLine(); } public function section($message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), ]); $this->newLine(); } public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } public function text($message) { $this->autoPrependText(); $messages = \is_array($message) ? array_values($message) : [$message]; foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } public function comment($message) { $this->block($message, null, null, ' // ', false, false); } public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } public function warning($message) { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } public function table(array $headers, array $rows) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle($style); $table->render(); $this->newLine(); } public function horizontalTable(array $headers, array $rows) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle($style); $table->setHorizontal(true); $table->render(); $this->newLine(); } public function definitionList(...$list) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); $table = new Table($this); $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); } $table->setHeaders($headers); $table->setRows([$row]); $table->setHorizontal(); $table->setStyle($style); $table->render(); $this->newLine(); } public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } public function choice($question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); $default = $values[$default] ?? $default; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { $progressBar->setEmptyBarCharacter('░'); $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); } return $progressBar; } 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; } public function writeln($messages, $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::writeln($message, $type); $this->writeBuffer($message, true, $type); } } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::write($message, $newline, $type); $this->writeBuffer($message, $newline, $type); } } public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } public function getErrorStyle() { return new self($this->input, $this->getErrorOutput()); } 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(); return; } $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText(): void { $fetched = $this->bufferedOutput->fetch(); if (!str_ends_with($fetched, "\n")) { $this->newLine(); } } private function writeBuffer(string $message, bool $newLine, int $type): void { $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::strlenWithoutDecoration($this->getFormatter(), $prefix); $lines = []; if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = \strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } $decorationLength = Helper::strlen($message) - Helper::strlenWithoutDecoration($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::strlenWithoutDecoration($this->getFormatter(), $line), 0)); if ($style) { $line = sprintf('<%s>%s', $style, $line); } } return $lines; } } output = $output; } public function newLine($count = 1) { $this->output->write(str_repeat(\PHP_EOL, $count)); } public function createProgressBar($max = 0) { return new ProgressBar($this->output, $max); } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } public function setVerbosity($level) { $this->output->setVerbosity($level); } public function getVerbosity() { return $this->output->getVerbosity(); } public function setDecorated($decorated) { $this->output->setDecorated($decorated); } public function isDecorated() { return $this->output->isDecorated(); } public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } public function getFormatter() { return $this->output->getFormatter(); } public function isQuiet() { return $this->output->isQuiet(); } public function isVerbose() { return $this->output->isVerbose(); } public function isVeryVerbose() { return $this->output->isVeryVerbose(); } public function isDebug() { return $this->output->isDebug(); } protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; } return $this->output->getErrorOutput(); } } command = $command; } public function execute(array $input, array $options = []) { 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); $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); } } application = $application; } public function run(array $input, $options = []) { $prevShellVerbosity = getenv('SHELL_VERBOSITY'); try { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); } $this->initOutput($options); return $this->statusCode = $this->application->run($this->input, $this->output); } finally { if (false === $prevShellVerbosity) { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY'); } unset($_ENV['SHELL_VERBOSITY']); unset($_SERVER['SHELL_VERBOSITY']); } else { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; } } } } 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; } public function getErrorOutput($normalize = false) { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); } rewind($this->output->getErrorOutput()->getStream()); $display = stream_get_contents($this->output->getErrorOutput()->getStream()); if ($normalize) { $display = str_replace(\PHP_EOL, "\n", $display); } return $display; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { return $this->statusCode; } public function setInputs(array $inputs) { $this->inputs = $inputs; return $this; } 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)); } } 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; } } 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; } 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; if (self::ERROR === $this->formatLevelMap[$level]) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = true; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } public function hasErrored() { return $this->errored; } 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); } } setName('list') ->setDefinition($this->createDefinition()) ->setDescription('List commands') ->setHelp(<<<'EOF' The %command.name% command lists all commands: php %command.full_name% You can also display the commands for a specific namespace: php %command.full_name% test You can also output the information in other formats by using the --format option: php %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): php %command.full_name% --raw EOF ) ; } public function getNativeDefinition() { return $this->createDefinition(); } 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'), ]); return 0; } private function createDefinition(): InputDefinition { return new InputDefinition([ 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'), ]); } } 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; } private function release() { if ($this->lock) { $this->lock->release(); $this->lock = null; } } } 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 %command.name% command displays help for a given command: php %command.full_name% list You can also output the help in other formats by using the --format option: php %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } public function setCommand(Command $command) { $this->command = $command; } 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; } } class ? static::$defaultName : null; } public function __construct(string $name = null) { $this->definition = new InputDefinition(); if (null !== $name || null !== $name = static::getDefaultName()) { $this->setName($name); } $this->configure(); } public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public function getApplication() { return $this->application; } public function isEnabled() { return true; } protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } protected function interact(InputInterface $input, OutputInterface $output) { } protected function initialize(InputInterface $input, OutputInterface $output) { } public function run(InputInterface $input, OutputInterface $output) { $this->getSynopsis(true); $this->getSynopsis(false); $this->mergeApplicationDefinition(); try { $input->bind($this->definition); } catch (ExceptionInterface $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (\function_exists('cli_set_process_title')) { if (!@cli_set_process_title($this->processTitle)) { if ('Darwin' === \PHP_OS) { $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', 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('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } 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)) { @trigger_error(sprintf('Return value of "%s::execute()" should always be of the type int since Symfony 4.4, %s returned.', static::class, \gettype($statusCode)), \E_USER_DEPRECATED); } } return is_numeric($statusCode) ? (int) $statusCode : 0; } 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; } public function mergeApplicationDefinition($mergeArgs = true) { if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { return; } $this->definition->addOptions($this->application->getDefinition()->getOptions()); $this->applicationDefinitionMerged = true; if ($mergeArgs) { $currentArguments = $this->definition->getArguments(); $this->definition->setArguments($this->application->getDefinition()->getArguments()); $this->definition->addArguments($currentArguments); $this->applicationDefinitionMergedWithArgs = true; } } public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } public function getDefinition() { if (null === $this->definition) { throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } return $this->definition; } public function getNativeDefinition() { return $this->getDefinition(); } public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } public function setProcessTitle($title) { $this->processTitle = $title; return $this; } public function getName() { return $this->name; } public function setHidden($hidden) { $this->hidden = (bool) $hidden; return $this; } public function isHidden() { return $this->hidden; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setHelp($help) { $this->help = $help; return $this; } public function getHelp() { return $this->help; } 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()); } public function setAliases($aliases) { if (!\is_array($aliases) && !$aliases instanceof \Traversable) { throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable.'); } foreach ($aliases as $alias) { $this->validateName($alias); } $this->aliases = $aliases; return $this; } public function getAliases() { return $this->aliases; } public function getSynopsis($short = false) { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } public function addUsage($usage) { if (!str_starts_with($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } public function getUsages() { return $this->usages; } public function getHelper($name) { if (null === $this->helperSet) { throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); } return $this->helperSet->get($name); } private function validateName(string $name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } factories = $factories; } public function has($name) { return isset($this->factories[$name]); } public function get($name) { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } $factory = $this->factories[$name]; return $factory(); } public function getNames() { return array_keys($this->factories); } } container = $container; $this->commandMap = $commandMap; } public function get($name) { if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->container->get($this->commandMap[$name]); } public function has($name) { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } public function getNames() { return array_keys($this->commandMap); } } commandLoaderServiceId = $commandLoaderServiceId; $this->commandTag = $commandTag; } public function process(ContainerBuilder $container) { $commandServices = $container->findTaggedServiceIds($this->commandTag, true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { $commandName = $tags[0]['command']; } else { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } $commandName = null !== $class::getDefaultName() ? str_replace('%', '%%', $class::getDefaultName()) : null; } if (null === $commandName) { if (!$definition->isPublic() || $definition->isPrivate()) { $commandId = 'console.command.public_alias.'.$id; $container->setAlias($commandId, $id)->setPublic(true); $id = $commandId; } $serviceIds[] = $id; continue; } unset($tags[0]); $lazyCommandMap[$commandName] = $id; $lazyCommandRefs[$id] = new TypedReference($id, $class); $aliases = []; foreach ($tags as $tag) { if (isset($tag['command'])) { $aliases[] = $tag['command']; $lazyCommandMap[$tag['command']] = $id; } } $definition->addMethodCall('setName', [$commandName]); if ($aliases) { $definition->addMethodCall('setAliases', [$aliases]); } } $container ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) ->setPublic(true) ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); } } alternatives = $alternatives; } public function getAlternatives() { return $this->alternatives; } } styleStack = clone $this->styleStack; foreach ($this->styles as $key => $value) { $this->styles[$key] = clone $value; } } public static function escape($text) { $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); return self::escapeTrailingBackslash($text); } 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; } 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(); } public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } public function isDecorated() { return $this->decorated; } public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } public function getStyle($name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); } return $this->styles[strtolower($name)]; } public function format($message) { return $this->formatAndWrap((string) $message, 0); } public function formatAndWrap(string $message, int $width) { $offset = 0; $output = ''; $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; $closeTagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); $offset = $pos + \strlen($text); 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); return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } public function getStyleStack() { return $this->styleStack; } private function createStyleFromString(string $string): ?OutputFormatterStyleInterface { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { return null; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); $match[0] = strtolower($match[0]); if ('fg' == $match[0]) { $style->setForeground(strtolower($match[1])); } elseif ('bg' == $match[0]) { $style->setBackground(strtolower($match[1])); } elseif ('href' === $match[0]) { $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); $style->setHref($url); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); foreach ($options as $option) { $style->setOption($option); } } else { return null; } } return $style; } 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); } } ['set' => 30, 'unset' => 39], 'red' => ['set' => 31, 'unset' => 39], 'green' => ['set' => 32, 'unset' => 39], 'yellow' => ['set' => 33, 'unset' => 39], 'blue' => ['set' => 34, 'unset' => 39], 'magenta' => ['set' => 35, 'unset' => 39], 'cyan' => ['set' => 36, 'unset' => 39], 'white' => ['set' => 37, 'unset' => 39], 'default' => ['set' => 39, 'unset' => 39], ]; private static $availableBackgroundColors = [ 'black' => ['set' => 40, 'unset' => 49], 'red' => ['set' => 41, 'unset' => 49], 'green' => ['set' => 42, 'unset' => 49], 'yellow' => ['set' => 43, 'unset' => 49], 'blue' => ['set' => 44, 'unset' => 49], 'magenta' => ['set' => 45, 'unset' => 49], 'cyan' => ['set' => 46, 'unset' => 49], 'white' => ['set' => 47, 'unset' => 49], 'default' => ['set' => 49, 'unset' => 49], ]; private static $availableOptions = [ 'bold' => ['set' => 1, 'unset' => 22], 'underscore' => ['set' => 4, 'unset' => 24], 'blink' => ['set' => 5, 'unset' => 25], 'reverse' => ['set' => 7, 'unset' => 27], 'conceal' => ['set' => 8, 'unset' => 28], ]; private $foreground; private $background; private $href; private $options = []; private $handlesHrefGracefully; public function __construct(string $foreground = null, string $background = null, array $options = []) { if (null !== $foreground) { $this->setForeground($foreground); } if (null !== $background) { $this->setBackground($background); } if (\count($options)) { $this->setOptions($options); } } public function setForeground($color = null) { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableForegroundColors)))); } $this->foreground = static::$availableForegroundColors[$color]; } public function setBackground($color = null) { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); } $this->background = static::$availableBackgroundColors[$color]; } public function setHref(string $url): void { $this->href = $url; } public function setOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); } if (!\in_array(static::$availableOptions[$option], $this->options)) { $this->options[] = static::$availableOptions[$option]; } } public function unsetOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); } $pos = array_search(static::$availableOptions[$option], $this->options); if (false !== $pos) { unset($this->options[$pos]); } } public function setOptions(array $options) { $this->options = []; foreach ($options as $option) { $this->setOption($option); } } public function apply($text) { $setCodes = []; $unsetCodes = []; if (null === $this->handlesHrefGracefully) { $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); } if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; } if (null !== $this->background) { $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } foreach ($this->options as $option) { $setCodes[] = $option['set']; $unsetCodes[] = $option['unset']; } if (null !== $this->href && $this->handlesHrefGracefully) { $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; } if (0 === \count($setCodes)) { return $text; } return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); } } emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); $this->reset(); } public function reset() { $this->styles = []; } public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } 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.'); } public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[\count($this->styles) - 1]; } public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } public function getEmptyStyle() { return $this->emptyStyle; } } getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = $options['total_width'] ?? Helper::strlen($argument->getName()); $spacingWidth = $totalWidth - \strlen($argument->getName()); $this->writeText(sprintf(' %s %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } protected function describeInputOption(InputOption $option, array $options = []) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf(' [default: %s]', $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('--%s%s', $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::strlen($synopsis); $this->writeText(sprintf(' %s %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); } if ($definition->getArguments()) { $this->writeText('Arguments:', $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('Options:', $options); foreach ($definition->getOptions() as $option) { if (\strlen($option->getShortcut() ?? '') > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } } } protected function describeCommand(Command $command, array $options = []) { $command->getSynopsis(true); $command->getSynopsis(false); $command->mergeApplicationDefinition(false); if ($description = $command->getDescription()) { $this->writeText('Description:', $options); $this->writeText("\n"); $this->writeText(' '.$description); $this->writeText("\n\n"); } $this->writeText('Usage:', $options); foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' '.OutputFormatter::escape($usage), $options); } $this->writeText("\n"); $definition = $command->getNativeDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } $help = $command->getProcessedHelp(); if ($help && $help !== $description) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } 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("Usage:\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) { $describedNamespaceInfo = reset($namespaces); foreach ($describedNamespaceInfo['commands'] as $name) { $commands[$name] = $description->getCommand($name); } } $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('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $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(' '.$namespace['id'].'', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - Helper::strlen($name); $command = $commands[$name]; $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); } } $this->writeText("\n"); } } 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 ); } private function getCommandAliasesText(Command $command): string { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '['.implode('|', $aliases).'] '; } return $text; } 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)); } private function getColumnWidth(array $commands): int { $widths = []; foreach ($commands as $command) { if ($command instanceof Command) { $widths[] = Helper::strlen($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = Helper::strlen($alias); } } else { $widths[] = Helper::strlen($command); } } return $widths ? max($widths) + 2 : 0; } private function calculateTotalWidthForOptions(array $options): int { $totalWidth = 0; foreach ($options as $option) { $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + Helper::strlen($option->getName()); $valueLength += $option->isValueOptional() ? 2 : 0; $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } 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): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $command->getSynopsis(); $command->mergeApplicationDefinition(false); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); return $dom; } public function getApplicationDocument(Application $application, string $namespace = null): \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)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeDocument($this->getInputArgumentDocument($argument)); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeDocument($this->getInputOptionDocument($option)); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } protected function describeCommand(Command $command, array $options = []) { $this->writeDocument($this->getCommandDocument($command)); } protected function describeApplication(Application $application, array $options = []) { $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null)); } private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } 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)); } } } return $dom; } } output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object))); } } protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } abstract protected function describeInputArgument(InputArgument $argument, array $options = []); abstract protected function describeInputOption(InputOption $option, array $options = []); abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); abstract protected function describeCommand(Command $command, array $options = []); abstract protected function describeApplication(Application $application, array $options = []); } isDecorated(); $output->setDecorated(false); parent::describe($output, $object, $options); $output->setDecorated($decorated); } protected function write($content, $decorated = true) { parent::write($content, $decorated); } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->write( '#### `'.($argument->getName() ?: '')."`\n\n" .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } protected function describeInputOption(InputOption $option, array $options = []) { $name = '--'.$option->getName(); if ($option->getShortcut()) { $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } $this->write( '#### `'.$name.'`'."\n\n" .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { if ($showArguments = \count($definition->getArguments()) > 0) { $this->write('### Arguments'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); $this->write($this->describeInputArgument($argument)); } } if (\count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); $this->write($this->describeInputOption($option)); } } } protected function describeCommand(Command $command, array $options = []) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); $this->write( '`'.$command->getName()."`\n" .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry.'* `'.$usage.'`'."\n"; }) ); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } if ($command->getNativeDefinition()) { $this->write("\n\n"); $this->describeInputDefinition($command->getNativeDefinition()); } } 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::strlen($title))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) use ($description) { return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); $this->write($this->describeCommand($command)); } } private function getApplicationTitle(Application $application): string { if ('UNKNOWN' !== $application->getName()) { if ('UNKNOWN' !== $application->getVersion()) { return sprintf('%s %s', $application->getName(), $application->getVersion()); } return $application->getName(); } return 'Console Tool'; } } application = $application; $this->namespace = $namespace; $this->showHidden = $showHidden; } public function getNamespaces(): array { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } public function getCommands(): array { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } 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 = []; 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; } } writeData($this->getInputArgumentData($argument), $options); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeData($this->getInputDefinitionData($definition), $options); } protected function describeCommand(Command $command, array $options = []) { $this->writeData($this->getCommandData($command), $options); } 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); } $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); } 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): array { return [ 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), ]; } 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); } return ['arguments' => $inputArguments, 'options' => $inputOptions]; } private function getCommandData(Command $command): array { $command->getSynopsis(); $command->mergeApplicationDefinition(false); return [ 'name' => $command->getName(), 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), 'description' => $command->getDescription(), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), 'hidden' => $command->isHidden(), ]; } } setTokens($this->tokenize($input)); } private function tokenize(string $input): array { $tokens = []; $length = \strlen($input); $cursor = 0; $token = null; while ($cursor < $length) { if ('\\' === $input[$cursor]) { $token .= $input[++$cursor] ?? ''; ++$cursor; continue; } if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { if (null !== $token) { $tokens[] = $token; $token = null; } } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= stripcslashes(substr($match[0], 1, -1)); } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= $match[1]; } else { throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); } $cursor += \strlen($match[0]); } if (null !== $token) { $tokens[] = $token; } return $tokens; } } 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); } public function getName() { return $this->name; } public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } public function setDefault($default = null) { if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } } parameters = $parameters; parent::__construct($definition); } public function getFirstArgument() { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { continue; } return $value; } return null; } public function hasParameterOption($values, $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!\is_int($k)) { $v = $k; } if ($onlyParams && '--' === $v) { return false; } if (\in_array($v, $values)) { return true; } } return false; } public function getParameterOption($values, $default = false, $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { return $default; } if (\is_int($k)) { if (\in_array($v, $values)) { return true; } } elseif (\in_array($k, $values)) { return $v; } } return $default; } 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); } 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); } } } 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); } private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isValueOptional()) { $value = true; } } $this->options[$name] = $value; } 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; } } tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { $parseOptions = false; } elseif ($parseOptions && str_starts_with($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } } } 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()) { $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } 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); } } } 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); } } private function parseArgument(string $token) { $c = \count($this->arguments); if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; } else { $all = $this->definition->getArguments(); if (\count($all)) { throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); } throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); } } 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); } private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { $next = array_shift($this->parsed); if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { $value = $next; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray() && !$option->isValueOptional()) { $value = true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } public function getFirstArgument() { $isOption = false; foreach ($this->tokens as $i => $token) { if ($token && '-' === $token[0]) { if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { continue; } $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { $isOption = true; } continue; } if ($isOption) { $isOption = false; continue; } return $token; } return null; } public function hasParameterOption($values, $onlyParams = false) { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && '--' === $token) { return false; } foreach ($values as $value) { $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { return true; } } } return false; } public function getParameterOption($values, $default = false, $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); } $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ('' !== $leading && str_starts_with($token, $leading)) { return substr($token, \strlen($leading)); } } } return $default; } 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); } } definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } public function bind(InputDefinition $definition) { $this->arguments = []; $this->options = []; $this->definition = $definition; $this->parse(); } abstract protected function parse(); 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))); } } public function isInteractive() { return $this->interactive; } public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } public function getArgument($name) { if (!$this->definition->hasArgument((string) $name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); } public function setArgument($name, $value) { if (!$this->definition->hasArgument((string) $name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } public function hasArgument($name) { return $this->definition->hasArgument((string) $name); } public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } public function getOption($name) { if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } public function hasOption($name) { return $this->definition->hasOption($name); } public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } public function setStream($stream) { $this->stream = $stream; } public function getStream() { return $this->stream; } } setDefinition($definition); } 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); } public function setArguments($arguments = []) { $this->arguments = []; $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } public function addArguments($arguments = []) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if ($this->hasAnArrayArgument) { throw new LogicException('Cannot add an argument after an array argument.'); } if ($argument->isRequired() && $this->hasOptional) { throw new LogicException('Cannot add a required argument after an optional one.'); } if ($argument->isArray()) { $this->hasAnArrayArgument = true; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->hasOptional = true; } $this->arguments[$argument->getName()] = $argument; } 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]; } public function hasArgument($name) { $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } public function getArguments() { return $this->arguments; } public function getArgumentCount() { return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments); } public function getArgumentRequiredCount() { return $this->requiredCount; } public function getArgumentDefaults() { $values = []; foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } public function setOptions($options = []) { $this->options = []; $this->shortcuts = []; $this->addOptions($options); } public function addOptions($options = []) { foreach ($options as $option) { $this->addOption($option); } } public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } } public function getOption($name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } public function hasOption($name) { return isset($this->options[$name]); } public function getOptions() { return $this->options; } public function hasShortcut($name) { return isset($this->shortcuts[$name]); } public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } 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]; } public function getSynopsis($short = false) { $elements = []; if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = sprintf( ' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '' ); } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); } } if (\count($elements) && $this->getArguments()) { $elements[] = '[--]'; } $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; } } 15 || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } $this->setDefault($default); } public function getShortcut() { return $this->shortcut; } public function getName() { return $this->name; } public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() ? $default : false; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } public function equals(self $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } format('c'); case $value instanceof \UnitEnum: return sprintf('!php/const %s::%s', \get_class($value), $value->name); case \is_object($value): if ($value instanceof TaggedValue) { return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); } if (Yaml::DUMP_OBJECT & $flags) { return '!php/object '.self::dump(serialize($value)); } if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { $output = []; foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return sprintf('{ %s }', implode(', ', $output)); } if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return self::dumpNull($flags); case \is_array($value): return self::dumpArray($value, $flags); case null === $value: return self::dumpNull($flags); case true === $value: return 'true'; case false === $value: return 'false'; case \is_int($value): return $value; case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): $locale = setlocale(\LC_NUMERIC, 0); if (false !== $locale) { setlocale(\LC_NUMERIC, 'C'); } if (\is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { $repr = '!!float '.$repr; } } else { $repr = \is_string($value) ? "'$value'" : (string) $value; } if (false !== $locale) { setlocale(\LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case self::isBinaryString($value): return '!!binary '.base64_encode($value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): case Parser::preg_match(self::getHexRegex(), $value): case Parser::preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } } public static function isHash($value): bool { if ($value instanceof \stdClass || $value instanceof \ArrayObject) { return true; } $expectedKey = 0; foreach ($value as $key => $val) { if ($key !== $expectedKey++) { return true; } } return false; } private static function dumpArray(array $value, int $flags): string { if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { $output = []; foreach ($value as $val) { $output[] = self::dump($val, $flags); } return sprintf('[%s]', implode(', ', $output)); } $output = []; foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return sprintf('{ %s }', implode(', ', $output)); } private static function dumpNull(int $flags): string { if (Yaml::DUMP_NULL_AS_TILDE & $flags) { return '~'; } return 'null'; } public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null) { if (\in_array($scalar[$i], ['"', "'"])) { $isQuoted = true; $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), " \n"); if ('' === $tmp) { throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (!\in_array($tmp[0], $delimiters)) { throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } } } else { $isQuoted = false; if (!$delimiters) { $output = substr($scalar, $i); $i += \strlen($output); if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { $output = substr($output, 0, $match[0][1]); } } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += \strlen($output); $output = trim($output); } else { throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); } if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); } if ($evaluate) { $output = self::evaluateScalar($output, $flags, $references, $isQuoted); } } return $output; } private static function parseQuotedScalar(string $scalar, int &$i = 0): string { if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } $output = substr($match[0], 1, \strlen($match[0]) - 2); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { $output = $unescaper->unescapeDoubleQuotedString($output); } else { $output = $unescaper->unescapeSingleQuotedString($output); } $i += \strlen($match[0]); return $output; } private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array { $output = []; $len = \strlen($sequence); ++$i; while ($i < $len) { if (']' === $sequence[$i]) { return $output; } if (',' === $sequence[$i] || ' ' === $sequence[$i]) { ++$i; continue; } $tag = self::parseTag($sequence, $i, $flags); switch ($sequence[$i]) { case '[': $value = self::parseSequence($sequence, $flags, $i, $references); break; case '{': $value = self::parseMapping($sequence, $flags, $i, $references); break; default: $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { try { $pos = 0; $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); } catch (\InvalidArgumentException $e) { } } if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { $references[$matches['ref']] = $matches['value']; $value = $matches['value']; } --$i; } if (null !== $tag && '' !== $tag) { $value = new TaggedValue($tag, $value); } $output[] = $value; ++$i; } throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); } private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []) { $output = []; $len = \strlen($mapping); ++$i; $allowOverwrite = false; while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': case "\n": ++$i; continue 2; case '}': if (self::$objectForMap) { return (object) $output; } return $output; } $offsetBeforeKeyParsing = $i; $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); if ($offsetBeforeKeyParsing === $i) { throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); } if ('!php/const' === $key) { $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); $key = self::evaluateScalar($key, $flags); } if (false === $i = strpos($mapping, ':', $i)) { break; } if (!$isKeyQuoted) { $evaluatedKey = self::evaluateScalar($key, $flags, $references); if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); } } if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); } if ('<<' === $key) { $allowOverwrite = true; } while ($i < $len) { if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { ++$i; continue; } $tag = self::parseTag($mapping, $i, $flags); switch ($mapping[$i]) { case '[': $value = self::parseSequence($mapping, $flags, $i, $references); if ('<<' === $key) { foreach ($value as $parsedValue) { $output += $parsedValue; } } elseif ($allowOverwrite || !isset($output[$key])) { if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; case '{': $value = self::parseMapping($mapping, $flags, $i, $references); if ('<<' === $key) { $output += $value; } elseif ($allowOverwrite || !isset($output[$key])) { if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; default: $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); if ('<<' === $key) { $output += $value; } elseif ($allowOverwrite || !isset($output[$key])) { if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { $references[$matches['ref']] = $matches['value']; $value = $matches['value']; } if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } --$i; } ++$i; continue 2; } } throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); } private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null) { $isQuotedString = false; $scalar = trim($scalar); $scalarLower = strtolower($scalar); if (0 === strpos($scalar, '*')) { if (false !== $pos = strpos($scalar, '#')) { $value = substr($scalar, 1, $pos - 2); } else { $value = substr($scalar, 1); } if (false === $value || '' === $value) { throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if (!\array_key_exists($value, $references)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } return $references[$value]; } switch (true) { case 'null' === $scalarLower: case '' === $scalar: case '~' === $scalar: return null; case 'true' === $scalarLower: return true; case 'false' === $scalarLower: return false; case '!' === $scalar[0]: switch (true) { case 0 === strpos($scalar, '!!str '): $s = (string) substr($scalar, 6); if (\in_array($s[0] ?? '', ['"', "'"], true)) { $isQuotedString = true; $s = self::parseQuotedScalar($s); } return $s; case 0 === strpos($scalar, '! '): return substr($scalar, 2); case 0 === strpos($scalar, '!php/object'): if (self::$objectSupport) { if (!isset($scalar[12])) { return false; } return unserialize(self::parseScalar(substr($scalar, 12))); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return null; case 0 === strpos($scalar, '!php/const'): if (self::$constantSupport) { if (!isset($scalar[11])) { return ''; } $i = 0; if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { return \constant($const); } throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (self::$exceptionOnInvalidType) { throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return null; case 0 === strpos($scalar, '!!float '): return (float) substr($scalar, 8); case 0 === strpos($scalar, '!!binary '): return self::evaluateBinaryScalar(substr($scalar, 9)); default: throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); } case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { $scalar = str_replace('_', '', $scalar); } switch (true) { case ctype_digit($scalar): if (preg_match('/^0[0-7]+$/', $scalar)) { return octdec($scalar); } $cast = (int) $scalar; return ($scalar === (string) $cast) ? $cast : $scalar; case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): if (preg_match('/^-0[0-7]+$/', $scalar)) { return -octdec(substr($scalar, 1)); } $cast = (int) $scalar; return ($scalar === (string) $cast) ? $cast : $scalar; case is_numeric($scalar): case Parser::preg_match(self::getHexRegex(), $scalar): $scalar = str_replace('_', '', $scalar); return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -log(0); case '-.inf' === $scalarLower: return log(0); case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): return (float) str_replace('_', '', $scalar); case Parser::preg_match(self::getTimestampRegex(), $scalar): $time = new \DateTime($scalar, new \DateTimeZone('UTC')); if (Yaml::PARSE_DATETIME & $flags) { return $time; } try { if (false !== $scalar = $time->getTimestamp()) { return $scalar; } } catch (\ValueError $e) { } return $time->format('U'); } } return (string) $scalar; } private static function parseTag(string $value, int &$i, int $flags): ?string { if ('!' !== $value[$i]) { return null; } $tagLength = strcspn($value, " \t\n[]{},", $i + 1); $tag = substr($value, $i + 1, $tagLength); $nextOffset = $i + $tagLength + 1; $nextOffset += strspn($value, ' ', $nextOffset); if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { return null; } $i = $nextOffset; if ('' !== $tag && '!' === $tag[0]) { throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if ('' !== $tag && !isset($value[$i])) { throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { return $tag; } throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } public static function evaluateBinaryScalar(string $scalar): string { $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); if (0 !== (\strlen($parsedBinaryData) % 4)) { throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return base64_decode($parsedBinaryData, true); } private static function isBinaryString(string $value) { return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); } private static function getTimestampRegex(): string { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } private static function getHexRegex(): string { return '~^0x[0-9a-f_]++$~i'; } } tag = $tag; $this->value = $value; } public function getTag(): string { return $this->tag; } public function getValue() { return $this->value; } } indentation = $indentation; } public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string { $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; $dumpObjectAsInlineMap = true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { $dumpObjectAsInlineMap = empty((array) $input); } if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); } else { $dumpAsMap = Inline::isHash($input); foreach ($input as $key => $value) { if ('' !== $output && "\n" !== $output[-1]) { $output .= "\n"; } if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) { $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { $blockChompingIndicator = '+'; } elseif ("\n" === $value[-1]) { $blockChompingIndicator = ''; } else { $blockChompingIndicator = '-'; } $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); foreach (explode("\n", $value) as $row) { if ('' === $row) { $output .= "\n"; } else { $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); } } continue; } if ($value instanceof TaggedValue) { $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; $output .= sprintf(' |%s', $blockIndentationIndicator); foreach (explode("\n", $value->getValue()) as $row) { $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); } continue; } if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; } else { $output .= "\n"; $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); } continue; } $dumpObjectAsInlineMap = true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { $dumpObjectAsInlineMap = empty((array) $value); } $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); $output .= sprintf('%s%s%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) ).($willBeInlined ? "\n" : ''); } } return $output; } } = ! % @ ` \p{Zs}]/xu', $value); } public static function escapeWithSingleQuotes(string $value): string { return sprintf("'%s'", str_replace('\'', '\'\'', $value)); } } directoryIteratorProvider = $directoryIteratorProvider; $this->isReadableProvider = $isReadableProvider; } protected function configure() { $this ->setDescription('Lint a file and outputs encountered errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags') ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT the first encountered syntax error. You can validates YAML contents passed from STDIN: cat filename | php %command.full_name% - You can also validate the syntax of a file: php %command.full_name% filename Or of a whole directory: php %command.full_name% dirname php %command.full_name% dirname --format=json EOF ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $filenames = (array) $input->getArgument('filename'); $this->format = $input->getOption('format'); $this->displayCorrectFiles = $output->isVerbose(); $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; if (['-'] === $filenames) { return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); } if (!$filenames) { if (0 === ftell(\STDIN)) { @trigger_error('Piping content from STDIN to the "lint:yaml" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED); return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); } throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); } $filesInfo = []; foreach ($filenames as $filename) { if (!$this->isReadable($filename)) { throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); } foreach ($this->getFiles($filename) as $file) { $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); } } return $this->display($io, $filesInfo); } private function validate(string $content, int $flags, string $file = null) { $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (\E_USER_DEPRECATED === $level) { throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); } return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; }); try { $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); } catch (ParseException $e) { return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; } finally { restore_error_handler(); } return ['file' => $file, 'valid' => true]; } private function display(SymfonyStyle $io, array $files): int { switch ($this->format) { case 'txt': return $this->displayTxt($io, $files); case 'json': return $this->displayJson($io, $files); default: throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } private function displayTxt(SymfonyStyle $io, array $filesInfo): int { $countFiles = \count($filesInfo); $erroredFiles = 0; $suggestTagOption = false; foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$erroredFiles; $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); $io->text(sprintf(' >> %s', $info['message'])); if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { $suggestTagOption = true; } } } if (0 === $erroredFiles) { $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); } else { $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); } return min($erroredFiles, 1); } private function displayJson(SymfonyStyle $io, array $filesInfo): int { $errors = 0; array_walk($filesInfo, function (&$v) use (&$errors) { $v['file'] = (string) $v['file']; if (!$v['valid']) { ++$errors; } if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; } }); $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); return min($errors, 1); } private function getFiles(string $fileOrDirectory): iterable { if (is_file($fileOrDirectory)) { yield new \SplFileInfo($fileOrDirectory); return; } foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { continue; } yield $file; } } private function getParser(): Parser { if (!$this->parser) { $this->parser = new Parser(); } return $this->parser; } private function getDirectoryIterator(string $directory): iterable { $default = function ($directory) { return new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY ); }; if (null !== $this->directoryIteratorProvider) { return ($this->directoryIteratorProvider)($directory, $default); } return $default($directory); } private function isReadable(string $fileOrDirectory): bool { $default = function ($fileOrDirectory) { return is_readable($fileOrDirectory); }; if (null !== $this->isReadableProvider) { return ($this->isReadableProvider)($fileOrDirectory, $default); } return $default($fileOrDirectory); } } unescapeCharacter($match[0]); }; return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); } private function unescapeCharacter(string $value): string { switch ($value[1]) { case '0': return "\x0"; case 'a': return "\x7"; case 'b': return "\x8"; case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return "\xB"; case 'f': return "\xC"; case 'r': return "\r"; case 'e': return "\x1B"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': return "\xC2\x85"; case '_': return "\xC2\xA0"; case 'L': return "\xE2\x80\xA8"; case 'P': return "\xE2\x80\xA9"; case 'x': return self::utf8chr(hexdec(substr($value, 2, 2))); case 'u': return self::utf8chr(hexdec(substr($value, 2, 4))); case 'U': return self::utf8chr(hexdec(substr($value, 2, 8))); default: throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); } } private static function utf8chr(int $c): string { if (0x80 > $c %= 0x200000) { return \chr($c); } if (0x800 > $c) { return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); } return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); } } parsedFile = $parsedFile; $this->parsedLine = $parsedLine; $this->snippet = $snippet; $this->rawMessage = $message; $this->updateRepr(); parent::__construct($this->message, 0, $previous); } public function getSnippet() { return $this->snippet; } public function setSnippet($snippet) { $this->snippet = $snippet; $this->updateRepr(); } public function getParsedFile() { return $this->parsedFile; } public function setParsedFile($parsedFile) { $this->parsedFile = $parsedFile; $this->updateRepr(); } public function getParsedLine() { return $this->parsedLine; } public function setParsedLine($parsedLine) { $this->parsedLine = $parsedLine; $this->updateRepr(); } private function updateRepr() { $this->message = $this->rawMessage; $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); $dot = true; } if (null !== $this->parsedFile) { $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); } if ($this->parsedLine >= 0) { $this->message .= sprintf(' at line %d', $this->parsedLine); } if ($this->snippet) { $this->message .= sprintf(' (near "%s")', $this->snippet); } if ($dot) { $this->message .= '.'; } } } ![\w!.\/:-]+)'; public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; private $filename; private $offset = 0; private $totalNumberOfLines; private $lines = []; private $currentLineNb = -1; private $currentLine = ''; private $refs = []; private $skippedLineNumbers = []; private $locallySkippedLineNumbers = []; private $refsBeingParsed = []; public function parseFile(string $filename, int $flags = 0) { if (!is_file($filename)) { throw new ParseException(sprintf('File "%s" does not exist.', $filename)); } if (!is_readable($filename)) { throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); } $this->filename = $filename; try { return $this->parse(file_get_contents($filename), $flags); } finally { $this->filename = null; } } public function parse(string $value, int $flags = 0) { if (false === preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); } $this->refs = []; $mbEncoding = null; if (2 & (int) \ini_get('mbstring.func_overload')) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } try { $data = $this->doParse($value, $flags); } finally { if (null !== $mbEncoding) { mb_internal_encoding($mbEncoding); } $this->refsBeingParsed = []; $this->offset = 0; $this->lines = []; $this->currentLine = ''; $this->refs = []; $this->skippedLineNumbers = []; $this->locallySkippedLineNumbers = []; $this->totalNumberOfLines = null; } return $data; } private function doParse(string $value, int $flags) { $this->currentLineNb = -1; $this->currentLine = ''; $value = $this->cleanup($value); $this->lines = explode("\n", $value); $this->locallySkippedLineNumbers = []; if (null === $this->totalNumberOfLines) { $this->totalNumberOfLines = \count($this->lines); } if (!$this->moveToNextLine()) { return null; } $data = []; $context = null; $allowOverwrite = false; while ($this->isCurrentLineEmpty()) { if (!$this->moveToNextLine()) { return null; } } if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { return new TaggedValue($tag, ''); } do { if ($this->isCurrentLineEmpty()) { continue; } if ("\t" === $this->currentLine[0]) { throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); $isRef = $mergeNode = false; if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $context = 'sequence'; if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; } if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } if (isset($values['value']) && 0 === strpos(ltrim($values['value'], ' '), '-')) { $currentLineNumber = $this->getRealCurrentLineNb(); $sequenceIndentation = \strlen($values['leadspaces']) + 1; $sequenceYaml = substr($this->currentLine, $sequenceIndentation); $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { $data[] = new TaggedValue( $subTag, $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) ); } else { if ( isset($values['leadspaces']) && ( '!' === $values['value'][0] || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) ) ) { $block = $values['value']; if ($this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); } $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); } else { $data[] = $this->parseValue($values['value'], $flags, $context); } } if ($isRef) { $this->refs[$isRef] = end($data); array_pop($this->refsBeingParsed); } } elseif ( self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) ) { if ($context && 'sequence' == $context) { throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); } $context = 'mapping'; try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (!\is_string($key) && !\is_int($key)) { throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } if (\is_float($key)) { $key = (string) $key; } if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { $mergeNode = true; $allowOverwrite = true; if (isset($values['value'][0]) && '*' === $values['value'][0]) { $refName = substr(rtrim($values['value']), 1); if (!\array_key_exists($refName, $this->refs)) { if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $refValue = $this->refs[$refName]; if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { $refValue = (array) $refValue; } if (!\is_array($refValue)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $data += $refValue; } else { if (isset($values['value']) && '' !== $values['value']) { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { $parsed = (array) $parsed; } if (!\is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } if (isset($parsed[0])) { foreach ($parsed as $parsedItem) { if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { $parsedItem = (array) $parsedItem; } if (!\is_array($parsedItem)) { throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); } $data += $parsedItem; } } else { $data += $parsed; } } } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; } $subTag = null; if ($mergeNode) { } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { if ($allowOverwrite || !isset($data[$key])) { if (null !== $subTag) { $data[$key] = new TaggedValue($subTag, ''); } else { $data[$key] = null; } } else { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { $realCurrentLineNbKey = $this->getRealCurrentLineNb(); $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); if ('<<' === $key) { $this->refs[$refMatches['ref']] = $value; if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { $value = (array) $value; } $data += $value; } elseif ($allowOverwrite || !isset($data[$key])) { if (null !== $subTag) { $data[$key] = new TaggedValue($subTag, $value); } else { $data[$key] = $value; } } else { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); } } } else { $value = $this->parseValue(rtrim($values['value']), $flags, $context); if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } else { throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } if ($isRef) { $this->refs[$isRef] = $data[$key]; array_pop($this->refsBeingParsed); } } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } elseif ('{' === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return $parsedMapping; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } elseif ('[' === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return $parsedSequence; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } else { if ('---' === $this->currentLine) { throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); } if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } if (\is_string($value) && $this->lines[0] === trim($value)) { try { $value = Inline::parse($this->lines[0], $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } return $value; } if (0 === $this->currentLineNb) { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = false; $value = ''; foreach ($this->lines as $line) { if ('' !== ltrim($line) && '#' === ltrim($line)[0]) { continue; } if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } if (false !== strpos($line, ': ')) { @trigger_error('Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0.', \E_USER_DEPRECATED); } if ('' === trim($line)) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } if ('' !== trim($line) && '\\' === substr($line, -1)) { $value .= ltrim(substr($line, 0, -1)); } elseif ('' !== trim($line)) { $value .= trim($line); } if ('' === trim($line)) { $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; } elseif ('\\' === substr($line, -1)) { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = true; } else { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = false; } } try { return Inline::parse(trim($value)); } catch (ParseException $e) { } } throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } while ($this->moveToNextLine()); if (null !== $tag) { $data = new TaggedValue($tag, $data); } if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) { $object = new \stdClass(); foreach ($data as $key => $value) { $object->$key = $value; } $data = $object; } return empty($data) ? null : $data; } private function parseBlock(int $offset, string $yaml, int $flags) { $skippedLineNumbers = $this->skippedLineNumbers; foreach ($this->locallySkippedLineNumbers as $lineNumber) { if ($lineNumber < $offset) { continue; } $skippedLineNumbers[] = $lineNumber; } $parser = new self(); $parser->offset = $offset; $parser->totalNumberOfLines = $this->totalNumberOfLines; $parser->skippedLineNumbers = $skippedLineNumbers; $parser->refs = &$this->refs; $parser->refsBeingParsed = $this->refsBeingParsed; return $parser->doParse($yaml, $flags); } public function getRealCurrentLineNb(): int { $realCurrentLineNumber = $this->currentLineNb + $this->offset; foreach ($this->skippedLineNumbers as $skippedLineNumber) { if ($skippedLineNumber > $realCurrentLineNumber) { break; } ++$realCurrentLineNumber; } return $realCurrentLineNumber; } private function getCurrentLineIndentation(): int { return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); } private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string { $oldLineIndentation = $this->getCurrentLineIndentation(); if (!$this->moveToNextLine()) { return ''; } if (null === $indentation) { $newIndent = null; $movements = 0; do { $EOF = false; if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } else { $newIndent = $this->getCurrentLineIndentation(); } } while (!$EOF && null === $newIndent); for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } else { $newIndent = $indentation; } $data = []; if ($this->getCurrentLineIndentation() >= $newIndent) { $data[] = substr($this->currentLine, $newIndent ?? 0); } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { $data[] = $this->currentLine; } else { $this->moveToPreviousLine(); return ''; } if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { $this->moveToPreviousLine(); return ''; } $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); $isItComment = $this->isCurrentLineComment(); while ($this->moveToNextLine()) { if ($isItComment && !$isItUnindentedCollection) { $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); $isItComment = $this->isCurrentLineComment(); } $indent = $this->getCurrentLineIndentation(); if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; } if ($this->isCurrentLineBlank()) { $data[] = substr($this->currentLine, $newIndent); continue; } if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } elseif ($this->isCurrentLineComment()) { $data[] = $this->currentLine; } elseif (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return implode("\n", $data); } private function hasMoreLines(): bool { return (\count($this->lines) - 1) > $this->currentLineNb; } private function moveToNextLine(): bool { if ($this->currentLineNb >= \count($this->lines) - 1) { return false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return true; } private function moveToPreviousLine(): bool { if ($this->currentLineNb < 1) { return false; } $this->currentLine = $this->lines[--$this->currentLineNb]; return true; } private function parseValue(string $value, int $flags, string $context) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { $value = substr($value, 1, $pos - 2); } else { $value = substr($value, 1); } if (!\array_key_exists($value, $this->refs)) { if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); } return $this->refs[$value]; } if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { $modifiers = $matches['modifiers'] ?? ''; $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); if ('' !== $matches['tag'] && '!' !== $matches['tag']) { if ('!!binary' === $matches['tag']) { return Inline::evaluateBinaryScalar($data); } return new TaggedValue(substr($matches['tag'], 1), $data); } return $data; } try { if ('' !== $value && '{' === $value[0]) { $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); } elseif ('' !== $value && '[' === $value[0]) { $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); } switch ($value[0] ?? '') { case '"': case "'": $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); } return $parsedValue; default: $lines = []; while ($this->moveToNextLine()) { if (0 === $this->getCurrentLineIndentation()) { $this->moveToPreviousLine(); break; } $lines[] = trim($this->currentLine); } for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { if ('' === $lines[$i]) { $value .= "\n"; $previousLineBlank = true; } elseif ($previousLineBlank) { $value .= $lines[$i]; $previousLineBlank = false; } else { $value .= ' '.$lines[$i]; $previousLineBlank = false; } } Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $parsedValue = Inline::parse($value, $flags, $this->refs); if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); } return $parsedValue; } } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string { $notEOF = $this->moveToNextLine(); if (!$notEOF) { return ''; } $isCurrentLineBlank = $this->isCurrentLineBlank(); $blockLines = []; while ($notEOF && $isCurrentLineBlank) { if ($notEOF = $this->moveToNextLine()) { $blockLines[] = ''; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } if (0 === $indentation) { $currentLineLength = \strlen($this->currentLine); for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { ++$indentation; } } if ($indentation > 0) { $pattern = sprintf('/^ {%d}(.*)$/', $indentation); while ( $notEOF && ( $isCurrentLineBlank || self::preg_match($pattern, $this->currentLine, $matches) ) ) { if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { $blockLines[] = substr($this->currentLine, $indentation); } elseif ($isCurrentLineBlank) { $blockLines[] = ''; } else { $blockLines[] = $matches[1]; } if ($notEOF = $this->moveToNextLine()) { $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { $blockLines[] = ''; } if ($notEOF) { $blockLines[] = ''; $this->moveToPreviousLine(); } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { $blockLines[] = ''; } if ('>' === $style) { $text = ''; $previousLineIndented = false; $previousLineBlank = false; for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { if ('' === $blockLines[$i]) { $text .= "\n"; $previousLineIndented = false; $previousLineBlank = true; } elseif (' ' === $blockLines[$i][0]) { $text .= "\n".$blockLines[$i]; $previousLineIndented = true; $previousLineBlank = false; } elseif ($previousLineIndented) { $text .= "\n".$blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } elseif ($previousLineBlank || 0 === $i) { $text .= $blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } else { $text .= ' '.$blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } } } else { $text = implode("\n", $blockLines); } if ('' === $chomping) { $text = preg_replace('/\n+$/', "\n", $text); } elseif ('-' === $chomping) { $text = preg_replace('/\n+$/', '', $text); } return $text; } private function isNextLineIndented(): bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; do { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); if ($EOF) { return false; } $ret = $this->getCurrentLineIndentation() > $currentIndentation; for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } return $ret; } private function isCurrentLineEmpty(): bool { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } private function isCurrentLineBlank(): bool { return '' == trim($this->currentLine, ' '); } private function isCurrentLineComment(): bool { $ltrimmedLine = ltrim($this->currentLine, ' '); return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; } private function isCurrentLineLastLineInDocument(): bool { return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); } private function cleanup(string $value): string { $value = str_replace(["\r\n", "\r"], "\n", $value); $count = 0; $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); $this->offset += $count; $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); if (1 === $count) { $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; } $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); if (1 === $count) { $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; $value = preg_replace('#\.\.\.\s*$#', '', $value); } return $value; } private function isNextLineUnIndentedCollection(): bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; do { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); if ($EOF) { return false; } $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } return $ret; } private function isStringUnIndentedCollectionItem(): bool { return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); } public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int { if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { switch (preg_last_error()) { case \PREG_INTERNAL_ERROR: $error = 'Internal PCRE error.'; break; case \PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case \PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case \PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case \PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Error.'; } throw new ParseException($error); } return $ret; } private function trimTag(string $value): string { if ('!' === $value[0]) { return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); } return $value; } private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string { if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { return null; } if ($nextLineCheck && !$this->isNextLineIndented()) { return null; } $tag = substr($matches['tag'], 1); if ($tag && '!' === $tag[0]) { throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } if (Yaml::PARSE_CUSTOM_TAGS & $flags) { return $tag; } throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } private function lexInlineQuotedString(int &$cursor = 0): string { $quotation = $this->currentLine[$cursor]; $value = $quotation; ++$cursor; $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; $lineNumber = 0; do { if (++$lineNumber > 1) { $cursor += strspn($this->currentLine, ' ', $cursor); } if ($this->isCurrentLineBlank()) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } for (; \strlen($this->currentLine) > $cursor; ++$cursor) { switch ($this->currentLine[$cursor]) { case '\\': if ("'" === $quotation) { $value .= '\\'; } elseif (isset($this->currentLine[++$cursor])) { $value .= '\\'.$this->currentLine[$cursor]; } break; case $quotation: ++$cursor; if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { $value .= "''"; break; } return $value.$quotation; default: $value .= $this->currentLine[$cursor]; } } if ($this->isCurrentLineBlank()) { $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; } elseif ('\\' === $this->currentLine[-1]) { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = true; } else { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = false; } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); throw new ParseException('Malformed inline YAML string.'); } private function lexUnquotedString(int &$cursor): string { $offset = $cursor; $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); if ($cursor === $offset) { throw new ParseException('Malformed unquoted YAML string.'); } return substr($this->currentLine, $offset, $cursor - $offset); } private function lexInlineMapping(int &$cursor = 0): string { return $this->lexInlineStructure($cursor, '}'); } private function lexInlineSequence(int &$cursor = 0): string { return $this->lexInlineStructure($cursor, ']'); } private function lexInlineStructure(int &$cursor, string $closingTag): string { $value = $this->currentLine[$cursor]; ++$cursor; do { $this->consumeWhitespaces($cursor); while (isset($this->currentLine[$cursor])) { switch ($this->currentLine[$cursor]) { case '"': case "'": $value .= $this->lexInlineQuotedString($cursor); break; case ':': case ',': $value .= $this->currentLine[$cursor]; ++$cursor; break; case '{': $value .= $this->lexInlineMapping($cursor); break; case '[': $value .= $this->lexInlineSequence($cursor); break; case $closingTag: $value .= $this->currentLine[$cursor]; ++$cursor; return $value; case '#': break 2; default: $value .= $this->lexUnquotedString($cursor); } if ($this->consumeWhitespaces($cursor)) { $value .= ' '; } } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); throw new ParseException('Malformed inline YAML string.'); } private function consumeWhitespaces(int &$cursor): bool { $whitespacesConsumed = 0; do { $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); $whitespacesConsumed += $whitespaceOnlyTokenLength; $cursor += $whitespaceOnlyTokenLength; if (isset($this->currentLine[$cursor])) { return 0 < $whitespacesConsumed; } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); return 0 < $whitespacesConsumed; } } parseFile($filename, $flags); } public static function parse(string $input, int $flags = 0) { $yaml = new Parser(); return $yaml->parse($input, $flags); } public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string { $yaml = new Dumper($indent); return $yaml->dump($input, $inline, 0, $flags); } } = 70200) { return; } if (!defined('PHP_FLOAT_DIG')) { define('PHP_FLOAT_DIG', 15); } if (!defined('PHP_FLOAT_EPSILON')) { define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); } if (!defined('PHP_FLOAT_MIN')) { define('PHP_FLOAT_MIN', 2.2250738585072E-308); } if (!defined('PHP_FLOAT_MAX')) { define('PHP_FLOAT_MAX', 1.7976931348623157E+308); } if (!defined('PHP_OS_FAMILY')) { define('PHP_OS_FAMILY', p\Php72::php_os_family()); } if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } } if (!function_exists('stream_isatty')) { function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } } if (!function_exists('utf8_encode')) { function utf8_encode($string) { return p\Php72::utf8_encode($string); } } if (!function_exists('utf8_decode')) { function utf8_decode($string) { return p\Php72::utf8_decode($string); } } if (!function_exists('spl_object_id')) { function spl_object_id($object) { return p\Php72::spl_object_id($object); } } if (!function_exists('mb_ord')) { function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); } } if (!function_exists('mb_chr')) { function mb_chr($codepoint, $encoding = null) { return p\Php72::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); } } > 1, $j = 0; $i < $len; ++$i, ++$j) { switch (true) { case $s[$i] < "\x80": $s[$j] = $s[$i]; break; case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; } } return substr($s, 0, $j); } public static function utf8_decode($s) { $s = (string) $s; $len = \strlen($s); for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { switch ($s[$i] & "\xF0") { case "\xC0": case "\xD0": $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); $s[$j] = $c < 256 ? \chr($c) : '?'; break; case "\xF0": ++$i; case "\xE0": $s[$j] = '?'; $i += 2; break; default: $s[$j] = $s[$i]; } } return substr($s, 0, $j); } public static function php_os_family() { if ('\\' === \DIRECTORY_SEPARATOR) { return 'Windows'; } $map = [ 'Darwin' => 'Darwin', 'DragonFly' => 'BSD', 'FreeBSD' => 'BSD', 'NetBSD' => 'BSD', 'OpenBSD' => 'BSD', 'Linux' => 'Linux', 'SunOS' => 'Solaris', ]; return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown'; } public static function spl_object_id($object) { if (null === self::$hashMask) { self::initHashMask(); } if (null === $hash = spl_object_hash($object)) { return; } return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); } public static function sapi_windows_vt100_support($stream, $enable = null) { if (!\is_resource($stream)) { trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); return false; } $meta = stream_get_meta_data($stream); if ('STDIO' !== $meta['stream_type']) { trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING); return false; } if (false === $enable || !self::stream_isatty($stream)) { return false; } $meta = array_map('strtolower', $meta); $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; return !$stdin && (false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM') || 'Hyper' === getenv('TERM_PROGRAM')); } public static function stream_isatty($stream) { if (!\is_resource($stream)) { trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); return false; } if ('\\' === \DIRECTORY_SEPARATOR) { $stat = @fstat($stream); return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } return \function_exists('posix_isatty') && @posix_isatty($stream); } private static function initHashMask() { $obj = (object) []; self::$hashMask = -1; $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { $frame['line'] = 0; break; } } if (!empty($frame['line'])) { ob_start(); debug_zval_dump($obj); self::$hashMask = (int) substr(ob_get_clean(), 17); } self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); } 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 = $encoding ?? mb_internal_encoding()) { $s = mb_convert_encoding($s, $encoding, 'UTF-8'); } return $s; } public static function mb_ord($s, $encoding = null) { if (null === $encoding) { $s = mb_convert_encoding($s, 'UTF-8'); } elseif ('UTF-8' !== $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; } } propagationStopped; } public function stopPropagation(): void { $this->propagationStopped = true; } } } else { class Event { private $propagationStopped = false; public function isPropagationStopped(): bool { return $this->propagationStopped; } public function stopPropagation(): void { $this->propagationStopped = true; } } } dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); $this->stopwatch = $stopwatch; $this->logger = $logger; $this->wrappedListeners = []; $this->orphanedEvents = []; $this->requestStack = $requestStack; } public function addListener($eventName, $listener, $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } public function addSubscriber(EventSubscriberInterface $subscriber) { $this->dispatcher->addSubscriber($subscriber); } public function removeListener($eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { $listener = $wrappedListener; unset($this->wrappedListeners[$eventName][$index]); break; } } } return $this->dispatcher->removeListener($eventName, $listener); } public function removeSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->removeSubscriber($subscriber); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); } } } return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } public function dispatch($event) { if (null === $this->callStack) { $this->callStack = new \SplObjectStorage(); } $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; if (\is_object($event)) { $eventName = $eventName ?? \get_class($event); } else { @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; if (!$event instanceof Event) { throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of "%s", "%s" given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event))); } } if (null !== $this->logger && ($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } $this->preProcess($eventName); try { $this->beforeDispatch($eventName, $event); try { $e = $this->stopwatch->start($eventName, 'section'); try { $this->dispatcher->dispatch($event, $eventName); } finally { if ($e->isStarted()) { $e->stop(); } } } finally { $this->afterDispatch($eventName, $event); } } finally { $this->currentRequestHash = $currentRequestHash; $this->postProcess($eventName); } return $event; } public function getCalledListeners() { if (null === $this->callStack) { return []; } $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; $called = []; foreach ($this->callStack as $listener) { [$eventName, $requestHash] = $this->callStack->getInfo(); if (null === $hash || $hash === $requestHash) { $called[] = $listener->getInfo($eventName); } } return $called; } public function getNotCalledListeners() { try { $allListeners = $this->getListeners(); } catch (\Exception $e) { if (null !== $this->logger) { $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); } return []; } $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null; $calledListeners = []; if (null !== $this->callStack) { foreach ($this->callStack as $calledListener) { [, $requestHash] = $this->callStack->getInfo(); if (null === $hash || $hash === $requestHash) { $calledListeners[] = $calledListener->getWrappedListener(); } } } $notCalled = []; foreach ($allListeners as $eventName => $listeners) { foreach ($listeners as $listener) { if (!\in_array($listener, $calledListeners, true)) { if (!$listener instanceof WrappedListener) { $listener = new WrappedListener($listener, null, $this->stopwatch, $this); } $notCalled[] = $listener->getInfo($eventName); } } } uasort($notCalled, [$this, 'sortNotCalledListeners']); return $notCalled; } public function getOrphanedEvents(): array { if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) { return $this->orphanedEvents[spl_object_hash($request)] ?? []; } if (!$this->orphanedEvents) { return []; } return array_merge(...array_values($this->orphanedEvents)); } public function reset() { $this->callStack = null; $this->orphanedEvents = []; $this->currentRequestHash = ''; } public function __call($method, $arguments) { return $this->dispatcher->{$method}(...$arguments); } protected function beforeDispatch(string $eventName, $event) { $this->preDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); } protected function afterDispatch(string $eventName, $event) { $this->postDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event)); } protected function preDispatch($eventName, Event $event) { } protected function postDispatch($eventName, Event $event) { } private function preProcess(string $eventName) { if (!$this->dispatcher->hasListeners($eventName)) { $this->orphanedEvents[$this->currentRequestHash][] = $eventName; return; } foreach ($this->dispatcher->getListeners($eventName) as $listener) { $priority = $this->getListenerPriority($eventName, $listener); $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); $this->wrappedListeners[$eventName][] = $wrappedListener; $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $wrappedListener, $priority); $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); } } private function postProcess(string $eventName) { unset($this->wrappedListeners[$eventName]); $skipped = false; foreach ($this->dispatcher->getListeners($eventName) as $listener) { if (!$listener instanceof WrappedListener) { continue; } $priority = $this->getListenerPriority($eventName, $listener); $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); if (null !== $this->logger) { $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; } if ($listener->wasCalled()) { if (null !== $this->logger) { $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); } } else { $this->callStack->detach($listener); } if (null !== $this->logger && $skipped) { $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); } if ($listener->stoppedPropagation()) { if (null !== $this->logger) { $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); } $skipped = true; } } } private function sortNotCalledListeners(array $a, array $b) { if (0 !== $cmp = strcmp($a['event'], $b['event'])) { return $cmp; } if (\is_int($a['priority']) && !\is_int($b['priority'])) { return 1; } if (!\is_int($a['priority']) && \is_int($b['priority'])) { return -1; } if ($a['priority'] === $b['priority']) { return 0; } if ($a['priority'] > $b['priority']) { return -1; } return 1; } } listener = $listener; $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; $this->stoppedPropagation = false; if (\is_array($listener)) { $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; $this->pretty = $this->name.'::'.$listener[1]; } elseif ($listener instanceof \Closure) { $r = new \ReflectionFunction($listener); if (str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; } elseif ($class = $r->getClosureScopeClass()) { $this->name = $class->name; $this->pretty = $this->name.'::'.$r->name; } else { $this->pretty = $this->name = $r->name; } } elseif (\is_string($listener)) { $this->pretty = $this->name = $listener; } else { $this->name = \get_class($listener); $this->pretty = $this->name.'::__invoke'; } if (null !== $name) { $this->name = $name; } if (null === self::$hasClassStub) { self::$hasClassStub = class_exists(ClassStub::class); } } public function getWrappedListener() { return $this->listener; } public function wasCalled() { return $this->called; } public function stoppedPropagation() { return $this->stoppedPropagation; } public function getPretty() { return $this->pretty; } public function getInfo($eventName) { if (null === $this->stub) { $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; } return [ 'event' => $eventName, 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), 'pretty' => $this->pretty, 'stub' => $this->stub, ]; } public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) { if ($event instanceof LegacyEventProxy) { $event = $event->getEvent(); } $dispatcher = $this->dispatcher ?: $dispatcher; $this->called = true; $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); $e = $this->stopwatch->start($this->name, 'event_listener'); ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); if ($e->isStarted()) { $e->stop(); } if (($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) { $this->stoppedPropagation = true; } } } dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } public function dispatch($event) { $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; if (\is_scalar($event)) { $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; } return $this->dispatcher->dispatch($event, $eventName); } public function addListener($eventName, $listener, $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeListener($eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } } event = $event; } public function getEvent() { return $this->event; } public function isPropagationStopped(): bool { if (!$this->event instanceof ContractsEvent && !$this->event instanceof StoppableEventInterface) { return false; } return $this->event->isPropagationStopped(); } public function stopPropagation() { if (!$this->event instanceof ContractsEvent) { return; } $this->event->stopPropagation(); } public function __call($name, $args) { return $this->event->{$name}(...$args); } } optimized = []; } } public function dispatch($event) { $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; if (\is_object($event)) { $eventName = $eventName ?? \get_class($event); } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED); $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; } else { throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); } if (null !== $this->optimized && null !== $eventName) { $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); } else { $listeners = $this->getListeners($eventName); } if ($listeners) { $this->callListeners($listeners, $eventName, $event); } return $event; } public function getListeners($eventName = null) { if (null !== $eventName) { if (empty($this->listeners[$eventName])) { return []; } if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } return $this->sorted[$eventName]; } foreach ($this->listeners as $eventName => $eventListeners) { if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } } return array_filter($this->sorted); } public function getListenerPriority($eventName, $listener) { if (empty($this->listeners[$eventName])) { return null; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); $v[1] = $v[1] ?? '__invoke'; } if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { return $priority; } } } return null; } public function hasListeners($eventName = null) { if (null !== $eventName) { return !empty($this->listeners[$eventName]); } foreach ($this->listeners as $eventListeners) { if ($eventListeners) { return true; } } return false; } public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName], $this->optimized[$eventName]); } public function removeListener($eventName, $listener) { if (empty($this->listeners[$eventName])) { return; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as $k => &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); $v[1] = $v[1] ?? '__invoke'; } if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); } } if (!$listeners) { unset($this->listeners[$eventName][$priority]); } } } public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (\is_string($params)) { $this->addListener($eventName, [$subscriber, $params]); } elseif (\is_string($params[0])) { $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } } public function removeSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (\is_array($params) && \is_array($params[0])) { foreach ($params as $listener) { $this->removeListener($eventName, [$subscriber, $listener[0]]); } } else { $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); } } } protected function callListeners(iterable $listeners, string $eventName, $event) { if ($event instanceof Event) { $this->doDispatch($listeners, $eventName, $event); return; } $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; foreach ($listeners as $listener) { if ($stoppable && $event->isPropagationStopped()) { break; } $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this); } } protected function doDispatch($listeners, $eventName, Event $event) { foreach ($listeners as $listener) { if ($event->isPropagationStopped()) { break; } $listener($event, $eventName, $this); } } private function sortListeners(string $eventName) { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { foreach ($listeners as $k => &$listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } $this->sorted[$eventName][] = $listener; } } } private function optimizeListeners(string $eventName): array { krsort($this->listeners[$eventName]); $this->optimized[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { foreach ($listeners as &$listener) { $closure = &$this->optimized[$eventName][]; if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $closure = static function (...$args) use (&$listener, &$closure) { if ($listener[0] instanceof \Closure) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } ($closure = \Closure::fromCallable($listener))(...$args); }; } else { $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); } } } return $this->optimized[$eventName]; } } dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; $this->eventAliasesParameter = $eventAliasesParameter; } public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') { $this->hotPathEvents = array_flip($hotPathEvents); $this->hotPathTagName = $tagName; return $this; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { return; } $aliases = []; if ($container->hasParameter($this->eventAliasesParameter)) { $aliases = $container->getParameter($this->eventAliasesParameter); } $definition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { foreach ($events as $event) { $priority = $event['priority'] ?? 0; if (!isset($event['event'])) { if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { continue; } $event['method'] = $event['method'] ?? '__invoke'; $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); } $event['event'] = $aliases[$event['event']] ?? $event['event']; if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace_callback([ '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { $event['method'] = '__invoke'; } } $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); if (isset($this->hotPathEvents[$event['event']])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } } } $extractingDispatcher = new ExtractingEventDispatcher(); foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { $def = $container->getDefinition($id); $class = $def->getClass(); if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(EventSubscriberInterface::class)) { throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); } $class = $r->name; ExtractingEventDispatcher::$aliases = $aliases; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); foreach ($extractingDispatcher->listeners as $args) { $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; $definition->addMethodCall('addListener', $args); if (isset($this->hotPathEvents[$args[0]])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } } $extractingDispatcher->listeners = []; ExtractingEventDispatcher::$aliases = []; } } private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string { if ( null === ($class = $container->getDefinition($id)->getClass()) || !($r = $container->getReflectionClass($class, false)) || !$r->hasMethod($method) || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType || $type->isBuiltin() || Event::class === ($name = $type->getName()) || LegacyEvent::class === $name ) { throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); } return $name; } } class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface { public $listeners = []; public static $aliases = []; public static $subscriber; public function addListener($eventName, $listener, $priority = 0) { $this->listeners[] = [$eventName, $listener[1], $priority]; } public static function getSubscribedEvents(): array { $events = []; foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { $events[self::$aliases[$eventName] ?? $eventName] = $params; } return $events; } } eventAliases = $eventAliases; $this->eventAliasesParameter = $eventAliasesParameter; } public function process(ContainerBuilder $container): void { $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; $container->setParameter( $this->eventAliasesParameter, array_merge($eventAliases, $this->eventAliases) ); } } getParameters()[1] ?? null; if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) { return $dispatcher; } @trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), \E_USER_DEPRECATED); $self = new self(); $self->dispatcher = $dispatcher; return $self; } public function dispatch($event) { $eventName = 1 < \func_num_args() ? func_get_arg(1) : null; if (\is_object($event)) { $eventName = $eventName ?? \get_class($event); } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) { @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), \E_USER_DEPRECATED); $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; } else { throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); } $listeners = $this->getListeners($eventName); $stoppable = $event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface; foreach ($listeners as $listener) { if ($stoppable && $event->isPropagationStopped()) { break; } $listener($event, $eventName, $this); } return $event; } public function addListener($eventName, $listener, $priority = 0) { return $this->dispatcher->addListener($eventName, $listener, $priority); } public function addSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->addSubscriber($subscriber); } public function removeListener($eventName, $listener) { return $this->dispatcher->removeListener($eventName, $listener); } public function removeSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->removeSubscriber($subscriber); } public function getListeners($eventName = null): array { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener): ?int { return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null): bool { return $this->dispatcher->hasListeners($eventName); } public function __call($method, $arguments) { return $this->dispatcher->{$method}(...$arguments); } } subject = $subject; $this->arguments = $arguments; } public function getSubject() { return $this->subject; } public function getArgument($key) { if ($this->hasArgument($key)) { return $this->arguments[$key]; } throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); } public function setArgument($key, $value) { $this->arguments[$key] = $value; return $this; } public function getArguments() { return $this->arguments; } public function setArguments(array $args = []) { $this->arguments = $args; return $this; } public function hasArgument($key) { return \array_key_exists($key, $this->arguments); } #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->getArgument($key); } #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->setArgument($key, $value); } #[\ReturnTypeWillChange] public function offsetUnset($key) { if ($this->hasArgument($key)) { unset($this->arguments[$key]); } } #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->hasArgument($key); } #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->arguments); } } propagationStopped; } public function stopPropagation() { $this->propagationStopped = true; } } 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; } $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); } return $services; } public function setContainer(ContainerInterface $container) { $this->container = $container; if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { return parent::setContainer($container); } return null; } } 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'); } } factories = $factories; } public function has($id) { return isset($this->factories[$id]); } public function get($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]); } } 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 { }; } } buildAndCacheFromFactory($className); } private function buildAndCacheFromFactory(string $className) { $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); $instance = $factory(); if ($this->isSafeToClone(new ReflectionClass($instance))) { self::$cachedCloneables[$className] = clone $instance; } return $instance; } private function buildFactory(string $className): callable { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return [$reflectionClass, 'newInstanceWithoutConstructor']; } $serializedString = sprintf( '%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return static function () use ($serializedString) { return unserialize($serializedString); }; } private function getReflectionClass(string $className): ReflectionClass { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } if (PHP_VERSION_ID >= 80100 && enum_exists($className, false)) { throw InvalidArgumentException::fromEnum($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void { set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line ); return true; }); try { $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); } finally { restore_error_handler(); } if ($error) { throw $error; } } private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void { try { unserialize($serializedString); } catch (Exception $exception) { throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } private function hasInternalAncestors(ReflectionClass $reflectionClass): bool { do { if ($reflectionClass->isInternal()) { return true; } $reflectionClass = $reflectionClass->getParentClass(); } while ($reflectionClass); return false; } private function isSafeToClone(ReflectionClass $reflectionClass): bool { return $reflectionClass->isCloneable() && ! $reflectionClass->hasMethod('__clone') && ! $reflectionClass->isSubclassOf(ArrayIterator::class); } } getName() ), 0, $exception ); } public static function fromUncleanUnSerialization( ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine ): self { return new self( sprintf( 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine ), 0, new Exception($errorString, $errorCode) ); } } getName() )); } public static function fromEnum(string $className): self { return new self(sprintf( 'The provided class "%s" is an enum, and cannot be instantiated', $className )); } } uriRetriever = $uriRetriever ?: new UriRetriever(); $this->uriResolver = $uriResolver ?: new UriResolver(); } public function getUriRetriever() { return $this->uriRetriever; } public function getUriResolver() { return $this->uriResolver; } public function addSchema($id, $schema = null) { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { $schema = $this->uriRetriever->retrieve($id); } if (is_array($schema)) { $schema = BaseConstraint::arrayToObjectRecursive($schema); } if (is_object($schema) && property_exists($schema, 'id')) { if ($schema->id == 'http://json-schema.org/draft-04/schema#') { $schema->properties->id->format = 'uri-reference'; } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } } $this->expandRefs($schema, $id); $this->schemas[$id] = $schema; } 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); } } public function getSchema($id) { if (!array_key_exists($id, $this->schemas)) { $this->addSchema($id); } return $this->schemas[$id]; } public function resolveRef($ref) { $jsonPointer = new JsonPointer($ref); $fileName = $jsonPointer->getFilename(); if (!strlen($fileName)) { throw new UnresolvableJsonPointerException(sprintf( "Could not resolve fragment '%s': no file is defined", $jsonPointer->getPropertyPathAsString() )); } $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; } 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; } } filename = $splitRef[0]; if (array_key_exists(1, $splitRef)) { $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); } } 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; } private function encodePropertyPaths() { return array_map( array($this, 'encodePath'), $this->getPropertyPaths() ); } private function decodePath($path) { return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); } private function encodePath($path) { return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25')); } public function getFilename() { return $this->filename; } public function getPropertyPaths() { return $this->propertyPaths; } public function withPropertyPaths(array $propertyPaths) { $new = clone $this; $new->propertyPaths = $propertyPaths; return $new; } public function getPropertyPathAsString() { return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); } public function __toString() { return $this->getFilename() . $this->getPropertyPathAsString(); } public function setFromDefault() { $this->fromDefault = true; } public function fromDefault() { return $this->fromDefault; } } 'package://dist/schema/json-schema-draft-$1.json' ); protected $allowedInvalidContentTypeEndpoints = array( 'http://json-schema.org/', 'https://json-schema.org/' ); protected $uriRetriever = null; private $schemaCache = array(); public function addInvalidContentTypeEndpoint($endpoint) { $this->allowedInvalidContentTypeEndpoints[] = $endpoint; } public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); if (is_null($contentType)) { 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)); } public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new FileGetContents()); } return $this->uriRetriever; } 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; } public function retrieve($uri, $baseUri = null, $translate = true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); $arParts = $resolver->parse($resolvedUri); if (isset($arParts['fragment'])) { unset($arParts['fragment']); $fetchUri = $resolver->generate($arParts); } if ($translate) { $fetchUri = $this->translate($fetchUri); } $jsonSchema = $this->loadSchema($fetchUri); $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { $jsonSchema->id = $resolvedUri; } return $jsonSchema; } 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; } public function setUriRetriever(UriRetrieverInterface $uriRetriever) { $this->uriRetriever = $uriRetriever; return $this; } 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; } 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; } 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); } public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } public function setTranslation($from, $to) { $this->translationMap[$from] = $to; } public function translate($uri) { foreach ($this->translationMap as $from => $to) { $uri = preg_replace($from, $to, $uri); } $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); return $uri; } } $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; } 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; } public function resolve($uri, $baseUri = null) { 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); } 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; } private static function normalizePath($path) { $path = preg_replace('|((?parse($uri); return !empty($components); } } contentType; } } messageBody = $response; if (!empty($http_response_header)) { $this->fetchContentType($http_response_header); } else { $this->contentType = null; } return $this->messageBody; } private function fetchContentType(array $headers) { foreach ($headers as $header) { if ($this->contentType = self::getContentTypeMatchInHeader($header)) { return true; } } return false; } protected static function getContentTypeMatchInHeader($header) { if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { return trim($match[1]); } return null; } } fetchMessageBody($response); $this->fetchContentType($response); curl_close($ch); return $this->messageBody; } private function fetchMessageBody($response) { preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); $this->messageBody = $match[1]; } protected function fetchContentType($response) { if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { $this->contentType = trim($match[1]); return true; } return false; } } schemas = $schemas; $this->contentType = $contentType; } 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]; } } object = $object; } public function current() { $this->initialize(); return $this->data[$this->position]; } public function next() { $this->initialize(); $this->position++; } public function key() { $this->initialize(); return $this->position; } public function valid() { $this->initialize(); return isset($this->data[$this->position]); } public function rewind() { $this->initialize(); $this->position = 0; } public function count() { $this->initialize(); return count($this->data); } private function initialize() { if (!$this->initialized) { $this->data = $this->buildDataFromObject($this->object); $this->initialized = true; } } 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; } private function getDataFromItem($item) { if (!is_object($item) && !is_array($item)) { return array(); } return is_object($item) ? get_object_vars($item) : $item; } } {$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); } private static function isAssociativeArray($arr) { return array_keys($arr) !== range(0, count($arr) - 1); } } {$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)); } } incrementPath($path ?: new JsonPointer(''), $i); if ($fromDefault) { $path->setFromDefault(); } $this->validateCommonProperties($value, $schema, $path, $i); $this->validateOfProperties($value, $schema, $path, ''); $this->validateTypes($value, $schema, $path, $i); } public function validateTypes(&$value, $schema, JsonPointer $path, $i = null) { if ($this->getTypeCheck()->isArray($value)) { $this->checkArray($value, $schema, $path, $i); } if (LooseTypeCheck::isObject($value)) { $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 ); } if (is_string($value)) { $this->checkString($value, $schema, $path, $i); } if (is_numeric($value)) { $this->checkNumber($value, $schema, $path, $i); } if (isset($schema->enum)) { $this->checkEnum($value, $schema, $path, $i); } } protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '') { 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); } } if (!$path->fromDefault()) { $this->applyDefaultValues($value, $schema, $path); } if ($this->getTypeCheck()->isObject($value)) { if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) { 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)) { if ($schema->required && $value instanceof self) { $propertyPaths = $path->getPropertyPaths(); $propertyName = end($propertyPaths); $this->addError( $path, 'The property ' . $propertyName . ' is required', 'required' ); } } else { if ($value instanceof self) { return; } } } if (!($value instanceof self)) { $this->checkType($value, $schema, $path, $i); } if (isset($schema->disallow)) { $initErrors = $this->getErrors(); $typeSchema = new \stdClass(); $typeSchema->type = $schema->disallow; $this->checkType($value, $typeSchema, $path); 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 (count($this->getErrors()) == count($initErrors)) { $this->addError($path, 'Matched a schema which it should not', 'not'); } else { $this->errors = $initErrors; } } if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { $this->validateDependencies($value, $schema->dependencies, $path); } } private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) { if (!$requiredOnly) { return true; } if ( $name !== null && isset($parentSchema->required) && is_array($parentSchema->required) && in_array($name, $parentSchema->required) ) { return true; } if (isset($schema->required) && !is_array($schema->required) && $schema->required) { return true; } return false; } protected function applyDefaultValues(&$value, $schema, $path) { if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { return; } $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { 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) ) { 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); } 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_object($schema->default) ? clone $schema->default : $schema->default; $path->setFromDefault(); } } protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') { 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) { } } if ($matchedSchemas !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError($path, 'Failed to match exactly one schema', 'oneOf'); } else { $this->errors = $startErrors; } } } protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') { foreach ($dependencies as $key => $dependency) { if ($this->getTypeCheck()->propertyExists($value, $key)) { if (is_string($dependency)) { if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); } } elseif (is_array($dependency)) { 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)) { $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; } } 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(); } public function reset() { $this->errors = array(); $this->errorMask = Validator::ERROR_NONE; } public function getErrorMask() { return $this->errorMask; } 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); } } 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)); } } appliedDefaults = $appliedDefaults; $matches = array(); if ($patternProperties) { $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($properties) { $this->validateProperties($element, $properties, $path); } $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 = '/'; foreach ($try as $delimiter) { if (strpos($pregex, $delimiter) === false) { break; } } 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; } 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); 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'); } 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)); } } $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); } } } 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)) { $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); } } } protected function &getProperty(&$element, $property, $fallback = null) { if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) ) { return $element[$property]; } elseif (is_object($element) && property_exists($element, $property)) { return $element->$property; } return $fallback; } protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) { 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)); } } 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)); } } } } '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' ); private $instanceCache = array(); public function __construct( SchemaStorageInterface $schemaStorage = null, UriRetrieverInterface $uriRetriever = null, $checkMode = Constraint::CHECK_MODE_NORMAL ) { $this->setConfig($checkMode); $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); } public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) { $this->checkMode = $checkMode; } public function addConfig($options) { $this->checkMode |= $options; } public function removeConfig($options) { $this->checkMode &= ~$options; } public function getConfig($options = null) { if ($options === null) { return $this->checkMode; } return $this->checkMode & $options; } 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]; } public function setConstraintClass($name, $class) { if (!class_exists($class)) { throw new InvalidArgumentException('Unknown constraint ' . $name); } if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { throw new InvalidArgumentException('Invalid class ' . $name); } $this->constraintMap[$name] = $class; return $this; } 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]; } public function getErrorContext() { return $this->errorContext; } public function setErrorContext($errorContext) { $this->errorContext = $errorContext; } } 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)); } 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)); } if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); } 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; } } getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); } else { throw new InvalidArgumentException('no schema found to verify against'); } if (is_array($validationSchema)) { $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); } 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; } $schemaStorage = $this->factory->getSchemaStorage(); if (!$this->getTypeCheck()->isObject($schemaSpec)) { $schemaSpec = $schemaStorage->getSchema($schemaSpec); } $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); 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'); } $this->factory->setConfig($initialConfig); $this->factory->setErrorContext($initialContext); } $this->checkUndefined($element, $validationSchema, $path, $i); } } 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)) { if (substr($element, 0, 2) === '//') { $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (substr($element, 0, 1) === '/') { $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (strlen($element)) { $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')) { $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); } 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: break; } } protected function validateDateTime($datetime, $format) { $dt = \DateTime::createFromFormat($format, $datetime); if (!$dt) { return false; } if ($datetime === $dt->format($format)) { return true; } 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); } } 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)); } 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)); } 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'); } } if (isset($schema->items)) { $this->validateItems($value, $schema, $path, $i); } } protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (is_object($schema->items)) { foreach ($value as $k => &$v) { $initErrors = $this->getErrors(); $this->checkUndefined($v, $schema->items, $path, $k); if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { $secondErrors = $this->getErrors(); $this->checkUndefined($v, $schema->additionalItems, $path, $k); } if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { $this->errors = $secondErrors; } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } unset($v); } else { foreach ($value as $k => &$v) { if (array_key_exists($k, $schema->items)) { $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { 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 { $this->checkUndefined($v, new \stdClass(), $path, $k); } } } unset($v); 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); } } } } } 'an integer', 'number' => 'a number', 'boolean' => 'a boolean', 'object' => 'an object', 'array' => 'an array', 'string' => 'a string', 'null' => 'a null', 'any' => null, 0 => null, ); 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'); } } protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path) { foreach ($type as $tp) { 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); } } } } 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); } protected function validateTypeNameWording($type) { if (!array_key_exists($type, self::$wording)) { throw new StandardUnexpectedValueException( sprintf( 'No wording for %s available, expected wordings are: [%s]', var_export($type, true), implode(', ', array_filter(self::$wording))) ); } } protected function validateType(&$value, $type) { 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); } protected function toBoolean($value) { if ($value === 'true') { return true; } if ($value === 'false') { return false; } return $value; } protected function toNumber($value) { if (is_numeric($value)) { return $value + 0; } return $value; } protected function toInteger($value) { if (is_numeric($value) && (int) $value == $value) { return (int) $value; } return $value; } } withPropertyPaths( array_merge( $path->getPropertyPaths(), array($i) ) ); return $path; } 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()); } 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()); } 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()); } 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()); } 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()); } 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()); } 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()); } 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()); } protected function getTypeCheck() { return $this->factory->getTypeCheck(); } protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer) { $result = array_map( function ($path) { return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths() ); return trim(implode('', $result), '.'); } } maxLength) && $this->strlen($element) > $schema->maxLength) { $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( 'maxLength' => $schema->maxLength, )); } 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, )); } 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)); } return strlen($string); } } factory->getConfig(); if ($checkMode !== null) { $this->factory->setConfig($checkMode); } 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(); } public function check($value, $schema) { return $this->validate($value, $schema); } public function coerce(&$value, $schema) { return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } 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); } public function hasUpdate() { $this->newVersionAvailable = $this->newVersionAvailable(); return $this->newVersionAvailable; } public function update() { if ($this->newVersionAvailable === false || (!is_bool($this->newVersionAvailable) && !$this->hasUpdate())) { return false; } $this->backupPhar(); $this->downloadPhar(); $this->replacePhar(); return true; } public function rollback() { if (!$this->restorePhar()) { return false; } return true; } public function setStrategy($strategy) { switch ($strategy) { case self::STRATEGY_GITHUB: $this->strategy = new GithubStrategy; break; default: $this->strategy = new ShaStrategy; break; } } public function setStrategyObject(StrategyInterface $strategy) { $this->strategy = $strategy; } public function getStrategy() { return $this->strategy; } public function setBackupExtension($extension) { $this->backupExtension = $extension; } 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; } 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; } public function getBackupPath() { return $this->backupPath; } 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; } public function getRestorePath() { return $this->restorePath; } public function throwRuntimeException($errno, $errstr) { if (E_USER_DEPRECATED === $errno) { return; } throw new RuntimeException($errstr); } public function throwHttpRequestException($errno, $errstr) { if (E_USER_DEPRECATED === $errno) { return; } 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) { $tmpVersion = sha1_file($this->getTempPharFile()); 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 SHA-1 hash: %s.', $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())); 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->getTempPharFile()); @unlink($this->getTempPubKeyFile()); } } getPharUrl()); restore_error_handler(); if (false === $result) { throw new HttpRequestException(sprintf( 'Request to URL failed: %s', $this->getPharUrl() )); } file_put_contents($updater->getTempPharFile(), $result); } public function getCurrentRemoteVersion(Updater $updater) { set_error_handler(array($updater, 'throwHttpRequestException')); $version = humbug_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]; } public function getCurrentLocalVersion(Updater $updater) { return sha1_file($updater->getLocalPharFile()); } public function setPharUrl($url) { if (!$this->validateAllowedUrl($url)) { throw new InvalidArgumentException( sprintf('Invalid url passed as argument: %s.', $url) ); } $this->pharUrl = $url; } public function getPharUrl() { return $this->pharUrl; } public function setVersionUrl($url) { if (!$this->validateAllowedUrl($url)) { throw new InvalidArgumentException( sprintf('Invalid url passed as argument: %s.', $url) ); } $this->versionUrl = $url; } public function getVersionUrl() { return $this->versionUrl; } protected function validateAllowedUrl($url) { if (filter_var($url, FILTER_VALIDATE_URL) && in_array(parse_url($url, PHP_URL_SCHEME), array('http', 'https', 'file'))) { return true; } return false; } } remoteUrl); restore_error_handler(); if (false === $result) { throw new HttpRequestException(sprintf( 'Request to URL failed: %s', $this->remoteUrl )); } file_put_contents($updater->getTempPharFile(), $result); } public function getCurrentRemoteVersion(Updater $updater) { set_error_handler(array($updater, 'throwHttpRequestException')); $packageUrl = $this->getApiUrl(); $package = json_decode(humbug_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(); } if (!empty($this->remoteVersion)) { $this->remoteUrl = $this->getDownloadUrl($package); } return $this->remoteVersion; } public function getCurrentLocalVersion(Updater $updater) { return $this->localVersion; } public function setCurrentLocalVersion($version) { $this->localVersion = $version; } public function setPackageName($name) { $this->packageName = $name; } public function getPackageName() { return $this->packageName; } public function setPharName($name) { $this->pharName = $name; } public function getPharName() { return $this->pharName; } 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; } 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; } } versions = $versions; } public function getMostRecentStable() { return $this->selectRecentStable(); } public function getMostRecentUnStable() { return $this->selectRecentUnstable(); } public function getMostRecentAll() { return $this->selectRecentAll(); } public function isStable($version) { return $this->stable($version); } public function isPreRelease($version) { return !$this->stable($version) && !$this->development($version); } public function isUnstable($version) { return !$this->stable($version); } 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; } } libxmlErrors = $libxmlErrors; $first = $this->libxmlErrors[0]; parent::__construct( \sprintf( '%s (Line: %d / Column: %d / File: %s)', $first->message, $first->line, $first->column, $first->file ), $first->code ); } public function getLibxmlErrors(): array { return $this->libxmlErrors; } } getContainsElement(); $type = $this->mapType($contains); $copyright = $this->mapCopyright($document->getCopyrightElement()); $requirements = $this->mapRequirements($document->getRequiresElement()); $bundledComponents = $this->mapBundledComponents($document); return new Manifest( new ApplicationName($contains->getName()), new Version($contains->getVersion()), $type, $copyright, $requirements, $bundledComponents ); } catch (VersionException $e) { throw new ManifestDocumentMapperException($e->getMessage(), (int)$e->getCode(), $e); } catch (Exception $e) { throw new ManifestDocumentMapperException($e->getMessage(), (int)$e->getCode(), $e); } } private function mapType(ContainsElement $contains): Type { switch ($contains->getType()) { case 'application': return Type::application(); case 'library': return Type::library(); case 'extension': return $this->mapExtension($contains->getExtensionElement()); } throw new ManifestDocumentMapperException( \sprintf('Unsupported type %s', $contains->getType()) ); } private function mapCopyright(CopyrightElement $copyright): CopyrightInformation { $authors = new AuthorCollection(); foreach ($copyright->getAuthorElements() as $authorElement) { $authors->add( new Author( $authorElement->getName(), new Email($authorElement->getEmail()) ) ); } $licenseElement = $copyright->getLicenseElement(); $license = new License( $licenseElement->getType(), new Url($licenseElement->getUrl()) ); return new CopyrightInformation( $authors, $license ); } private function mapRequirements(RequiresElement $requires): RequirementCollection { $collection = new RequirementCollection(); $phpElement = $requires->getPHPElement(); $parser = new VersionConstraintParser; try { $versionConstraint = $parser->parse($phpElement->getVersion()); } catch (VersionException $e) { throw new ManifestDocumentMapperException( \sprintf('Unsupported version constraint - %s', $e->getMessage()), (int)$e->getCode(), $e ); } $collection->add( new PhpVersionRequirement( $versionConstraint ) ); if (!$phpElement->hasExtElements()) { return $collection; } foreach ($phpElement->getExtElements() as $extElement) { $collection->add( new PhpExtensionRequirement($extElement->getName()) ); } return $collection; } private function mapBundledComponents(ManifestDocument $document): BundledComponentCollection { $collection = new BundledComponentCollection(); if (!$document->hasBundlesElement()) { return $collection; } foreach ($document->getBundlesElement()->getComponentElements() as $componentElement) { $collection->add( new BundledComponent( $componentElement->getName(), new Version( $componentElement->getVersion() ) ) ); } return $collection; } private function mapExtension(ExtensionElement $extension): Extension { try { $versionConstraint = (new VersionConstraintParser)->parse($extension->getCompatible()); return Type::extension( new ApplicationName($extension->getFor()), $versionConstraint ); } catch (VersionException $e) { throw new ManifestDocumentMapperException( \sprintf('Unsupported version constraint - %s', $e->getMessage()), (int)$e->getCode(), $e ); } } } name = $name; $this->email = $email; } public function asString(): string { return \sprintf( '%s <%s>', $this->name, $this->email->asString() ); } public function getName(): string { return $this->name; } public function getEmail(): Email { return $this->email; } } bundledComponents = $bundledComponents->getBundledComponents(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->bundledComponents); } public function key(): int { return $this->position; } public function current(): BundledComponent { return $this->bundledComponents[$this->position]; } public function next(): void { $this->position++; } } authors = $authors; $this->license = $license; } public function getAuthors(): AuthorCollection { return $this->authors; } public function getLicense(): License { return $this->license; } } authors[] = $author; } public function getAuthors(): array { return $this->authors; } public function count(): int { return \count($this->authors); } public function getIterator(): AuthorCollectionIterator { return new AuthorCollectionIterator($this); } } versionConstraint = $versionConstraint; } public function getVersionConstraint(): VersionConstraint { return $this->versionConstraint; } } bundledComponents[] = $bundledComponent; } public function getBundledComponents(): array { return $this->bundledComponents; } public function count(): int { return \count($this->bundledComponents); } public function getIterator(): BundledComponentCollectionIterator { return new BundledComponentCollectionIterator($this); } } authors = $authors->getAuthors(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->authors); } public function key(): int { return $this->position; } public function current(): Author { return $this->authors[$this->position]; } public function next(): void { $this->position++; } } extension = $extension; } public function asString(): string { return $this->extension; } } ensureUrlIsValid($url); $this->url = $url; } public function asString(): string { return $this->url; } private function ensureUrlIsValid($url): void { if (\filter_var($url, \FILTER_VALIDATE_URL) === false) { throw new InvalidUrlException; } } } ensureEmailIsValid($email); $this->email = $email; } public function asString(): string { return $this->email; } private function ensureEmailIsValid(string $url): void { if (\filter_var($url, \FILTER_VALIDATE_EMAIL) === false) { throw new InvalidEmailException; } } } ensureValidFormat($name); $this->name = $name; } public function asString(): string { return $this->name; } public function isEqual(ApplicationName $name): bool { return $this->name === $name->name; } private function ensureValidFormat(string $name): void { if (!\preg_match('#\w/\w#', $name)) { throw new InvalidApplicationNameException( \sprintf('Format of name "%s" is not valid - expected: vendor/packagename', $name), InvalidApplicationNameException::InvalidFormat ); } } } requirements[] = $requirement; } public function getRequirements(): array { return $this->requirements; } public function count(): int { return \count($this->requirements); } public function getIterator(): RequirementCollectionIterator { return new RequirementCollectionIterator($this); } } application = $application; $this->versionConstraint = $versionConstraint; } public function getApplicationName(): ApplicationName { return $this->application; } public function getVersionConstraint(): VersionConstraint { return $this->versionConstraint; } public function isExtension(): bool { return true; } public function isExtensionFor(ApplicationName $name): bool { return $this->application->isEqual($name); } public function isCompatibleWith(ApplicationName $name, Version $version): bool { return $this->isExtensionFor($name) && $this->versionConstraint->complies($version); } } requirements = $requirements->getRequirements(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->requirements); } public function key(): int { return $this->position; } public function current(): Requirement { return $this->requirements[$this->position]; } public function next(): void { $this->position++; } } name = $name; $this->version = $version; $this->type = $type; $this->copyrightInformation = $copyrightInformation; $this->requirements = $requirements; $this->bundledComponents = $bundledComponents; } public function getName(): ApplicationName { return $this->name; } public function getVersion(): Version { return $this->version; } public function getType(): Type { return $this->type; } public function getCopyrightInformation(): CopyrightInformation { return $this->copyrightInformation; } public function getRequirements(): RequirementCollection { return $this->requirements; } public function getBundledComponents(): BundledComponentCollection { return $this->bundledComponents; } public function isApplication(): bool { return $this->type->isApplication(); } public function isLibrary(): bool { return $this->type->isLibrary(); } public function isExtension(): bool { return $this->type->isExtension(); } public function isExtensionFor(ApplicationName $application, Version $version = null): bool { if (!$this->isExtension()) { return false; } $type = $this->type; if ($version !== null) { return $type->isCompatibleWith($application, $version); } return $type->isExtensionFor($application); } } name = $name; $this->url = $url; } public function getName(): string { return $this->name; } public function getUrl(): Url { return $this->url; } } name = $name; $this->version = $version; } public function getName(): string { return $this->name; } public function getVersion(): Version { return $this->version; } } element = $element; } protected function getAttributeValue(string $name): string { if (!$this->element->hasAttribute($name)) { throw new ManifestElementException( \sprintf( 'Attribute %s not set on element %s', $name, $this->element->localName ) ); } return $this->element->getAttribute($name); } protected function getChildByName(string $elementName): DOMElement { $element = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); if (!$element instanceof DOMElement) { throw new ManifestElementException( \sprintf('Element %s missing', $elementName) ); } return $element; } protected function getChildrenByName(string $elementName): DOMNodeList { $elementList = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName); if ($elementList->length === 0) { throw new ManifestElementException( \sprintf('Element(s) %s missing', $elementName) ); } return $elementList; } protected function hasChild(string $elementName): bool { return $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->length !== 0; } } getAttributeValue('version'); } public function hasExtElements(): bool { return $this->hasChild('ext'); } public function getExtElements(): ExtElementCollection { return new ExtElementCollection( $this->getChildrenByName('ext') ); } } getChildrenByName('author') ); } public function getLicenseElement(): LicenseElement { return new LicenseElement( $this->getChildByName('license') ); } } getCurrentElement() ); } } getAttributeValue('type'); } public function getUrl(): string { return $this->getAttributeValue('url'); } } loadXML($xmlString); $errors = \libxml_get_errors(); \libxml_use_internal_errors($prev); if (\count($errors) !== 0) { throw new ManifestDocumentLoadingException($errors); } return new self($dom); } private function __construct(DOMDocument $dom) { $this->ensureCorrectDocumentType($dom); $this->dom = $dom; } public function getContainsElement(): ContainsElement { return new ContainsElement( $this->fetchElementByName('contains') ); } public function getCopyrightElement(): CopyrightElement { return new CopyrightElement( $this->fetchElementByName('copyright') ); } public function getRequiresElement(): RequiresElement { return new RequiresElement( $this->fetchElementByName('requires') ); } public function hasBundlesElement(): bool { return $this->dom->getElementsByTagNameNS(self::XMLNS, 'bundles')->length === 1; } public function getBundlesElement(): BundlesElement { return new BundlesElement( $this->fetchElementByName('bundles') ); } private function ensureCorrectDocumentType(DOMDocument $dom): void { $root = $dom->documentElement; if ($root->localName !== 'phar' || $root->namespaceURI !== self::XMLNS) { throw new ManifestDocumentException('Not a phar.io manifest document'); } } private function fetchElementByName(string $elementName): DOMElement { $element = $this->dom->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); if (!$element instanceof DOMElement) { throw new ManifestDocumentException( \sprintf('Element %s missing', $elementName) ); } return $element; } } getAttributeValue('for'); } public function getCompatible(): string { return $this->getAttributeValue('compatible'); } } getAttributeValue('name'); } } getAttributeValue('name'); } public function getVersion(): string { return $this->getAttributeValue('version'); } } getAttributeValue('name'); } public function getVersion(): string { return $this->getAttributeValue('version'); } public function getType(): string { return $this->getAttributeValue('type'); } public function getExtensionElement(): ExtensionElement { return new ExtensionElement( $this->getChildByName('extension') ); } } getAttributeValue('name'); } public function getEmail(): string { return $this->getAttributeValue('email'); } } getChildByName('php') ); } } getCurrentElement() ); } } getChildrenByName('component') ); } } getCurrentElement() ); } } position = 0; $this->importNodes($nodeList); } #[\ReturnTypeWillChange] abstract public function current(); public function next(): void { $this->position++; } public function key(): int { return $this->position; } public function valid(): bool { return $this->position < \count($this->nodes); } public function rewind(): void { $this->position = 0; } protected function getCurrentElement(): DOMElement { return $this->nodes[$this->position]; } private function importNodes(DOMNodeList $nodeList): void { foreach ($nodeList as $node) { if (!$node instanceof DOMElement) { throw new ElementCollectionException( \sprintf('\DOMElement expected, got \%s', \get_class($node)) ); } $this->nodes[] = $node; } } } serializeToString($manifest) ); } public function serializeToString(Manifest $manifest): string { $this->startDocument(); $this->addContains($manifest->getName(), $manifest->getVersion(), $manifest->getType()); $this->addCopyright($manifest->getCopyrightInformation()); $this->addRequirements($manifest->getRequirements()); $this->addBundles($manifest->getBundledComponents()); return $this->finishDocument(); } private function startDocument(): void { $xmlWriter = new XMLWriter(); $xmlWriter->openMemory(); $xmlWriter->setIndent(true); $xmlWriter->setIndentString(\str_repeat(' ', 4)); $xmlWriter->startDocument('1.0', 'UTF-8'); $xmlWriter->startElement('phar'); $xmlWriter->writeAttribute('xmlns', 'https://phar.io/xml/manifest/1.0'); $this->xmlWriter = $xmlWriter; } private function finishDocument(): string { $this->xmlWriter->endElement(); $this->xmlWriter->endDocument(); return $this->xmlWriter->outputMemory(); } private function addContains(ApplicationName $name, Version $version, Type $type): void { $this->xmlWriter->startElement('contains'); $this->xmlWriter->writeAttribute('name', $name->asString()); $this->xmlWriter->writeAttribute('version', $version->getVersionString()); switch (true) { case $type->isApplication(): { $this->xmlWriter->writeAttribute('type', 'application'); break; } case $type->isLibrary(): { $this->xmlWriter->writeAttribute('type', 'library'); break; } case $type->isExtension(): { $this->xmlWriter->writeAttribute('type', 'extension'); $this->addExtension( $type->getApplicationName(), $type->getVersionConstraint() ); break; } default: { $this->xmlWriter->writeAttribute('type', 'custom'); } } $this->xmlWriter->endElement(); } private function addCopyright(CopyrightInformation $copyrightInformation): void { $this->xmlWriter->startElement('copyright'); foreach ($copyrightInformation->getAuthors() as $author) { $this->xmlWriter->startElement('author'); $this->xmlWriter->writeAttribute('name', $author->getName()); $this->xmlWriter->writeAttribute('email', $author->getEmail()->asString()); $this->xmlWriter->endElement(); } $license = $copyrightInformation->getLicense(); $this->xmlWriter->startElement('license'); $this->xmlWriter->writeAttribute('type', $license->getName()); $this->xmlWriter->writeAttribute('url', $license->getUrl()->asString()); $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); } private function addRequirements(RequirementCollection $requirementCollection): void { $phpRequirement = new AnyVersionConstraint(); $extensions = []; foreach ($requirementCollection as $requirement) { if ($requirement instanceof PhpVersionRequirement) { $phpRequirement = $requirement->getVersionConstraint(); continue; } if ($requirement instanceof PhpExtensionRequirement) { $extensions[] = $requirement->asString(); } } $this->xmlWriter->startElement('requires'); $this->xmlWriter->startElement('php'); $this->xmlWriter->writeAttribute('version', $phpRequirement->asString()); foreach ($extensions as $extension) { $this->xmlWriter->startElement('ext'); $this->xmlWriter->writeAttribute('name', $extension); $this->xmlWriter->endElement(); } $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); } private function addBundles(BundledComponentCollection $bundledComponentCollection): void { if (\count($bundledComponentCollection) === 0) { return; } $this->xmlWriter->startElement('bundles'); foreach ($bundledComponentCollection as $bundledComponent) { $this->xmlWriter->startElement('component'); $this->xmlWriter->writeAttribute('name', $bundledComponent->getName()); $this->xmlWriter->writeAttribute('version', $bundledComponent->getVersion()->getVersionString()); $this->xmlWriter->endElement(); } $this->xmlWriter->endElement(); } private function addExtension(ApplicationName $applicationName, VersionConstraint $versionConstraint): void { $this->xmlWriter->startElement('extension'); $this->xmlWriter->writeAttribute('for', $applicationName->asString()); $this->xmlWriter->writeAttribute('compatible', $versionConstraint->asString()); $this->xmlWriter->endElement(); } } map( ManifestDocument::fromFile($filename) ); } catch (Exception $e) { throw new ManifestLoaderException( \sprintf('Loading %s failed.', $filename), (int)$e->getCode(), $e ); } } public static function fromPhar(string $filename): Manifest { return self::fromFile('phar://' . $filename . '/manifest.xml'); } public static function fromString(string $manifest): Manifest { try { return (new ManifestDocumentMapper())->map( ManifestDocument::fromString($manifest) ); } catch (Exception $e) { throw new ManifestLoaderException( 'Processing string failed', (int)$e->getCode(), $e ); } } } value = $value; } public function asString(): string { return $this->value; } public function equals(BuildMetaData $other): bool { return $this->asString() === $other->asString(); } } ensureVersionStringIsValid($versionString); $this->originalVersionString = $versionString; } public function getPreReleaseSuffix(): PreReleaseSuffix { if ($this->preReleaseSuffix === null) { throw new NoPreReleaseSuffixException('No pre-release suffix set'); } return $this->preReleaseSuffix; } public function getOriginalString(): string { return $this->originalVersionString; } public function getVersionString(): string { $str = \sprintf( '%d.%d.%d', $this->getMajor()->getValue() ?? 0, $this->getMinor()->getValue() ?? 0, $this->getPatch()->getValue() ?? 0 ); if (!$this->hasPreReleaseSuffix()) { return $str; } return $str . '-' . $this->getPreReleaseSuffix()->asString(); } public function hasPreReleaseSuffix(): bool { return $this->preReleaseSuffix !== null; } public function equals(Version $other): bool { if ($this->getVersionString() !== $other->getVersionString()) { return false; } if ($this->hasBuildMetaData() !== $other->hasBuildMetaData()) { return false; } if ($this->hasBuildMetaData() && $other->hasBuildMetaData() && !$this->getBuildMetaData()->equals($other->getBuildMetaData())) { return false; } return true; } public function isGreaterThan(Version $version): bool { if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { return false; } if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { return true; } if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { return false; } if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { return true; } if ($version->getPatch()->getValue() > $this->getPatch()->getValue()) { return false; } if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { return true; } if (!$version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { return false; } if ($version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { return true; } if (!$version->hasPreReleaseSuffix() && $this->hasPreReleaseSuffix()) { return false; } return $this->getPreReleaseSuffix()->isGreaterThan($version->getPreReleaseSuffix()); } public function getMajor(): VersionNumber { return $this->major; } public function getMinor(): VersionNumber { return $this->minor; } public function getPatch(): VersionNumber { return $this->patch; } public function hasBuildMetaData(): bool { return $this->buildMetadata !== null; } public function getBuildMetaData(): BuildMetaData { if (!$this->hasBuildMetaData()) { throw new NoBuildMetaDataException('No build metadata set'); } return $this->buildMetadata; } private function parseVersion(array $matches): void { $this->major = new VersionNumber((int)$matches['Major']); $this->minor = new VersionNumber((int)$matches['Minor']); $this->patch = isset($matches['Patch']) ? new VersionNumber((int)$matches['Patch']) : new VersionNumber(0); if (isset($matches['PreReleaseSuffix']) && $matches['PreReleaseSuffix'] !== '') { $this->preReleaseSuffix = new PreReleaseSuffix($matches['PreReleaseSuffix']); } if (isset($matches['BuildMetadata'])) { $this->buildMetadata = new BuildMetaData($matches['BuildMetadata']); } } private function ensureVersionStringIsValid($version): void { $regex = '/^v? (?P0|[1-9]\d*) \\. (?P0|[1-9]\d*) (\\. (?P0|[1-9]\d*) )? (?: - (?(?:(dev|beta|b|rc|alpha|a|patch|p|pl)\.?\d*)) )? (?: \\+ (?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-@]+)*) )? $/xi'; if (\preg_match($regex, $version, $matches) !== 1) { throw new InvalidVersionException( \sprintf("Version string '%s' does not follow SemVer semantics", $version) ); } $this->parseVersion($matches); } } versionString = $versionString; $this->parseVersion($versionString); } public function getLabel(): string { return $this->label; } public function getBuildMetaData(): string { return $this->buildMetaData; } public function getVersionString(): string { return $this->versionString; } public function getMajor(): VersionNumber { return $this->major; } public function getMinor(): VersionNumber { return $this->minor; } public function getPatch(): VersionNumber { return $this->patch; } private function parseVersion(string $versionString): void { $this->extractBuildMetaData($versionString); $this->extractLabel($versionString); $this->stripPotentialVPrefix($versionString); $versionSegments = \explode('.', $versionString); $this->major = new VersionNumber(\is_numeric($versionSegments[0]) ? (int)$versionSegments[0] : null); $minorValue = isset($versionSegments[1]) && \is_numeric($versionSegments[1]) ? (int)$versionSegments[1] : null; $patchValue = isset($versionSegments[2]) && \is_numeric($versionSegments[2]) ? (int)$versionSegments[2] : null; $this->minor = new VersionNumber($minorValue); $this->patch = new VersionNumber($patchValue); } private function extractBuildMetaData(string &$versionString): void { if (\preg_match('/\+(.*)/', $versionString, $matches) === 1) { $this->buildMetaData = $matches[1]; $versionString = \str_replace($matches[0], '', $versionString); } } private function extractLabel(string &$versionString): void { if (\preg_match('/-(.*)/', $versionString, $matches) === 1) { $this->label = $matches[1]; $versionString = \str_replace($matches[0], '', $versionString); } } private function stripPotentialVPrefix(string &$versionString): void { if ($versionString[0] !== 'v') { return; } $versionString = \substr($versionString, 1); } } 0, 'a' => 1, 'alpha' => 1, 'b' => 2, 'beta' => 2, 'rc' => 3, 'p' => 4, 'pl' => 4, 'patch' => 4, ]; private $value; private $valueScore; private $number = 0; private $full; public function __construct(string $value) { $this->parseValue($value); } public function asString(): string { return $this->full; } public function getValue(): string { return $this->value; } public function getNumber(): ?int { return $this->number; } public function isGreaterThan(PreReleaseSuffix $suffix): bool { if ($this->valueScore > $suffix->valueScore) { return true; } if ($this->valueScore < $suffix->valueScore) { return false; } return $this->getNumber() > $suffix->getNumber(); } private function mapValueToScore(string $value): int { $value = \strtolower($value); return self::valueScoreMap[$value]; } private function parseValue(string $value): void { $regex = '/-?((dev|beta|b|rc|alpha|a|patch|p|pl)\.?(\d*)).*$/i'; if (\preg_match($regex, $value, $matches) !== 1) { throw new InvalidPreReleaseSuffixException(\sprintf('Invalid label %s', $value)); } $this->full = $matches[1]; $this->value = $matches[2]; if ($matches[3] !== '') { $this->number = (int)$matches[3]; } $this->valueScore = $this->mapValueToScore($matches[2]); } } value = $value; } public function isAny(): bool { return $this->value === null; } public function getValue(): ?int { return $this->value; } } handleOrGroup($value); } if (!\preg_match('/^[\^~*]?v?[\d.*]+(?:-.*)?$/i', $value)) { throw new UnsupportedVersionConstraintException( \sprintf('Version constraint %s is not supported.', $value) ); } switch ($value[0]) { case '~': return $this->handleTildeOperator($value); case '^': return $this->handleCaretOperator($value); } $constraint = new VersionConstraintValue($value); if ($constraint->getMajor()->isAny()) { return new AnyVersionConstraint(); } if ($constraint->getMinor()->isAny()) { return new SpecificMajorVersionConstraint( $constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0 ); } if ($constraint->getPatch()->isAny()) { return new SpecificMajorAndMinorVersionConstraint( $constraint->getVersionString(), $constraint->getMajor()->getValue() ?? 0, $constraint->getMinor()->getValue() ?? 0 ); } return new ExactVersionConstraint($constraint->getVersionString()); } private function handleOrGroup(string $value): OrVersionConstraintGroup { $constraints = []; foreach (\preg_split('{\s*\|\|?\s*}', \trim($value)) as $groupSegment) { $constraints[] = $this->parse(\trim($groupSegment)); } return new OrVersionConstraintGroup($value, $constraints); } private function handleTildeOperator(string $value): AndVersionConstraintGroup { $constraintValue = new VersionConstraintValue(\substr($value, 1)); if ($constraintValue->getPatch()->isAny()) { return $this->handleCaretOperator($value); } $constraints = [ new GreaterThanOrEqualToVersionConstraint( $value, new Version(\substr($value, 1)) ), new SpecificMajorAndMinorVersionConstraint( $value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0 ) ]; return new AndVersionConstraintGroup($value, $constraints); } private function handleCaretOperator(string $value): AndVersionConstraintGroup { $constraintValue = new VersionConstraintValue(\substr($value, 1)); $constraints = [ new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1))) ]; if ($constraintValue->getMajor()->getValue() === 0) { $constraints[] = new SpecificMajorAndMinorVersionConstraint( $value, $constraintValue->getMajor()->getValue() ?? 0, $constraintValue->getMinor()->getValue() ?? 0 ); } else { $constraints[] = new SpecificMajorVersionConstraint( $value, $constraintValue->getMajor()->getValue() ?? 0 ); } return new AndVersionConstraintGroup( $value, $constraints ); } } major = $major; $this->minor = $minor; } public function complies(Version $version): bool { if ($version->getMajor()->getValue() !== $this->major) { return false; } return $version->getMinor()->getValue() === $this->minor; } } constraints = $constraints; } public function complies(Version $version): bool { foreach ($this->constraints as $constraint) { if ($constraint->complies($version)) { return true; } } return false; } } constraints = $constraints; } public function complies(Version $version): bool { foreach ($this->constraints as $constraint) { if (!$constraint->complies($version)) { return false; } } return true; } } major = $major; } public function complies(Version $version): bool { return $version->getMajor()->getValue() === $this->major; } } minimalVersion = $minimalVersion; } public function complies(Version $version): bool { return $version->getVersionString() === $this->minimalVersion->getVersionString() || $version->isGreaterThan($this->minimalVersion); } } originalValue = $originalValue; } public function asString(): string { return $this->originalValue; } } getVersionString(); if ($version->hasBuildMetaData()) { $other .= '+' . $version->getBuildMetaData()->asString(); } return $this->asString() === $other; } } times = intval($times); $this->util = $util ?: new StringUtil; } public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) { if ($this->times == count($calls)) { return; } $methodCalls = $object->findProphecyMethodCalls( $method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken)) ); if (count($calls)) { $message = sprintf( "Expected exactly %d calls that match:\n". " %s->%s(%s)\n". "but %d were made:\n%s", $this->times, get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), count($calls), $this->util->stringifyCalls($calls) ); } elseif (count($methodCalls)) { $message = sprintf( "Expected exactly %d calls that match:\n". " %s->%s(%s)\n". "but none were made.\n". "Recorded `%s(...)` calls:\n%s", $this->times, get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls) ); } else { $message = sprintf( "Expected exactly %d calls that match:\n". " %s->%s(%s)\n". "but none were made.", $this->times, get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard() ); } throw new UnexpectedCallsCountException($message, $method, $this->times, $calls); } } util = $util ?: new StringUtil; } public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) { if (count($calls)) { return; } $methodCalls = $object->findProphecyMethodCalls( $method->getMethodName(), new ArgumentsWildcard(array(new AnyValuesToken)) ); if (count($methodCalls)) { throw new NoCallsException(sprintf( "No calls have been made that match:\n". " %s->%s(%s)\n". "but expected at least one.\n". "Recorded `%s(...)` calls:\n%s", get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), $method->getMethodName(), $this->util->stringifyCalls($methodCalls) ), $method); } throw new NoCallsException(sprintf( "No calls have been made that match:\n". " %s->%s(%s)\n". "but expected at least one.", get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard() ), $method); } } callback = $callback; } public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) { $callback = $this->callback; if ($callback instanceof Closure && method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { $callback = Closure::bind($callback, $object); } call_user_func($callback, $calls, $object, $method); } } util = $util ?: new StringUtil; } public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) { if (!count($calls)) { return; } $verb = count($calls) === 1 ? 'was' : 'were'; throw new UnexpectedCallsException(sprintf( "No calls expected that match:\n". " %s->%s(%s)\n". "but %d %s made:\n%s", get_class($object->reveal()), $method->getMethodName(), $method->getArgumentsWildcard(), count($calls), $verb, $this->util->stringifyCalls($calls) ), $method, $calls); } } register(new ClosureComparator()); $this->register(new ProphecyComparator()); } public static function getInstance() { if (self::$instance === null) { self::$instance = new Factory; } return self::$instance; } } reveal(), $delta, $canonicalize, $ignoreCase, $processed); } } verbose = $verbose; } public function stringify($value, $exportObject = true) { if (is_array($value)) { if (range(0, count($value) - 1) === array_keys($value)) { return '['.implode(', ', array_map(array($this, __FUNCTION__), $value)).']'; } $stringify = array($this, __FUNCTION__); return '['.implode(', ', array_map(function ($item, $key) use ($stringify) { return (is_integer($key) ? $key : '"'.$key.'"'). ' => '.call_user_func($stringify, $item); }, $value, array_keys($value))).']'; } if (is_resource($value)) { return get_resource_type($value).':'.$value; } if (is_object($value)) { return $exportObject ? ExportUtil::export($value) : sprintf('%s:%s', get_class($value), spl_object_hash($value)); } if (true === $value || false === $value) { return $value ? 'true' : 'false'; } if (is_string($value)) { $str = sprintf('"%s"', str_replace("\n", '\\n', $value)); if (!$this->verbose && 50 <= strlen($str)) { return substr($str, 0, 50).'"...'; } return $str; } if (null === $value) { return 'null'; } return (string) $value; } public function stringifyCalls(array $calls) { $self = $this; return implode(PHP_EOL, array_map(function (Call $call) use ($self) { return sprintf(' - %s(%s) @ %s', $call->getMethodName(), implode(', ', array_map(array($self, 'stringify'), $call->getArguments())), str_replace(GETCWD().DIRECTORY_SEPARATOR, '', $call->getCallPlace()) ); }, $calls)); } } $val) { if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { $key = $matches[1]; } if ($key === "\0gcdata") { continue; } $array[$key] = $val; } if ($value instanceof \SplObjectStorage) { if (property_exists('\SplObjectStorage', '__storage')) { unset($array['__storage']); } elseif (property_exists('\SplObjectStorage', 'storage')) { unset($array['storage']); } if (property_exists('\SplObjectStorage', '__key')) { unset($array['__key']); } foreach ($value as $key => $val) { $array[spl_object_hash($val)] = array( 'obj' => $val, 'inf' => $value->getInfo(), ); } } return $array; } protected static function recursiveExport(&$value, $indentation, $processed = null) { if ($value === null) { return 'null'; } if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } if (is_float($value) && floatval(intval($value)) === $value) { return "$value.0"; } if (is_resource($value)) { return sprintf( 'resource(%d) of type (%s)', $value, get_resource_type($value) ); } if (is_string($value)) { if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) { return 'Binary String: 0x' . bin2hex($value); } return "'" . str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . "'"; } $whitespace = str_repeat(' ', 4 * $indentation); if (!$processed) { $processed = new Context; } if (is_array($value)) { if (($key = $processed->contains($value)) !== false) { return 'Array &' . $key; } $array = $value; $key = $processed->add($value); $values = ''; if (count($array) > 0) { foreach ($array as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, self::recursiveExport($k, $indentation), self::recursiveExport($value[$k], $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('Array &%s (%s)', $key, $values); } if (is_object($value)) { $class = get_class($value); if ($hash = $processed->contains($value)) { return sprintf('%s:%s Object', $class, $hash); } $hash = $processed->add($value); $values = ''; $array = self::toArray($value); if (count($array) > 0) { foreach ($array as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, self::recursiveExport($k, $indentation), self::recursiveExport($v, $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('%s:%s Object (%s)', $class, $hash, $values); } return var_export($value, true); } } index = $index; } public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) { return count($args) > $this->index ? $args[$this->index] : null; } } callback = $callback; } public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) { $callback = $this->callback; if ($callback instanceof Closure && method_exists('Closure', 'bind') && (new ReflectionFunction($callback))->getClosureThis() !== null) { $callback = Closure::bind($callback, $object); } return call_user_func($callback, $args, $object, $method); } } returnValues = $returnValues; } public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) { $value = array_shift($this->returnValues); if (!count($this->returnValues)) { $this->returnValues[] = $value; } return $value; } } isAValidThrowable($exception)) { throw new InvalidArgumentException(sprintf( 'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', $exception )); } } elseif (!$exception instanceof \Exception && !$exception instanceof \Throwable) { throw new InvalidArgumentException(sprintf( 'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', is_object($exception) ? get_class($exception) : gettype($exception) )); } $this->exception = $exception; } public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) { if (is_string($this->exception)) { $classname = $this->exception; $reflection = new ReflectionClass($classname); $constructor = $reflection->getConstructor(); if ($constructor->isPublic() && 0 == $constructor->getNumberOfRequiredParameters()) { throw $reflection->newInstance(); } if (!$this->instantiator) { $this->instantiator = new Instantiator(); } throw $this->instantiator->instantiate($classname); } throw $this->exception; } private function isAValidThrowable($exception) { return is_a($exception, 'Exception', true) || is_a($exception, 'Throwable', true); } } registerClassPatch(new ClassPatch\SplFileInfoPatch); $doubler->registerClassPatch(new ClassPatch\TraversablePatch); $doubler->registerClassPatch(new ClassPatch\ThrowablePatch); $doubler->registerClassPatch(new ClassPatch\DisableConstructorPatch); $doubler->registerClassPatch(new ClassPatch\ProphecySubjectPatch); $doubler->registerClassPatch(new ClassPatch\ReflectionClassNewInstancePatch); $doubler->registerClassPatch(new ClassPatch\HhvmExceptionPatch()); $doubler->registerClassPatch(new ClassPatch\MagicCallPatch); $doubler->registerClassPatch(new ClassPatch\KeywordPatch); } $this->doubler = $doubler; $this->revealer = $revealer ?: new Revealer; $this->util = $util ?: new StringUtil; } public function prophesize($classOrInterface = null) { $this->prophecies[] = $prophecy = new ObjectProphecy( new LazyDouble($this->doubler), new CallCenter($this->util), $this->revealer ); if ($classOrInterface && class_exists($classOrInterface)) { return $prophecy->willExtend($classOrInterface); } if ($classOrInterface && interface_exists($classOrInterface)) { return $prophecy->willImplement($classOrInterface); } return $prophecy; } public function getProphecies() { return $this->prophecies; } public function getDoubler() { return $this->doubler; } public function checkPredictions() { $exception = new AggregateException("Some predictions failed:\n"); foreach ($this->prophecies as $prophecy) { try { $prophecy->checkProphecyMethodsPredictions(); } catch (PredictionException $e) { $exception->append($e); } } if (count($exception->getExceptions())) { throw $exception; } } } name = $methodName; $this->value = $value; $this->util = $util ?: new StringUtil; $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); } public function scoreArgument($argument) { if (is_object($argument) && method_exists($argument, $this->name)) { $actual = call_user_func(array($argument, $this->name)); $comparator = $this->comparatorFactory->getComparatorFor( $this->value, $actual ); try { $comparator->assertEquals($this->value, $actual); return 8; } catch (ComparisonFailure $failure) { return false; } } if (is_object($argument) && property_exists($argument, $this->name)) { return $argument->{$this->name} === $this->value ? 8 : false; } return false; } public function isLast() { return false; } public function __toString() { return sprintf('state(%s(), %s)', $this->name, $this->util->stringify($this->value) ); } } value = $value; } public function scoreArgument($argument) { if (!$argument instanceof \Traversable && !is_array($argument)) { return false; } $scores = array(); foreach ($argument as $key => $argumentEntry) { $scores[] = $this->value->scoreArgument($argumentEntry); } if (empty($scores) || in_array(false, $scores, true)) { return false; } return array_sum($scores) / count($scores); } public function isLast() { return false; } public function __toString() { return sprintf('[%s, ..., %s]', $this->value, $this->value); } public function getValue() { return $this->value; } } type = $type; } public function scoreArgument($argument) { $checker = "is_{$this->type}"; if (function_exists($checker)) { return call_user_func($checker, $argument) ? 5 : false; } return $argument instanceof $this->type ? 5 : false; } public function isLast() { return false; } public function __toString() { return sprintf('type(%s)', $this->type); } } value = $value; $this->precision = $precision; } public function scoreArgument($argument) { return round((float)$argument, $this->precision) === round($this->value, $this->precision) ? 10 : false; } public function isLast() { return false; } public function __toString() { return sprintf('≅%s', round($this->value, $this->precision)); } } callback = $callback; } public function scoreArgument($argument) { return call_user_func($this->callback, $argument) ? 7 : false; } public function isLast() { return false; } public function __toString() { return 'callback()'; } } value = $value; $this->util = $util ?: new StringUtil(); } public function scoreArgument($argument) { return $argument === $this->value ? 11 : false; } public function isLast() { return false; } public function __toString() { if (null === $this->string) { $this->string = sprintf('identical(%s)', $this->util->stringify($this->value)); } return $this->string; } } token = $value instanceof TokenInterface? $value : new ExactValueToken($value); } public function scoreArgument($argument) { return false === $this->token->scoreArgument($argument) ? 4 : false; } public function isLast() { return $this->token->isLast(); } public function getOriginatingToken() { return $this->token; } public function __toString() { return sprintf('not(%s)', $this->token); } } tokens[] = $argument; } } public function scoreArgument($argument) { if (0 === count($this->tokens)) { return false; } $maxScore = 0; foreach ($this->tokens as $token) { $score = $token->scoreArgument($argument); if (false === $score) { return false; } $maxScore = max($score, $maxScore); } return $maxScore; } public function isLast() { return false; } public function __toString() { return sprintf('bool(%s)', implode(' AND ', $this->tokens)); } } count = $value; } public function scoreArgument($argument) { return $this->isCountable($argument) && $this->hasProperCount($argument) ? 6 : false; } public function isLast() { return false; } public function __toString() { return sprintf('count(%s)', $this->count); } private function isCountable($argument) { return (is_array($argument) || $argument instanceof \Countable); } private function hasProperCount($argument) { return $this->count === count($argument); } } value = $value; } public function scoreArgument($argument) { return is_string($argument) && strpos($argument, $this->value) !== false ? 6 : false; } public function getValue() { return $this->value; } public function isLast() { return false; } public function __toString() { return sprintf('contains("%s")', $this->value); } } key = $this->wrapIntoExactValueToken($key); $this->value = $this->wrapIntoExactValueToken($value); } public function scoreArgument($argument) { if ($argument instanceof \Traversable) { $argument = iterator_to_array($argument); } if ($argument instanceof \ArrayAccess) { $argument = $this->convertArrayAccessToEntry($argument); } if (!is_array($argument) || empty($argument)) { return false; } $keyScores = array_map(array($this->key,'scoreArgument'), array_keys($argument)); $valueScores = array_map(array($this->value,'scoreArgument'), $argument); $scoreEntry = function ($value, $key) { return $value && $key ? min(8, ($key + $value) / 2) : false; }; return max(array_map($scoreEntry, $valueScores, $keyScores)); } public function isLast() { return false; } public function __toString() { return sprintf('[..., %s => %s, ...]', $this->key, $this->value); } public function getKey() { return $this->key; } public function getValue() { return $this->value; } private function wrapIntoExactValueToken($value) { return $value instanceof TokenInterface ? $value : new ExactValueToken($value); } private function convertArrayAccessToEntry(\ArrayAccess $object) { if (!$this->key instanceof ExactValueToken) { throw new InvalidArgumentException(sprintf( 'You can only use exact value tokens to match key of ArrayAccess object'.PHP_EOL. 'But you used `%s`.', $this->key )); } $key = $this->key->getValue(); return $object->offsetExists($key) ? array($key => $object[$key]) : array(); } } value = $value; $this->util = $util ?: new StringUtil(); $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); } public function scoreArgument($argument) { if (is_object($argument) && is_object($this->value)) { $comparator = $this->comparatorFactory->getComparatorFor( $argument, $this->value ); try { $comparator->assertEquals($argument, $this->value); return 10; } catch (ComparisonFailure $failure) { return false; } } if (is_object($argument) xor is_object($this->value)) { if (is_object($argument) && !method_exists($argument, '__toString')) { return false; } if (is_object($this->value) && !method_exists($this->value, '__toString')) { return false; } } elseif (is_numeric($argument) && is_numeric($this->value)) { } elseif (gettype($argument) !== gettype($this->value)) { return false; } return $argument == $this->value ? 10 : false; } public function getValue() { return $this->value; } public function isLast() { return false; } public function __toString() { if (null === $this->string) { $this->string = sprintf('exact(%s)', $this->util->stringify($this->value)); } return $this->string; } } token = $arguments; $this->strict = $strict; } public function scoreArgument($argument) { if (count($this->token) === 0) { return false; } if (\in_array($argument, $this->token, $this->strict)) { return 8; } return false; } public function isLast() { return false; } public function __toString() { $arrayAsString = implode(', ', $this->token); return "[{$arrayAsString}]"; } } token = $arguments; $this->strict = $strict; } public function scoreArgument($argument) { if (count($this->token) === 0) { return false; } if (!\in_array($argument, $this->token, $this->strict)) { return 8; } return false; } public function isLast() { return false; } public function __toString() { $arrayAsString = implode(', ', $this->token); return "[{$arrayAsString}]"; } } tokens[] = $argument; } } public function scoreArguments(array $arguments) { if (0 == count($arguments) && 0 == count($this->tokens)) { return 1; } $arguments = array_values($arguments); $totalScore = 0; foreach ($this->tokens as $i => $token) { $argument = isset($arguments[$i]) ? $arguments[$i] : null; if (1 >= $score = $token->scoreArgument($argument)) { return false; } $totalScore += $score; if (true === $token->isLast()) { return $totalScore; } } if (count($arguments) > count($this->tokens)) { return false; } return $totalScore; } public function __toString() { if (null === $this->string) { $this->string = implode(', ', array_map(function ($token) { return (string) $token; }, $this->tokens)); } return $this->string; } public function getTokens() { return $this->tokens; } } reveal(); if (!method_exists($double, $methodName)) { throw new MethodNotFoundException(sprintf( 'Method `%s::%s()` is not defined.', get_class($double), $methodName ), get_class($double), $methodName, $arguments); } $this->objectProphecy = $objectProphecy; $this->methodName = $methodName; $reflectedMethod = new \ReflectionMethod($double, $methodName); if ($reflectedMethod->isFinal()) { throw new MethodProphecyException(sprintf( "Can not add prophecy for a method `%s::%s()`\n". "as it is a final method.", get_class($double), $methodName ), $this); } if (null !== $arguments) { $this->withArguments($arguments); } $hasTentativeReturnType = method_exists($reflectedMethod, 'hasTentativeReturnType') && $reflectedMethod->hasTentativeReturnType(); if (true === $reflectedMethod->hasReturnType() || $hasTentativeReturnType) { if ($hasTentativeReturnType) { $reflectionType = $reflectedMethod->getTentativeReturnType(); } else { $reflectionType = $reflectedMethod->getReturnType(); } if ($reflectionType instanceof ReflectionNamedType) { $types = [$reflectionType]; } elseif ($reflectionType instanceof ReflectionUnionType) { $types = $reflectionType->getTypes(); } $types = array_map( function(ReflectionType $type) { return $type->getName(); }, $types ); usort( $types, static function(string $type1, string $type2) { if ($type2 == 'null') { return -1; } elseif ($type1 == 'null') { return 1; } $isObject = static function($type) { return class_exists($type) || interface_exists($type); }; if($isObject($type1) && !$isObject($type2)) { return -1; } elseif(!$isObject($type1) && $isObject($type2)) { return 1; } return 0; } ); $defaultType = $types[0]; if ('void' === $defaultType) { $this->voidReturnType = true; } $this->will(function () use ($defaultType) { switch ($defaultType) { case 'void': return; case 'string': return ''; case 'float': return 0.0; case 'int': return 0; case 'bool': return false; case 'array': return array(); case 'callable': case 'Closure': return function () {}; case 'Traversable': case 'Generator': return (function () { yield; })(); default: $prophet = new Prophet; return $prophet->prophesize($defaultType)->reveal(); } }); } } public function withArguments($arguments) { if (is_array($arguments)) { $arguments = new Argument\ArgumentsWildcard($arguments); } if (!$arguments instanceof Argument\ArgumentsWildcard) { throw new InvalidArgumentException(sprintf( "Either an array or an instance of ArgumentsWildcard expected as\n". 'a `MethodProphecy::withArguments()` argument, but got %s.', gettype($arguments) )); } $this->argumentsWildcard = $arguments; return $this; } public function will($promise) { if (is_callable($promise)) { $promise = new Promise\CallbackPromise($promise); } if (!$promise instanceof Promise\PromiseInterface) { throw new InvalidArgumentException(sprintf( 'Expected callable or instance of PromiseInterface, but got %s.', gettype($promise) )); } $this->bindToObjectProphecy(); $this->promise = $promise; return $this; } public function willReturn() { if ($this->voidReturnType) { throw new MethodProphecyException( "The method \"$this->methodName\" has a void return type, and so cannot return anything", $this ); } return $this->will(new Promise\ReturnPromise(func_get_args())); } public function willYield($items, $return = null) { if ($this->voidReturnType) { throw new MethodProphecyException( "The method \"$this->methodName\" has a void return type, and so cannot yield anything", $this ); } if (!is_array($items)) { throw new InvalidArgumentException(sprintf( 'Expected array, but got %s.', gettype($items) )); } $generator = function() use ($items, $return) { yield from $items; return $return; }; return $this->will($generator); } public function willReturnArgument($index = 0) { if ($this->voidReturnType) { throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this); } return $this->will(new Promise\ReturnArgumentPromise($index)); } public function willThrow($exception) { return $this->will(new Promise\ThrowPromise($exception)); } public function should($prediction) { if (is_callable($prediction)) { $prediction = new Prediction\CallbackPrediction($prediction); } if (!$prediction instanceof Prediction\PredictionInterface) { throw new InvalidArgumentException(sprintf( 'Expected callable or instance of PredictionInterface, but got %s.', gettype($prediction) )); } $this->bindToObjectProphecy(); $this->prediction = $prediction; return $this; } public function shouldBeCalled() { return $this->should(new Prediction\CallPrediction); } public function shouldNotBeCalled() { return $this->should(new Prediction\NoCallsPrediction); } public function shouldBeCalledTimes($count) { return $this->should(new Prediction\CallTimesPrediction($count)); } public function shouldBeCalledOnce() { return $this->shouldBeCalledTimes(1); } public function shouldHave($prediction) { if (is_callable($prediction)) { $prediction = new Prediction\CallbackPrediction($prediction); } if (!$prediction instanceof Prediction\PredictionInterface) { throw new InvalidArgumentException(sprintf( 'Expected callable or instance of PredictionInterface, but got %s.', gettype($prediction) )); } if (null === $this->promise && !$this->voidReturnType) { $this->willReturn(); } $calls = $this->getObjectProphecy()->findProphecyMethodCalls( $this->getMethodName(), $this->getArgumentsWildcard() ); try { $prediction->check($calls, $this->getObjectProphecy(), $this); $this->checkedPredictions[] = $prediction; } catch (\Exception $e) { $this->checkedPredictions[] = $prediction; throw $e; } return $this; } public function shouldHaveBeenCalled() { return $this->shouldHave(new Prediction\CallPrediction); } public function shouldNotHaveBeenCalled() { return $this->shouldHave(new Prediction\NoCallsPrediction); } public function shouldNotBeenCalled() { return $this->shouldNotHaveBeenCalled(); } public function shouldHaveBeenCalledTimes($count) { return $this->shouldHave(new Prediction\CallTimesPrediction($count)); } public function shouldHaveBeenCalledOnce() { return $this->shouldHaveBeenCalledTimes(1); } public function checkPrediction() { if (null === $this->prediction) { return; } $this->shouldHave($this->prediction); } public function getPromise() { return $this->promise; } public function getPrediction() { return $this->prediction; } public function getCheckedPredictions() { return $this->checkedPredictions; } public function getObjectProphecy() { return $this->objectProphecy; } public function getMethodName() { return $this->methodName; } public function getArgumentsWildcard() { return $this->argumentsWildcard; } public function hasReturnVoid() { return $this->voidReturnType; } private function bindToObjectProphecy() { if ($this->bound) { return; } $this->getObjectProphecy()->addMethodProphecy($this); $this->bound = true; } } lazyDouble = $lazyDouble; $this->callCenter = $callCenter ?: new CallCenter; $this->revealer = $revealer ?: new Revealer; $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); } public function willExtend($class) { $this->lazyDouble->setParentClass($class); return $this; } public function willImplement($interface) { $this->lazyDouble->addInterface($interface); return $this; } public function willBeConstructedWith(array $arguments = null) { $this->lazyDouble->setArguments($arguments); return $this; } public function reveal() { $double = $this->lazyDouble->getInstance(); if (null === $double || !$double instanceof ProphecySubjectInterface) { throw new ObjectProphecyException( "Generated double must implement ProphecySubjectInterface, but it does not.\n". 'It seems you have wrongly configured doubler without required ClassPatch.', $this ); } $double->setProphecy($this); return $double; } public function addMethodProphecy(MethodProphecy $methodProphecy) { $argumentsWildcard = $methodProphecy->getArgumentsWildcard(); if (null === $argumentsWildcard) { throw new MethodProphecyException(sprintf( "Can not add prophecy for a method `%s::%s()`\n". "as you did not specify arguments wildcard for it.", get_class($this->reveal()), $methodProphecy->getMethodName() ), $methodProphecy); } $methodName = strtolower($methodProphecy->getMethodName()); if (!isset($this->methodProphecies[$methodName])) { $this->methodProphecies[$methodName] = array(); } $this->methodProphecies[$methodName][] = $methodProphecy; } public function getMethodProphecies($methodName = null) { if (null === $methodName) { return $this->methodProphecies; } $methodName = strtolower($methodName); if (!isset($this->methodProphecies[$methodName])) { return array(); } return $this->methodProphecies[$methodName]; } public function makeProphecyMethodCall($methodName, array $arguments) { $arguments = $this->revealer->reveal($arguments); $return = $this->callCenter->makeCall($this, $methodName, $arguments); return $this->revealer->reveal($return); } public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard) { return $this->callCenter->findCalls($methodName, $wildcard); } public function checkProphecyMethodsPredictions() { $exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal()))); $exception->setObjectProphecy($this); $this->callCenter->checkUnexpectedCalls(); foreach ($this->methodProphecies as $prophecies) { foreach ($prophecies as $prophecy) { try { $prophecy->checkPrediction(); } catch (PredictionException $e) { $exception->append($e); } } } if (count($exception->getExceptions())) { throw $exception; } } public function __call($methodName, array $arguments) { $arguments = new ArgumentsWildcard($this->revealer->reveal($arguments)); foreach ($this->getMethodProphecies($methodName) as $prophecy) { $argumentsWildcard = $prophecy->getArgumentsWildcard(); $comparator = $this->comparatorFactory->getComparatorFor( $argumentsWildcard, $arguments ); try { $comparator->assertEquals($argumentsWildcard, $arguments); return $prophecy; } catch (ComparisonFailure $failure) {} } return new MethodProphecy($this, $methodName, $arguments); } public function __get($name) { return $this->reveal()->$name; } public function __set($name, $value) { $this->reveal()->$name = $this->revealer->reveal($value); } } reveal(); } return $value; } } getDocComment()); return $phpdoc->getTagsByName('method'); } } docBlockFactory = DocBlockFactory::createInstance(); $this->contextFactory = new ContextFactory(); } public function getTagList(\ReflectionClass $reflectionClass) { try { $phpdoc = $this->docBlockFactory->create( $reflectionClass, $this->contextFactory->createFromReflector($reflectionClass) ); $methods = array(); foreach ($phpdoc->getTagsByName('method') as $tag) { if ($tag instanceof Method) { $methods[] = $tag; } } return $methods; } catch (\InvalidArgumentException $e) { return array(); } } } classRetriever = $classRetriever; return; } $this->classRetriever = class_exists('phpDocumentor\Reflection\DocBlockFactory') && class_exists('phpDocumentor\Reflection\Types\ContextFactory') ? new ClassTagRetriever() : new LegacyClassTagRetriever() ; } public function getTagList(\ReflectionClass $reflectionClass) { return array_merge( $this->classRetriever->getTagList($reflectionClass), $this->getInterfacesTagList($reflectionClass) ); } private function getInterfacesTagList(\ReflectionClass $reflectionClass) { $interfaces = $reflectionClass->getInterfaces(); $tagList = array(); foreach($interfaces as $interface) { $tagList = array_merge($tagList, $this->classRetriever->getTagList($interface)); } return $tagList; } } util = $util ?: new StringUtil; $this->unexpectedCalls = new SplObjectStorage(); } public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $file = $line = null; if (isset($backtrace[2]) && isset($backtrace[2]['file'])) { $file = $backtrace[2]['file']; $line = $backtrace[2]['line']; } if ('__destruct' === strtolower($methodName) || 0 == count($prophecy->getMethodProphecies())) { $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line); return null; } $matches = $this->findMethodProphecies($prophecy, $methodName, $arguments); if (!count($matches)) { $this->unexpectedCalls->attach(new Call($methodName, $arguments, null, null, $file, $line), $prophecy); $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line); return null; } @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; }); $score = $matches[0][0]; $methodProphecy = $matches[0][1]; $returnValue = null; $exception = null; if ($promise = $methodProphecy->getPromise()) { try { $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy); } catch (\Exception $e) { $exception = $e; } } if ($methodProphecy->hasReturnVoid() && $returnValue !== null) { throw new MethodProphecyException( "The method \"$methodName\" has a void return type, but the promise returned a value", $methodProphecy ); } $this->recordedCalls[] = $call = new Call( $methodName, $arguments, $returnValue, $exception, $file, $line ); $call->addScore($methodProphecy->getArgumentsWildcard(), $score); if (null !== $exception) { throw $exception; } return $returnValue; } public function findCalls($methodName, ArgumentsWildcard $wildcard) { $methodName = strtolower($methodName); return array_values( array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) { return $methodName === strtolower($call->getMethodName()) && 0 < $call->getScore($wildcard) ; }) ); } public function checkUnexpectedCalls() { foreach ($this->unexpectedCalls as $call) { $prophecy = $this->unexpectedCalls[$call]; if (!count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) { throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments()); } } } private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName, array $arguments) { $classname = get_class($prophecy->reveal()); $indentationLength = 8; $argstring = implode( ",\n", $this->indentArguments( array_map(array($this->util, 'stringify'), $arguments), $indentationLength ) ); $expected = array(); foreach (array_merge(...array_values($prophecy->getMethodProphecies())) as $methodProphecy) { $expected[] = sprintf( " - %s(\n" . "%s\n" . " )", $methodProphecy->getMethodName(), implode( ",\n", $this->indentArguments( array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()), $indentationLength ) ) ); } return new UnexpectedCallException( sprintf( "Unexpected method call on %s:\n". " - %s(\n". "%s\n". " )\n". "expected calls were:\n". "%s", $classname, $methodName, $argstring, implode("\n", $expected) ), $prophecy, $methodName, $arguments ); } private function indentArguments(array $arguments, $indentationLength) { return preg_replace_callback( '/^/m', function () use ($indentationLength) { return str_repeat(' ', $indentationLength); }, $arguments ); } private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments) { $matches = array(); foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) { if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) { $matches[] = array($score, $methodProphecy); } } return $matches; } } methodName = $methodName; $this->arguments = $arguments; $this->returnValue = $returnValue; $this->exception = $exception; $this->scores = new \SplObjectStorage(); if ($file) { $this->file = $file; $this->line = intval($line); } } public function getMethodName() { return $this->methodName; } public function getArguments() { return $this->arguments; } public function getReturnValue() { return $this->returnValue; } public function getException() { return $this->exception; } public function getFile() { return $this->file; } public function getLine() { return $this->line; } public function getCallPlace() { if (null === $this->file) { return 'unknown'; } return sprintf('%s:%d', $this->file, $this->line); } public function addScore(ArgumentsWildcard $wildcard, $score) { $this->scores[$wildcard] = $score; return $this; } public function getScore(ArgumentsWildcard $wildcard) { if (isset($this->scores[$wildcard])) { return $this->scores[$wildcard]; } return $this->scores[$wildcard] = $wildcard->scoreArguments($this->getArguments()); } } getName(); } else { foreach ($interfaces as $interface) { $parts[] = $interface->getShortName(); } } if (!count($parts)) { $parts[] = 'stdClass'; } return sprintf('Double\%s\P%d', implode('\\', $parts), self::$counter++); } } generator = $generator ?: new ClassCodeGenerator; } public function create($classname, Node\ClassNode $class) { $code = $this->generator->generate($classname, $class); $return = eval($code); if (!class_exists($classname, false)) { if (count($class->getInterfaces())) { throw new ClassCreatorException(sprintf( 'Could not double `%s` and implement interfaces: [%s].', $class->getParentClass(), implode(', ', $class->getInterfaces()) ), $class); } throw new ClassCreatorException( sprintf('Could not double `%s`.', $class->getParentClass()), $class ); } return $return; } } parentClass; } public function setParentClass($class) { $this->parentClass = $class ?: 'stdClass'; } public function getInterfaces() { return $this->interfaces; } public function addInterface($interface) { if ($this->hasInterface($interface)) { return; } array_unshift($this->interfaces, $interface); } public function hasInterface($interface) { return in_array($interface, $this->interfaces); } public function getProperties() { return $this->properties; } public function addProperty($name, $visibility = 'public') { $visibility = strtolower($visibility); if (!in_array($visibility, array('public', 'private', 'protected'))) { throw new InvalidArgumentException(sprintf( '`%s` property visibility is not supported.', $visibility )); } $this->properties[$name] = $visibility; } public function getMethods() { return $this->methods; } public function addMethod(MethodNode $method, $force = false) { if (!$this->isExtendable($method->getName())){ $message = sprintf( 'Method `%s` is not extendable, so can not be added.', $method->getName() ); throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); } if ($force || !isset($this->methods[$method->getName()])) { $this->methods[$method->getName()] = $method; } } public function removeMethod($name) { unset($this->methods[$name]); } public function getMethod($name) { return $this->hasMethod($name) ? $this->methods[$name] : null; } public function hasMethod($name) { return isset($this->methods[$name]); } public function getUnextendableMethods() { return $this->unextendableMethods; } public function addUnextendableMethod($unextendableMethod) { if (!$this->isExtendable($unextendableMethod)){ return; } $this->unextendableMethods[] = $unextendableMethod; } public function isExtendable($method) { return !in_array($method, $this->unextendableMethods); } } name = $name; $this->code = $code; $this->returnTypeNode = new ReturnTypeNode(); } public function getVisibility() { return $this->visibility; } public function setVisibility($visibility) { $visibility = strtolower($visibility); if (!in_array($visibility, array('public', 'private', 'protected'))) { throw new InvalidArgumentException(sprintf( '`%s` method visibility is not supported.', $visibility )); } $this->visibility = $visibility; } public function isStatic() { return $this->static; } public function setStatic($static = true) { $this->static = (bool) $static; } public function returnsReference() { return $this->returnsReference; } public function setReturnsReference() { $this->returnsReference = true; } public function getName() { return $this->name; } public function addArgument(ArgumentNode $argument) { $this->arguments[] = $argument; } public function getArguments() { return $this->arguments; } public function hasReturnType() { return (bool) $this->returnTypeNode->getNonNullTypes(); } public function setReturnTypeNode(ReturnTypeNode $returnTypeNode): void { $this->returnTypeNode = $returnTypeNode; } public function setReturnType($type = null) { $this->returnTypeNode = ($type === '' || $type === null) ? new ReturnTypeNode() : new ReturnTypeNode($type); } public function setNullableReturnType($bool = true) { if ($bool) { $this->returnTypeNode = new ReturnTypeNode('null', ...$this->returnTypeNode->getTypes()); } else { $this->returnTypeNode = new ReturnTypeNode(...$this->returnTypeNode->getNonNullTypes()); } } public function getReturnType() { if ($types = $this->returnTypeNode->getNonNullTypes()) { return $types[0]; } return null; } public function getReturnTypeNode() : ReturnTypeNode { return $this->returnTypeNode; } public function hasNullableReturnType() { return $this->returnTypeNode->canUseNullShorthand(); } public function setCode($code) { $this->code = $code; } public function getCode() { if ($this->returnsReference) { return "throw new \Prophecy\Exception\Doubler\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');"; } return (string) $this->code; } public function useParentCode() { $this->code = sprintf( 'return parent::%s(%s);', $this->getName(), implode(', ', array_map(array($this, 'generateArgument'), $this->arguments) ) ); } private function generateArgument(ArgumentNode $arg) { $argument = '$'.$arg->getName(); if ($arg->isVariadic()) { $argument = '...'.$argument; } return $argument; } } types['void']) && count($this->types) !== 1) { throw new DoubleException('void cannot be part of a union'); } if (isset($this->types['never']) && count($this->types) !== 1) { throw new DoubleException('never cannot be part of a union'); } parent::guardIsValidType(); } public function isVoid() { return $this->types == ['void' => 'void']; } public function hasReturnStatement(): bool { return $this->types !== ['void' => 'void'] && $this->types !== ['never' => 'never']; } } getRealType($type); $this->types[$type] = $type; } $this->guardIsValidType(); } public function canUseNullShorthand(): bool { return isset($this->types['null']) && count($this->types) <= 2; } public function getTypes(): array { return array_values($this->types); } public function getNonNullTypes(): array { $nonNullTypes = $this->types; unset($nonNullTypes['null']); return array_values($nonNullTypes); } protected function prefixWithNsSeparator(string $type): string { return '\\' . ltrim($type, '\\'); } protected function getRealType(string $type): string { switch ($type) { case 'double': case 'real': return 'float'; case 'boolean': return 'bool'; case 'integer': return 'int'; case 'self': case 'static': case 'array': case 'callable': case 'bool': case 'false': case 'float': case 'int': case 'string': case 'iterable': case 'object': case 'null': return $type; case 'mixed': return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; default: return $this->prefixWithNsSeparator($type); } } protected function guardIsValidType() { if ($this->types == ['null' => 'null']) { throw new DoubleException('Type cannot be standalone null'); } if ($this->types == ['false' => 'false']) { throw new DoubleException('Type cannot be standalone false'); } if ($this->types == ['false' => 'false', 'null' => 'null']) { throw new DoubleException('Type cannot be nullable false'); } if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && count($this->types) !== 1) { throw new DoubleException('mixed cannot be part of a union'); } } } name = $name; $this->typeNode = new ArgumentTypeNode(); } public function getName() { return $this->name; } public function setTypeNode(ArgumentTypeNode $typeNode) { $this->typeNode = $typeNode; } public function getTypeNode() : ArgumentTypeNode { return $this->typeNode; } public function hasDefault() { return $this->isOptional() && !$this->isVariadic(); } public function getDefault() { return $this->default; } public function setDefault($default = null) { $this->optional = true; $this->default = $default; } public function isOptional() { return $this->optional; } public function setAsPassedByReference($byReference = true) { $this->byReference = $byReference; } public function isPassedByReference() { return $this->byReference; } public function setAsVariadic($isVariadic = true) { $this->isVariadic = $isVariadic; } public function isVariadic() { return $this->isVariadic; } public function getTypeHint() { $type = $this->typeNode->getNonNullTypes() ? $this->typeNode->getNonNullTypes()[0] : null; return $type ? ltrim($type, '\\') : null; } public function setTypeHint($typeHint = null) { $this->typeNode = ($typeHint === null) ? new ArgumentTypeNode() : new ArgumentTypeNode($typeHint); } public function isNullable() { return $this->typeNode->canUseNullShorthand(); } public function setAsNullable($isNullable = true) { $nonNullTypes = $this->typeNode->getNonNullTypes(); $this->typeNode = $isNullable ? new ArgumentTypeNode('null', ...$nonNullTypes) : new ArgumentTypeNode(...$nonNullTypes); } } = 80000; default: return false; } } public function isBuiltInReturnTypeHint($type) { if ($type === 'void') { return true; } return $this->isBuiltInParamTypeHint($type); } } getParentClass(), implode(', ', array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces()) ) ); foreach ($class->getProperties() as $name => $visibility) { $code .= sprintf("%s \$%s;\n", $visibility, $name); } $code .= "\n"; foreach ($class->getMethods() as $method) { $code .= $this->generateMethod($method)."\n"; } $code .= "\n}"; return sprintf("namespace %s {\n%s\n}", $namespace, $code); } private function generateMethod(Node\MethodNode $method) { $php = sprintf("%s %s function %s%s(%s)%s {\n", $method->getVisibility(), $method->isStatic() ? 'static' : '', $method->returnsReference() ? '&':'', $method->getName(), implode(', ', $this->generateArguments($method->getArguments())), ($ret = $this->generateTypes($method->getReturnTypeNode())) ? ': '.$ret : '' ); $php .= $method->getCode()."\n"; return $php.'}'; } private function generateTypes(TypeNodeAbstract $typeNode): string { if (!$typeNode->getTypes()) { return ''; } if ($typeNode->canUseNullShorthand()) { return sprintf( '?%s', $typeNode->getNonNullTypes()[0]); } else { return join('|', $typeNode->getTypes()); } } private function generateArguments(array $arguments) { return array_map(function (Node\ArgumentNode $argument){ $php = $this->generateTypes($argument->getTypeNode()); $php .= ' '.($argument->isPassedByReference() ? '&' : ''); $php .= $argument->isVariadic() ? '...' : ''; $php .= '$'.$argument->getName(); if ($argument->isOptional() && !$argument->isVariadic()) { $php .= ' = '.var_export($argument->getDefault(), true); } return $php; }, $arguments); } } isInterface()) { throw new InvalidArgumentException(sprintf( "Could not reflect %s as a class, because it\n". "is interface - use the second argument instead.", $class->getName() )); } $this->reflectClassToNode($class, $node); } foreach ($interfaces as $interface) { if (!$interface instanceof ReflectionClass) { throw new InvalidArgumentException(sprintf( "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". "a second argument to `ClassMirror::reflect(...)`, but got %s.", is_object($interface) ? get_class($interface).' class' : gettype($interface) )); } if (false === $interface->isInterface()) { throw new InvalidArgumentException(sprintf( "Could not reflect %s as an interface, because it\n". "is class - use the first argument instead.", $interface->getName() )); } $this->reflectInterfaceToNode($interface, $node); } $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface'); return $node; } private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node) { if (true === $class->isFinal()) { throw new ClassMirrorException(sprintf( 'Could not reflect class %s as it is marked final.', $class->getName() ), $class); } $node->setParentClass($class->getName()); foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { if (false === $method->isProtected()) { continue; } $this->reflectMethodToNode($method, $node); } foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { if (0 === strpos($method->getName(), '_') && !in_array($method->getName(), self::$reflectableMethods)) { continue; } if (true === $method->isFinal()) { $node->addUnextendableMethod($method->getName()); continue; } $this->reflectMethodToNode($method, $node); } } private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node) { $node->addInterface($interface->getName()); foreach ($interface->getMethods() as $method) { $this->reflectMethodToNode($method, $node); } } private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode) { $node = new Node\MethodNode($method->getName()); if (true === $method->isProtected()) { $node->setVisibility('protected'); } if (true === $method->isStatic()) { $node->setStatic(); } if (true === $method->returnsReference()) { $node->setReturnsReference(); } if ($method->hasReturnType()) { $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()->allowsNull()); $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); } elseif (method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) { $returnTypes = $this->getTypeHints($method->getTentativeReturnType(), $method->getDeclaringClass(), $method->getTentativeReturnType()->allowsNull()); $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); } if (is_array($params = $method->getParameters()) && count($params)) { foreach ($params as $param) { $this->reflectArgumentToNode($param, $node); } } $classNode->addMethod($node); } private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode) { $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); $node = new Node\ArgumentNode($name); $typeHints = $this->getTypeHints($parameter->getType(), $parameter->getDeclaringClass(), $parameter->allowsNull()); $node->setTypeNode(new ArgumentTypeNode(...$typeHints)); if ($parameter->isVariadic()) { $node->setAsVariadic(); } if ($this->hasDefaultValue($parameter)) { $node->setDefault($this->getDefaultValue($parameter)); } if ($parameter->isPassedByReference()) { $node->setAsPassedByReference(); } $methodNode->addArgument($node); } private function hasDefaultValue(ReflectionParameter $parameter) { if ($parameter->isVariadic()) { return false; } if ($parameter->isDefaultValueAvailable()) { return true; } return $parameter->isOptional() || ($parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100); } private function getDefaultValue(ReflectionParameter $parameter) { if (!$parameter->isDefaultValueAvailable()) { return null; } return $parameter->getDefaultValue(); } private function getTypeHints(?ReflectionType $type, ?ReflectionClass $class, bool $allowsNull) : array { $types = []; if ($type instanceof ReflectionNamedType) { $types = [$type->getName()]; } elseif ($type instanceof ReflectionUnionType) { $types = $type->getTypes(); } elseif ($type instanceof ReflectionIntersectionType) { throw new ClassMirrorException('Doubling intersection types is not supported', $class); } elseif(is_object($type)) { throw new ClassMirrorException('Unknown reflection type ' . get_class($type), $class); } $types = array_map( function(string $type) use ($class) { if ($type === 'self') { return $class->getName(); } if ($type === 'parent') { return $class->getParentClass()->getName(); } return $type; }, $types ); if ($types && $types != ['mixed'] && $allowsNull) { $types[] = 'null'; } return $types; } } generateClassId($class, $interfaces); if (isset(self::$classes[$classId])) { return self::$classes[$classId]; } return self::$classes[$classId] = parent::createDoubleClass($class, $interfaces); } private function generateClassId(ReflectionClass $class = null, array $interfaces) { $parts = array(); if (null !== $class) { $parts[] = $class->getName(); } foreach ($interfaces as $interface) { $parts[] = $interface->getName(); } foreach ($this->getClassPatches() as $patch) { $parts[] = get_class($patch); } sort($parts); return md5(implode('', $parts)); } public function resetCache() { self::$classes = array(); } } doubler = $doubler; } public function setParentClass($class) { if (null !== $this->double) { throw new DoubleException('Can not extend class with already instantiated double.'); } if (!$class instanceof ReflectionClass) { if (!class_exists($class)) { throw new ClassNotFoundException(sprintf('Class %s not found.', $class), $class); } $class = new ReflectionClass($class); } $this->class = $class; } public function addInterface($interface) { if (null !== $this->double) { throw new DoubleException( 'Can not implement interface with already instantiated double.' ); } if (!$interface instanceof ReflectionClass) { if (!interface_exists($interface)) { throw new InterfaceNotFoundException( sprintf('Interface %s not found.', $interface), $interface ); } $interface = new ReflectionClass($interface); } $this->interfaces[] = $interface; } public function setArguments(array $arguments = null) { $this->arguments = $arguments; } public function getInstance() { if (null === $this->double) { if (null !== $this->arguments) { return $this->double = $this->doubler->double( $this->class, $this->interfaces, $this->arguments ); } $this->double = $this->doubler->double($this->class, $this->interfaces); } return $this->double; } } mirror = $mirror ?: new ClassMirror; $this->creator = $creator ?: new ClassCreator; $this->namer = $namer ?: new NameGenerator; } public function getClassPatches() { return $this->patches; } public function registerClassPatch(ClassPatchInterface $patch) { $this->patches[] = $patch; @usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { return $patch2->getPriority() - $patch1->getPriority(); }); } public function double(ReflectionClass $class = null, array $interfaces, array $args = null) { foreach ($interfaces as $interface) { if (!$interface instanceof ReflectionClass) { throw new InvalidArgumentException(sprintf( "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". "a second argument to `Doubler::double(...)`, but got %s.", is_object($interface) ? get_class($interface).' class' : gettype($interface) )); } } $classname = $this->createDoubleClass($class, $interfaces); $reflection = new ReflectionClass($classname); if (null !== $args) { return $reflection->newInstanceArgs($args); } if ((null === $constructor = $reflection->getConstructor()) || ($constructor->isPublic() && !$constructor->isFinal())) { return $reflection->newInstance(); } if (!$this->instantiator) { $this->instantiator = new Instantiator(); } return $this->instantiator->instantiate($classname); } protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) { $name = $this->namer->name($class, $interfaces); $node = $this->mirror->reflect($class, $interfaces); foreach ($this->patches as $patch) { if ($patch->supports($node)) { $patch->apply($node); } } $this->creator->create($name, $node); return $name; } } getMethods()); $methodsToRemove = array_intersect($methodNames, $this->getKeywords()); foreach ($methodsToRemove as $methodName) { $node->removeMethod($methodName); } } public function getPriority() { return 49; } private function getKeywords() { return ['__halt_compiler']; } } getParentClass(); } public function apply(ClassNode $node) { foreach ($node->getMethod('newInstance')->getArguments() as $argument) { $argument->setDefault(null); } } public function getPriority() { return 50; } } isExtendable('__construct')) { return; } if (!$node->hasMethod('__construct')) { $node->addMethod(new MethodNode('__construct', '')); return; } $constructor = $node->getMethod('__construct'); foreach ($constructor->getArguments() as $argument) { $argument->setDefault(null); } $constructor->setCode(<<getParentClass() || is_subclass_of($node->getParentClass(), 'Exception'); } public function apply(ClassNode $node) { if ($node->hasMethod('setTraceOptions')) { $node->getMethod('setTraceOptions')->useParentCode(); } if ($node->hasMethod('getTraceOptions')) { $node->getMethod('getTraceOptions')->useParentCode(); } } public function getPriority() { return -50; } } tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever; } public function supports(ClassNode $node) { return true; } public function apply(ClassNode $node) { $types = array_filter($node->getInterfaces(), function ($interface) { return 0 !== strpos($interface, 'Prophecy\\'); }); $types[] = $node->getParentClass(); foreach ($types as $type) { $reflectionClass = new \ReflectionClass($type); while ($reflectionClass) { $tagList = $this->tagRetriever->getTagList($reflectionClass); foreach ($tagList as $tag) { $methodName = $tag->getMethodName(); if (empty($methodName)) { continue; } if (!$reflectionClass->hasMethod($methodName)) { $methodNode = new MethodNode($methodName); if (in_array($methodName, self::MAGIC_METHODS_WITH_ARGUMENTS)) { foreach($tag->getArguments() as $argument) { $argumentNode = new ArgumentNode($argument['name']); $methodNode->addArgument($argumentNode); } } $methodNode->setStatic($tag->isStatic()); $node->addMethod($methodNode); } } $reflectionClass = $reflectionClass->getParentClass(); } } } public function getPriority() { return 50; } } addInterface('Prophecy\Prophecy\ProphecySubjectInterface'); $node->addProperty('objectProphecyClosure', 'private'); foreach ($node->getMethods() as $name => $method) { if ('__construct' === strtolower($name)) { continue; } if (!$method->getReturnTypeNode()->hasReturnStatement()) { $method->setCode( '$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' ); } else { $method->setCode( 'return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' ); } } $prophecySetter = new MethodNode('setProphecy'); $prophecyArgument = new ArgumentNode('prophecy'); $prophecyArgument->setTypeNode(new ArgumentTypeNode('Prophecy\Prophecy\ProphecyInterface')); $prophecySetter->addArgument($prophecyArgument); $prophecySetter->setCode(<<objectProphecyClosure) { \$this->objectProphecyClosure = static function () use (\$prophecy) { return \$prophecy; }; } PHP ); $prophecyGetter = new MethodNode('getProphecy'); $prophecyGetter->setCode('return \call_user_func($this->objectProphecyClosure);'); if ($node->hasMethod('__call')) { $__call = $node->getMethod('__call'); } else { $__call = new MethodNode('__call'); $__call->addArgument(new ArgumentNode('name')); $__call->addArgument(new ArgumentNode('arguments')); $node->addMethod($__call, true); } $__call->setCode(<<addMethod($prophecySetter, true); $node->addMethod($prophecyGetter, true); } public function getPriority() { return 0; } } getInterfaces())) { return false; } if (in_array('IteratorAggregate', $node->getInterfaces())) { return false; } foreach ($node->getInterfaces() as $interface) { if ('Traversable' !== $interface && !is_subclass_of($interface, 'Traversable')) { continue; } if ('Iterator' === $interface || is_subclass_of($interface, 'Iterator')) { continue; } if ('IteratorAggregate' === $interface || is_subclass_of($interface, 'IteratorAggregate')) { continue; } return true; } return false; } public function apply(ClassNode $node) { $node->addInterface('Iterator'); $currentMethod = new MethodNode('current'); (\PHP_VERSION_ID >= 80100) && $currentMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); $node->addMethod($currentMethod); $keyMethod = new MethodNode('key'); (\PHP_VERSION_ID >= 80100) && $keyMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); $node->addMethod($keyMethod); $nextMethod = new MethodNode('next'); (\PHP_VERSION_ID >= 80100) && $nextMethod->setReturnTypeNode(new ReturnTypeNode('void')); $node->addMethod($nextMethod); $rewindMethod = new MethodNode('rewind'); (\PHP_VERSION_ID >= 80100) && $rewindMethod->setReturnTypeNode(new ReturnTypeNode('void')); $node->addMethod($rewindMethod); $validMethod = new MethodNode('valid'); (\PHP_VERSION_ID >= 80100) && $validMethod->setReturnTypeNode(new ReturnTypeNode('bool')); $node->addMethod($validMethod); } public function getPriority() { return 100; } } implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node); } private function implementsAThrowableInterface(ClassNode $node) { foreach ($node->getInterfaces() as $type) { if (is_a($type, 'Throwable', true)) { return true; } } return false; } private function doesNotExtendAThrowableClass(ClassNode $node) { return !is_a($node->getParentClass(), 'Throwable', true); } public function apply(ClassNode $node) { $this->checkItCanBeDoubled($node); $this->setParentClassToException($node); } private function checkItCanBeDoubled(ClassNode $node) { $className = $node->getParentClass(); if ($className !== 'stdClass') { throw new ClassCreatorException( sprintf( 'Cannot double concrete class %s as well as implement Traversable', $className ), $node ); } } private function setParentClassToException(ClassNode $node) { $node->setParentClass('Exception'); $node->removeMethod('getMessage'); $node->removeMethod('getCode'); $node->removeMethod('getFile'); $node->removeMethod('getLine'); $node->removeMethod('getTrace'); $node->removeMethod('getPrevious'); $node->removeMethod('getNext'); $node->removeMethod('getTraceAsString'); } public function getPriority() { return 100; } } getParentClass()) { return false; } return 'SplFileInfo' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'SplFileInfo') ; } public function apply(ClassNode $node) { if ($node->hasMethod('__construct')) { $constructor = $node->getMethod('__construct'); } else { $constructor = new MethodNode('__construct'); $node->addMethod($constructor); } if ($this->nodeIsDirectoryIterator($node)) { $constructor->setCode('return parent::__construct("' . __DIR__ . '");'); return; } if ($this->nodeIsSplFileObject($node)) { $filePath = str_replace('\\','\\\\',__FILE__); $constructor->setCode('return parent::__construct("' . $filePath .'");'); return; } if ($this->nodeIsSymfonySplFileInfo($node)) { $filePath = str_replace('\\','\\\\',__FILE__); $constructor->setCode('return parent::__construct("' . $filePath .'", "", "");'); return; } $constructor->useParentCode(); } public function getPriority() { return 50; } private function nodeIsDirectoryIterator(ClassNode $node) { $parent = $node->getParentClass(); return 'DirectoryIterator' === $parent || is_subclass_of($parent, 'DirectoryIterator'); } private function nodeIsSplFileObject(ClassNode $node) { $parent = $node->getParentClass(); return 'SplFileObject' === $parent || is_subclass_of($parent, 'SplFileObject'); } private function nodeIsSymfonySplFileInfo(ClassNode $node) { $parent = $node->getParentClass(); return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; } } expectedCount = intval($count); } public function getExpectedCount() { return $this->expectedCount; } } calls = $calls; } public function getCalls() { return $this->calls; } } getMessage(); $message = strtr($message, array("\n" => "\n "))."\n"; $message = empty($this->exceptions) ? $message : "\n" . $message; $this->message = rtrim($this->message.$message); $this->exceptions[] = $exception; } public function getExceptions() { return $this->exceptions; } public function setObjectProphecy(ObjectProphecy $objectProphecy) { $this->objectProphecy = $objectProphecy; } public function getObjectProphecy() { return $this->objectProphecy; } } getObjectProphecy()); $this->methodProphecy = $methodProphecy; } public function getMethodProphecy() { return $this->methodProphecy; } } objectProphecy = $objectProphecy; } public function getObjectProphecy() { return $this->objectProphecy; } } methodName = $methodName; $this->arguments = $arguments; } public function getMethodName() { return $this->methodName; } public function getArguments() { return $this->arguments; } } methodName = $methodName; $this->className = $className; } public function getMethodName() { return $this->methodName; } public function getClassName() { return $this->className; } } node = $node; } public function getClassNode() { return $this->node; } } class = $class; } public function getReflectedClass() { return $this->class; } } classname = $classname; $this->methodName = $methodName; } public function getClassname() { return $this->classname; } public function getMethodName() { return $this->methodName; } } getClassname(); } } classname = $classname; $this->methodName = $methodName; $this->arguments = $arguments; } public function getClassname() { return $this->classname; } public function getMethodName() { return $this->methodName; } public function getArguments() { return $this->arguments; } } classname = $classname; } public function getClassname() { return $this->classname; } } array( 'name' => 'codeception/phar-file', 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'reference' => '62edd74497422a16f882a011106e3ba4d209cc95', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => true, ), 'versions' => array( 'behat/gherkin' => array( 'pretty_version' => 'v4.9.0', 'version' => '4.9.0.0', 'reference' => '0bc8d1e30e96183e4f36db9dc79caead300beff4', 'type' => 'library', 'install_path' => __DIR__ . '/../behat/gherkin', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/codeception' => array( 'pretty_version' => '4.2.2', 'version' => '4.2.2.0', 'reference' => 'b88014f3348c93f3df99dc6d0967b0dbfa804474', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/codeception', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/lib-asserts' => array( 'pretty_version' => '1.13.2', 'version' => '1.13.2.0', 'reference' => '184231d5eab66bc69afd6b9429344d80c67a33b6', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/lib-asserts', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/lib-innerbrowser' => array( 'pretty_version' => '1.5.1', 'version' => '1.5.1.0', 'reference' => '31b4b56ad53c3464fcb2c0a14d55a51a201bd3c2', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/lib-innerbrowser', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-amqp' => array( 'pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => 'be399360429aff06d6291c389e9c64aff567b041', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-amqp', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-apc' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', 'reference' => '07fbd6dd12b9d3f4b738bcac8266eecefb470365', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-apc', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-asserts' => array( 'pretty_version' => '1.3.1', 'version' => '1.3.1.0', 'reference' => '59374f2fef0cabb9e8ddb53277e85cdca74328de', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-asserts', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-cli' => array( 'pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => '1f841ad4a1d43e5d9e60a43c4cc9e5af8008024f', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-cli', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-db' => array( 'pretty_version' => '1.2.0', 'version' => '1.2.0.0', 'reference' => '04c3e66fbd3a3ced17fcccc49627f6393a97b04b', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-db', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-filesystem' => array( 'pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => '781be167fb1557bfc9b61e0a4eac60a32c534ec1', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-filesystem', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-ftp' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', 'reference' => 'eed215edf32998c212da6f375a6d1c5e7c3a01c0', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-ftp', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-memcache' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => '96d65b6cf80bf19a858d7fa21257b8af1fe806ee', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-memcache', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-mongodb' => array( 'pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => '7bafcc9e7755b762436d742aa88c53726b5ddca5', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-mongodb', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-phpbrowser' => array( 'pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => '8ba6bede11d0914e74d98691f427fd8f397f192e', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-phpbrowser', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-queue' => array( 'pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => 'cf4116ed143758c8f36ac24c787f6647787e40f8', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-queue', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-redis' => array( 'pretty_version' => '1.2.0', 'version' => '1.2.0.0', 'reference' => 'eb68a805ee7a84af6548697bf6e21f1125e99b92', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-redis', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-rest' => array( 'pretty_version' => '1.4.2', 'version' => '1.4.2.0', 'reference' => '9cd7a87fd9343494e7782f7bdb51687c25046917', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-rest', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-sequence' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => 'b75be26681ae90824cde8f8df785981f293667e1', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-sequence', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-soap' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => '49d081cb0c455f405344a497fdbfda20168dcb9d', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-soap', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/module-webdriver' => array( 'pretty_version' => '1.4.0', 'version' => '1.4.0.0', 'reference' => 'baa18b7bf70aa024012f967b5ce5021e1faa9151', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/module-webdriver', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/phar-file' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'reference' => '62edd74497422a16f882a011106e3ba4d209cc95', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/phpunit-wrapper' => array( 'pretty_version' => '8.1.6', 'version' => '8.1.6.0', 'reference' => '7d3479bab7e2b6349044db8af11cd05d57809f9c', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/phpunit-wrapper', 'aliases' => array(), 'dev_requirement' => false, ), 'codeception/stub' => array( 'pretty_version' => '3.7.0', 'version' => '3.7.0.0', 'reference' => '468dd5fe659f131fc997f5196aad87512f9b1304', 'type' => 'library', 'install_path' => __DIR__ . '/../codeception/stub', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/ca-bundle' => array( 'pretty_version' => '1.3.3', 'version' => '1.3.3.0', 'reference' => '30897edbfb15e784fe55587b4f73ceefd3c4d98c', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), 'dev_requirement' => false, ), 'doctrine/instantiator' => array( 'pretty_version' => '1.4.1', 'version' => '1.4.1.0', 'reference' => '10dcfce151b967d20fde1b34ae6640712c3891bc', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/instantiator', 'aliases' => array(), 'dev_requirement' => false, ), 'facebook/webdriver' => array( 'dev_requirement' => false, 'replaced' => array( 0 => '*', ), ), 'flow/jsonpath' => array( 'dev_requirement' => false, 'replaced' => array( 0 => '*', ), ), 'guzzlehttp/guzzle' => array( 'pretty_version' => '6.5.8', 'version' => '6.5.8.0', 'reference' => 'a52f0440530b54fa079ce76e8c5d196a42cad981', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/promises' => array( 'pretty_version' => '1.5.1', 'version' => '1.5.1.0', 'reference' => 'fe752aedc9fd8fcca3fe7ad05d419d32998a06da', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/promises', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( 'pretty_version' => '1.9.0', 'version' => '1.9.0.0', 'reference' => 'e98e3e6d4f86621a9b75f623996e6bbdeb4b9318', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/consistency' => array( 'pretty_version' => '1.17.05.02', 'version' => '1.17.05.02', 'reference' => 'fd7d0adc82410507f332516faf655b6ed22e4c2f', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/consistency', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/console' => array( 'pretty_version' => '3.17.05.02', 'version' => '3.17.05.02', 'reference' => 'e231fd3ea70e6d773576ae78de0bdc1daf331a66', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/console', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/event' => array( 'pretty_version' => '1.17.01.13', 'version' => '1.17.01.13', 'reference' => '6c0060dced212ffa3af0e34bb46624f990b29c54', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/event', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/exception' => array( 'pretty_version' => '1.17.01.16', 'version' => '1.17.01.16', 'reference' => '091727d46420a3d7468ef0595651488bfc3a458f', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/exception', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/file' => array( 'pretty_version' => '1.17.07.11', 'version' => '1.17.07.11', 'reference' => '35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/file', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/iterator' => array( 'pretty_version' => '2.17.01.10', 'version' => '2.17.01.10', 'reference' => 'd1120ba09cb4ccd049c86d10058ab94af245f0cc', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/iterator', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/protocol' => array( 'pretty_version' => '1.17.01.14', 'version' => '1.17.01.14', 'reference' => '5c2cf972151c45f373230da170ea015deecf19e2', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/protocol', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/stream' => array( 'pretty_version' => '1.17.02.21', 'version' => '1.17.02.21', 'reference' => '3293cfffca2de10525df51436adf88a559151d82', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/stream', 'aliases' => array(), 'dev_requirement' => false, ), 'hoa/ustring' => array( 'pretty_version' => '4.17.01.16', 'version' => '4.17.01.16', 'reference' => 'e6326e2739178799b1fe3fdd92029f9517fa17a0', 'type' => 'library', 'install_path' => __DIR__ . '/../hoa/ustring', 'aliases' => array(), 'dev_requirement' => false, ), 'justinrainbow/json-schema' => array( 'pretty_version' => '5.2.12', 'version' => '5.2.12.0', 'reference' => 'ad87d5a5ca981228e0e205c2bc7dfb8e24559b60', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), 'dev_requirement' => false, ), 'laravel-zero/phar-updater' => array( 'pretty_version' => 'v1.0.6', 'version' => '1.0.6.0', 'reference' => 'ce6c555f34a1a779e84e41b241b755929b170009', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel-zero/phar-updater', 'aliases' => array(), 'dev_requirement' => false, ), 'myclabs/deep-copy' => array( 'pretty_version' => '1.11.0', 'version' => '1.11.0.0', 'reference' => '14daed4296fae74d9e3201d2c4925d1acb7aa614', 'type' => 'library', 'install_path' => __DIR__ . '/../myclabs/deep-copy', '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( 'dev_requirement' => false, 'replaced' => array( 0 => '1.0.6', ), ), 'phar-io/manifest' => array( 'pretty_version' => '2.0.3', 'version' => '2.0.3.0', 'reference' => '97803eca37d319dfa7826cc2437fc020857acb53', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/manifest', 'aliases' => array(), 'dev_requirement' => false, ), 'phar-io/version' => array( 'pretty_version' => '3.2.1', 'version' => '3.2.1.0', 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/version', 'aliases' => array(), 'dev_requirement' => false, ), 'php-amqplib/php-amqplib' => array( 'pretty_version' => 'v3.2.0', 'version' => '3.2.0.0', 'reference' => '0bec5b392428e0ac3b3f34fbc4e02d706995833e', 'type' => 'library', 'install_path' => __DIR__ . '/../php-amqplib/php-amqplib', 'aliases' => array(), 'dev_requirement' => false, ), 'php-webdriver/webdriver' => array( 'pretty_version' => '1.12.1', 'version' => '1.12.1.0', 'reference' => 'b27ddf458d273c7d4602106fcaf978aa0b7fe15a', 'type' => 'library', 'install_path' => __DIR__ . '/../php-webdriver/webdriver', 'aliases' => array(), 'dev_requirement' => false, ), 'phpdocumentor/reflection-common' => array( 'pretty_version' => '2.2.0', 'version' => '2.2.0.0', 'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b', 'type' => 'library', 'install_path' => __DIR__ . '/../phpdocumentor/reflection-common', 'aliases' => array(), 'dev_requirement' => false, ), 'phpdocumentor/reflection-docblock' => array( 'pretty_version' => '5.3.0', 'version' => '5.3.0.0', 'reference' => '622548b623e81ca6d78b721c5e029f4ce664f170', 'type' => 'library', 'install_path' => __DIR__ . '/../phpdocumentor/reflection-docblock', 'aliases' => array(), 'dev_requirement' => false, ), 'phpdocumentor/type-resolver' => array( 'pretty_version' => '1.6.1', 'version' => '1.6.1.0', 'reference' => '77a32518733312af16a44300404e945338981de3', 'type' => 'library', 'install_path' => __DIR__ . '/../phpdocumentor/type-resolver', 'aliases' => array(), 'dev_requirement' => false, ), 'phpseclib/phpseclib' => array( 'pretty_version' => '2.0.37', 'version' => '2.0.37.0', 'reference' => 'c812fbb4d6b4d7f30235ab7298a12f09ba13b37c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpseclib/phpseclib', 'aliases' => array(), 'dev_requirement' => false, ), 'phpspec/prophecy' => array( 'pretty_version' => 'v1.15.0', 'version' => '1.15.0.0', 'reference' => 'bbcd7380b0ebf3961ee21409db7b38bc31d69a13', 'type' => 'library', 'install_path' => __DIR__ . '/../phpspec/prophecy', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/php-code-coverage' => array( 'pretty_version' => '7.0.15', 'version' => '7.0.15.0', 'reference' => '819f92bba8b001d4363065928088de22f25a3a48', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/php-file-iterator' => array( 'pretty_version' => '2.0.5', 'version' => '2.0.5.0', 'reference' => '42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-file-iterator', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/php-text-template' => array( 'pretty_version' => '1.2.1', 'version' => '1.2.1.0', 'reference' => '31f8b717e51d9a2afca6c9f046f5d69fc27c8686', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-text-template', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/php-timer' => array( 'pretty_version' => '2.1.3', 'version' => '2.1.3.0', 'reference' => '2454ae1765516d20c4ffe103d85a58a9a3bd5662', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-timer', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/php-token-stream' => array( 'pretty_version' => '3.1.3', 'version' => '3.1.3.0', 'reference' => '9c1da83261628cb24b6a6df371b6e312b3954768', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-token-stream', 'aliases' => array(), 'dev_requirement' => false, ), 'phpunit/phpunit' => array( 'pretty_version' => '8.5.28', 'version' => '8.5.28.0', 'reference' => '8f2d1c9c7b30382459c871467853da1a6e44fbd4', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/phpunit', 'aliases' => array(), 'dev_requirement' => false, ), 'predis/predis' => array( 'pretty_version' => 'v1.1.10', 'version' => '1.1.10.0', 'reference' => 'a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e', 'type' => 'library', 'install_path' => __DIR__ . '/../predis/predis', 'aliases' => array(), '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/event-dispatcher-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0', ), ), 'psr/http-message' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), 'dev_requirement' => false, ), 'psr/http-message-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0', ), ), 'psr/log-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0|2.0', ), ), 'ralouphie/getallheaders' => array( 'pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/code-unit-reverse-lookup' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', 'reference' => '1de8cd5c010cb153fcd68b8d0f64606f523f7619', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/comparator' => array( 'pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '1071dfcef776a57013124ff35e1fc41ccd294758', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/comparator', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/diff' => array( 'pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '14f72dd46eaf2f2293cbe79c93cc0bc43161a211', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/diff', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/environment' => array( 'pretty_version' => '4.2.4', 'version' => '4.2.4.0', 'reference' => 'd47bbbad83711771f167c72d4e3f25f7fcc1f8b0', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/environment', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/exporter' => array( 'pretty_version' => '3.1.4', 'version' => '3.1.4.0', 'reference' => '0c32ea2e40dbf59de29f3b49bf375176ce7dd8db', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/exporter', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/global-state' => array( 'pretty_version' => '3.0.2', 'version' => '3.0.2.0', 'reference' => 'de036ec91d55d2a9e0db2ba975b512cdb1c23921', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/global-state', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/object-enumerator' => array( 'pretty_version' => '3.0.4', 'version' => '3.0.4.0', 'reference' => 'e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-enumerator', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/object-reflector' => array( 'pretty_version' => '1.1.2', 'version' => '1.1.2.0', 'reference' => '9b8772b9cbd456ab45d4a598d2dd1a1bced6363d', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-reflector', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/recursion-context' => array( 'pretty_version' => '3.0.1', 'version' => '3.0.1.0', 'reference' => '367dcba38d6e1977be014dc4b22f47a484dac7fb', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/recursion-context', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/resource-operations' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', 'reference' => '31d35ca87926450c44eae7e2611d45a7a65ea8b3', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/resource-operations', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/type' => array( 'pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'reference' => '0150cfbc4495ed2df3872fb31b26781e4e077eb4', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/type', 'aliases' => array(), 'dev_requirement' => false, ), 'sebastian/version' => array( 'pretty_version' => '2.0.1', 'version' => '2.0.1.0', 'reference' => '99732be0ddb3361e16ad77b68ba41efc8e979019', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/version', 'aliases' => array(), 'dev_requirement' => false, ), 'softcreatr/jsonpath' => array( 'pretty_version' => '0.7.5', 'version' => '0.7.5.0', 'reference' => '008569bf80aa3584834f7890781576bc7b65afa7', 'type' => 'library', 'install_path' => __DIR__ . '/../softcreatr/jsonpath', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/browser-kit' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => '2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/browser-kit', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/console' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => 'c35fafd7f12ebd6f9e29c95a370df7f1fb171a40', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/css-selector' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => 'bd0a6737e48de45b4b0b7b6fc98c78404ddceaed', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/css-selector', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/dom-crawler' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => '53cee1108a9748682b1268bc1a76a3d6a665ede2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dom-crawler', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/event-dispatcher' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => '1e866e9e5c1b22168e0ce5f0b467f19bba61266a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/event-dispatcher-contracts' => array( 'pretty_version' => 'v1.1.13', 'version' => '1.1.13.0', 'reference' => '1d5cd762abaa6b2a4169d3e77610193a7157129e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/event-dispatcher-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.1', ), ), 'symfony/finder' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => '66bd787edb5e42ff59d3523f623895af05043e4f', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => '6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-idn' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => '59a8d271f00dd0e4c2e518104cc7963f655a1aa8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => '219aa369ceff116e673852dce47c3a41794c14bd', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => '9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php72' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => 'bf44a9fd41feaac72b074de600314a93e2ae78e2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php72', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php73' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => 'e440d35fa0286f77fb45b79a03fedbeda9307e85', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( 'pretty_version' => 'v1.26.0', 'version' => '1.26.0.0', 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/process' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => '5cee9cdc4f7805e2699d9fd66991a0e6df8252a2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/service-contracts' => array( 'pretty_version' => 'v1.1.13', 'version' => '1.1.13.0', 'reference' => 'afa00c500c2d6aea6e3b2f4862355f507bc5ebb4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/yaml' => array( 'pretty_version' => 'v4.4.44', 'version' => '4.4.44.0', 'reference' => 'c2b28c10fb3b7ac67bafa7b8f952cd83f35acde2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), 'dev_requirement' => false, ), 'theseer/tokenizer' => array( 'pretty_version' => '1.2.1', 'version' => '1.2.1.0', 'reference' => '34a41e998c2183e22995f158c581e7b5e755ab9e', 'type' => 'library', 'install_path' => __DIR__ . '/../theseer/tokenizer', 'aliases' => array(), 'dev_requirement' => false, ), 'videlalvaro/php-amqplib' => array( 'dev_requirement' => false, 'replaced' => array( 0 => 'v3.2.0', ), ), 'webmozart/assert' => array( 'pretty_version' => '1.11.0', 'version' => '1.11.0.0', 'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991', 'type' => 'library', 'install_path' => __DIR__ . '/../webmozart/assert', 'aliases' => array(), 'dev_requirement' => false, ), ), ); $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', 'e88992873b7765f9b5710cab95ba5dd7' => $vendorDir . '/hoa/consistency/Prelude.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', 'e3b2795a8a512b6083af088fb53afe6c' => $vendorDir . '/codeception/codeception/functions.php', '3e76f7f02b41af8cea96018933f6b7e3' => $vendorDir . '/hoa/protocol/Wrapper.php', 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', 'd9d39f82a605ebe5918f683dd402334c' => $vendorDir . '/padraic/humbug_get_contents/src/function.php', '3a50d90d85c7fe889a94ae1114b921ce' => $vendorDir . '/padraic/humbug_get_contents/src/functions.php', '2a3c2110e8e0295330dc3d11a4cbc4cb' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php', ); register(true); $includeFiles = \Composer\Autoload\ComposerStaticInit62a680ddf9b2a2f37d125b4c7fbcd759::$files; foreach ($includeFiles as $fileIdentifier => $file) { composerRequire62a680ddf9b2a2f37d125b4c7fbcd759($fileIdentifier, $file); } return $loader; } } function composerRequire62a680ddf9b2a2f37d125b4c7fbcd759($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; require $file; } } $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Codeception\\Exception\\ConnectionException' => $vendorDir . '/codeception/module-webdriver/src/Codeception/Exception/ConnectionException.php', 'Codeception\\Exception\\ExternalUrlException' => $vendorDir . '/codeception/lib-innerbrowser/src/Codeception/Exception/ExternalUrlException.php', 'Codeception\\Lib\\Connector\\Guzzle' => $vendorDir . '/codeception/module-phpbrowser/src/Codeception/Lib/Connector/Guzzle.php', 'Codeception\\Lib\\DbPopulator' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/DbPopulator.php', 'Codeception\\Lib\\Driver\\AmazonSQS' => $vendorDir . '/codeception/module-queue/src/Codeception/Lib/Driver/AmazonSQS.php', 'Codeception\\Lib\\Driver\\Beanstalk' => $vendorDir . '/codeception/module-queue/src/Codeception/Lib/Driver/Beanstalk.php', 'Codeception\\Lib\\Driver\\Db' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/Db.php', 'Codeception\\Lib\\Driver\\Iron' => $vendorDir . '/codeception/module-queue/src/Codeception/Lib/Driver/Iron.php', 'Codeception\\Lib\\Driver\\MongoDb' => $vendorDir . '/codeception/module-mongodb/src/Codeception/Lib/Driver/MongoDb.php', 'Codeception\\Lib\\Driver\\MySql' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/MySql.php', 'Codeception\\Lib\\Driver\\Oci' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/Oci.php', 'Codeception\\Lib\\Driver\\Pheanstalk4' => $vendorDir . '/codeception/module-queue/src/Codeception/Lib/Driver/Pheanstalk4.php', 'Codeception\\Lib\\Driver\\PostgreSql' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/PostgreSql.php', 'Codeception\\Lib\\Driver\\SqlSrv' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/SqlSrv.php', 'Codeception\\Lib\\Driver\\Sqlite' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Driver/Sqlite.php', 'Codeception\\Lib\\Framework' => $vendorDir . '/codeception/lib-innerbrowser/src/Codeception/Lib/Framework.php', 'Codeception\\Lib\\InnerBrowser' => $vendorDir . '/codeception/lib-innerbrowser/src/Codeception/Lib/InnerBrowser.php', 'Codeception\\Lib\\Interfaces\\Db' => $vendorDir . '/codeception/module-db/src/Codeception/Lib/Interfaces/Db.php', 'Codeception\\Lib\\Interfaces\\Queue' => $vendorDir . '/codeception/module-queue/src/Codeception/Lib/Interfaces/Queue.php', 'Codeception\\Lib\\Interfaces\\ScreenshotSaver' => $vendorDir . '/codeception/module-webdriver/src/Codeception/Lib/Interfaces/ScreenshotSaver.php', 'Codeception\\Lib\\Interfaces\\SessionSnapshot' => $vendorDir . '/codeception/module-webdriver/src/Codeception/Lib/Interfaces/SessionSnapshot.php', 'Codeception\\Module\\AMQP' => $vendorDir . '/codeception/module-amqp/src/Codeception/Module/AMQP.php', 'Codeception\\Module\\AbstractAsserts' => $vendorDir . '/codeception/module-asserts/src/Codeception/Module/AbstractAsserts.php', 'Codeception\\Module\\Apc' => $vendorDir . '/codeception/module-apc/src/Codeception/Module/Apc.php', 'Codeception\\Module\\Asserts' => $vendorDir . '/codeception/module-asserts/src/Codeception/Module/Asserts.php', 'Codeception\\Module\\Cli' => $vendorDir . '/codeception/module-cli/src/Codeception/Module/Cli.php', 'Codeception\\Module\\Db' => $vendorDir . '/codeception/module-db/src/Codeception/Module/Db.php', 'Codeception\\Module\\FTP' => $vendorDir . '/codeception/module-ftp/src/Codeception/Module/FTP.php', 'Codeception\\Module\\Filesystem' => $vendorDir . '/codeception/module-filesystem/src/Codeception/Module/Filesystem.php', 'Codeception\\Module\\Memcache' => $vendorDir . '/codeception/module-memcache/src/Codeception/Module/Memcache.php', 'Codeception\\Module\\MongoDb' => $vendorDir . '/codeception/module-mongodb/src/Codeception/Module/MongoDb.php', 'Codeception\\Module\\PhpBrowser' => $vendorDir . '/codeception/module-phpbrowser/src/Codeception/Module/PhpBrowser.php', 'Codeception\\Module\\Queue' => $vendorDir . '/codeception/module-queue/src/Codeception/Module/Queue.php', 'Codeception\\Module\\REST' => $vendorDir . '/codeception/module-rest/src/Codeception/Module/REST.php', 'Codeception\\Module\\Redis' => $vendorDir . '/codeception/module-redis/src/Codeception/Module/Redis.php', 'Codeception\\Module\\SOAP' => $vendorDir . '/codeception/module-soap/src/Codeception/Module/SOAP.php', 'Codeception\\Module\\Sequence' => $vendorDir . '/codeception/module-sequence/src/Codeception/Module/Sequence.php', 'Codeception\\Module\\WebDriver' => $vendorDir . '/codeception/module-webdriver/src/Codeception/Module/WebDriver.php', 'Codeception\\Step\\AsJson' => $vendorDir . '/codeception/module-rest/src/Codeception/Step/AsJson.php', 'Codeception\\Util\\HttpCode' => $vendorDir . '/codeception/lib-innerbrowser/src/Codeception/Util/HttpCode.php', 'Codeception\\Util\\JsonArray' => $vendorDir . '/codeception/module-rest/src/Codeception/Util/JsonArray.php', 'Codeception\\Util\\JsonType' => $vendorDir . '/codeception/module-rest/src/Codeception/Util/JsonType.php', 'Codeception\\Util\\Shared\\Asserts' => $vendorDir . '/codeception/lib-asserts/src/Codeception/Util/Shared/Asserts.php', 'Codeception\\Util\\Shared\\InheritedAsserts' => $vendorDir . '/codeception/lib-asserts/src/Codeception/Util/Shared/InheritedAsserts.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'PHPUnit\\Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php', 'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php', 'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php', 'PHPUnit\\Framework\\CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php', 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', 'PHPUnit\\Framework\\Constraint\\ArraySubset' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', 'PHPUnit\\Framework\\Constraint\\Attribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', 'PHPUnit\\Framework\\Constraint\\Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', 'PHPUnit\\Framework\\Constraint\\Composite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', 'PHPUnit\\Framework\\Constraint\\Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', 'PHPUnit\\Framework\\Constraint\\Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Count.php', 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', 'PHPUnit\\Framework\\Constraint\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.php', 'PHPUnit\\Framework\\Constraint\\FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', 'PHPUnit\\Framework\\Constraint\\GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', 'PHPUnit\\Framework\\Constraint\\IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', 'PHPUnit\\Framework\\Constraint\\IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', 'PHPUnit\\Framework\\Constraint\\IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', 'PHPUnit\\Framework\\Constraint\\IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', 'PHPUnit\\Framework\\Constraint\\IsFinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', 'PHPUnit\\Framework\\Constraint\\IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', 'PHPUnit\\Framework\\Constraint\\IsInfinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', 'PHPUnit\\Framework\\Constraint\\IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', 'PHPUnit\\Framework\\Constraint\\IsNan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', 'PHPUnit\\Framework\\Constraint\\IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', 'PHPUnit\\Framework\\Constraint\\IsReadable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', 'PHPUnit\\Framework\\Constraint\\IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', 'PHPUnit\\Framework\\Constraint\\IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', 'PHPUnit\\Framework\\Constraint\\IsWritable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', 'PHPUnit\\Framework\\Constraint\\JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', 'PHPUnit\\Framework\\Constraint\\LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php', 'PHPUnit\\Framework\\Constraint\\LogicalNot' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalNot.php', 'PHPUnit\\Framework\\Constraint\\LogicalOr' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalOr.php', 'PHPUnit\\Framework\\Constraint\\LogicalXor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php', 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php', 'PHPUnit\\Framework\\Constraint\\SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', 'PHPUnit\\Framework\\Constraint\\StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.php', 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', 'PHPUnit\\Framework\\Constraint\\TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsEqual.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsIdentical.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php', 'PHPUnit\\Framework\\DataProviderTestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', 'PHPUnit\\Framework\\Error\\Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', 'PHPUnit\\Framework\\Error\\Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Error.php', 'PHPUnit\\Framework\\Error\\Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', 'PHPUnit\\Framework\\Error\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', 'PHPUnit\\Framework\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/Exception.php', 'PHPUnit\\Framework\\ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'PHPUnit\\Framework\\ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php', 'PHPUnit\\Framework\\IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', 'PHPUnit\\Framework\\IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', 'PHPUnit\\Framework\\IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php', 'PHPUnit\\Framework\\InvalidArgumentException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php', 'PHPUnit\\Framework\\InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php', 'PHPUnit\\Framework\\InvalidDataProviderException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php', 'PHPUnit\\Framework\\InvalidParameterGroupException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php', 'PHPUnit\\Framework\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php', 'PHPUnit\\Framework\\MockObject\\Api' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Api/Api.php', 'PHPUnit\\Framework\\MockObject\\BadMethodCallException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Identity' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php', 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationMocker' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php', 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationStubber' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Match_' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/Match_.php', 'PHPUnit\\Framework\\MockObject\\Builder\\MethodNameMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php', 'PHPUnit\\Framework\\MockObject\\Builder\\ParametersMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php', 'PHPUnit\\Framework\\MockObject\\ConfigurableMethod' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php', 'PHPUnit\\Framework\\MockObject\\ConfigurableMethodsAlreadyInitializedException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php', 'PHPUnit\\Framework\\MockObject\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php', 'PHPUnit\\Framework\\MockObject\\Generator' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Generator.php', 'PHPUnit\\Framework\\MockObject\\IncompatibleReturnValueException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php', 'PHPUnit\\Framework\\MockObject\\Invocation' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Invocation.php', 'PHPUnit\\Framework\\MockObject\\InvocationHandler' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php', 'PHPUnit\\Framework\\MockObject\\Matcher' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Matcher.php', 'PHPUnit\\Framework\\MockObject\\Method' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Api/Method.php', 'PHPUnit\\Framework\\MockObject\\MethodNameConstraint' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php', 'PHPUnit\\Framework\\MockObject\\MockBuilder' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php', 'PHPUnit\\Framework\\MockObject\\MockClass' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockClass.php', 'PHPUnit\\Framework\\MockObject\\MockMethod' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockMethod.php', 'PHPUnit\\Framework\\MockObject\\MockMethodSet' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php', 'PHPUnit\\Framework\\MockObject\\MockObject' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockObject.php', 'PHPUnit\\Framework\\MockObject\\MockTrait' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockTrait.php', 'PHPUnit\\Framework\\MockObject\\MockType' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockType.php', 'PHPUnit\\Framework\\MockObject\\Rule\\AnyInvokedCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\AnyParameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvocationOrder' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtIndex' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\MethodName' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php', 'PHPUnit\\Framework\\MockObject\\Rule\\Parameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\ParametersRule' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php', 'PHPUnit\\Framework\\MockObject\\RuntimeException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php', 'PHPUnit\\Framework\\MockObject\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php', 'PHPUnit\\Framework\\MockObject\\Stub\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnArgument' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnCallback' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnReference' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnSelf' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnStub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnValueMap' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php', 'PHPUnit\\Framework\\MockObject\\Stub\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php', 'PHPUnit\\Framework\\MockObject\\Verifiable' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Verifiable.php', 'PHPUnit\\Framework\\NoChildTestSuiteException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php', 'PHPUnit\\Framework\\OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/OutputError.php', 'PHPUnit\\Framework\\PHPTAssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php', 'PHPUnit\\Framework\\RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php', 'PHPUnit\\Framework\\SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php', 'PHPUnit\\Framework\\SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php', 'PHPUnit\\Framework\\SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', 'PHPUnit\\Framework\\SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php', 'PHPUnit\\Framework\\SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php', 'PHPUnit\\Framework\\SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SyntheticError.php', 'PHPUnit\\Framework\\SyntheticSkippedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php', 'PHPUnit\\Framework\\Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php', 'PHPUnit\\Framework\\TestBuilder' => $vendorDir . '/phpunit/phpunit/src/Framework/TestBuilder.php', 'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php', 'PHPUnit\\Framework\\TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php', 'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php', 'PHPUnit\\Framework\\TestListenerDefaultImplementation' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php', 'PHPUnit\\Framework\\TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php', 'PHPUnit\\Framework\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php', 'PHPUnit\\Framework\\TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php', 'PHPUnit\\Framework\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/Warning.php', 'PHPUnit\\Framework\\WarningTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/WarningTestCase.php', 'PHPUnit\\Runner\\AfterIncompleteTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php', 'PHPUnit\\Runner\\AfterLastTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php', 'PHPUnit\\Runner\\AfterRiskyTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php', 'PHPUnit\\Runner\\AfterSkippedTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php', 'PHPUnit\\Runner\\AfterSuccessfulTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php', 'PHPUnit\\Runner\\AfterTestErrorHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php', 'PHPUnit\\Runner\\AfterTestFailureHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php', 'PHPUnit\\Runner\\AfterTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php', 'PHPUnit\\Runner\\AfterTestWarningHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php', 'PHPUnit\\Runner\\BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', 'PHPUnit\\Runner\\BeforeFirstTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php', 'PHPUnit\\Runner\\BeforeTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php', 'PHPUnit\\Runner\\DefaultTestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/DefaultTestResultCache.php', 'PHPUnit\\Runner\\Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php', 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php', 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', 'PHPUnit\\Runner\\Hook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/Hook.php', 'PHPUnit\\Runner\\NullTestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/NullTestResultCache.php', 'PHPUnit\\Runner\\PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Runner/PhptTestCase.php', 'PHPUnit\\Runner\\ResultCacheExtension' => $vendorDir . '/phpunit/phpunit/src/Runner/ResultCacheExtension.php', 'PHPUnit\\Runner\\StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', 'PHPUnit\\Runner\\TestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/TestHook.php', 'PHPUnit\\Runner\\TestListenerAdapter' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php', 'PHPUnit\\Runner\\TestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/TestResultCache.php', 'PHPUnit\\Runner\\TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', 'PHPUnit\\Runner\\TestSuiteSorter' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteSorter.php', 'PHPUnit\\Runner\\Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php', 'PHPUnit\\TextUI\\Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php', 'PHPUnit\\TextUI\\Exception' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception.php', 'PHPUnit\\TextUI\\Help' => $vendorDir . '/phpunit/phpunit/src/TextUI/Help.php', 'PHPUnit\\TextUI\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', 'PHPUnit\\TextUI\\TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php', 'PHPUnit\\Util\\Annotation\\DocBlock' => $vendorDir . '/phpunit/phpunit/src/Util/Annotation/DocBlock.php', 'PHPUnit\\Util\\Annotation\\Registry' => $vendorDir . '/phpunit/phpunit/src/Util/Annotation/Registry.php', 'PHPUnit\\Util\\Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php', 'PHPUnit\\Util\\Color' => $vendorDir . '/phpunit/phpunit/src/Util/Color.php', 'PHPUnit\\Util\\Configuration' => $vendorDir . '/phpunit/phpunit/src/Util/Configuration.php', 'PHPUnit\\Util\\ConfigurationGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', 'PHPUnit\\Util\\ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php', 'PHPUnit\\Util\\Exception' => $vendorDir . '/phpunit/phpunit/src/Util/Exception.php', 'PHPUnit\\Util\\FileLoader' => $vendorDir . '/phpunit/phpunit/src/Util/FileLoader.php', 'PHPUnit\\Util\\Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php', 'PHPUnit\\Util\\Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php', 'PHPUnit\\Util\\Getopt' => $vendorDir . '/phpunit/phpunit/src/Util/Getopt.php', 'PHPUnit\\Util\\GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php', 'PHPUnit\\Util\\InvalidDataSetException' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidDataSetException.php', 'PHPUnit\\Util\\Json' => $vendorDir . '/phpunit/phpunit/src/Util/Json.php', 'PHPUnit\\Util\\Log\\JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php', 'PHPUnit\\Util\\Log\\TeamCity' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TeamCity.php', 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php', 'PHPUnit\\Util\\Reflection' => $vendorDir . '/phpunit/phpunit/src/Util/Reflection.php', 'PHPUnit\\Util\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php', 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php', 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', 'PHPUnit\\Util\\TestDox\\NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', 'PHPUnit\\Util\\TestDox\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', 'PHPUnit\\Util\\TestDox\\TestDoxPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php', 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', 'PHPUnit\\Util\\TextTestListRenderer' => $vendorDir . '/phpunit/phpunit/src/Util/TextTestListRenderer.php', 'PHPUnit\\Util\\Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php', 'PHPUnit\\Util\\VersionComparisonOperator' => $vendorDir . '/phpunit/phpunit/src/Util/VersionComparisonOperator.php', 'PHPUnit\\Util\\XdebugFilterScriptGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php', 'PHPUnit\\Util\\Xml' => $vendorDir . '/phpunit/phpunit/src/Util/Xml.php', 'PHPUnit\\Util\\XmlTestListRenderer' => $vendorDir . '/phpunit/phpunit/src/Util/XmlTestListRenderer.php', 'PHP_Token' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScope' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScopeAndVisibility' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ABSTRACT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AMPERSAND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AND_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BACKTICK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BAD_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOL_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BREAK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CALLABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CARET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CASE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CATCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_NAME_CONSTANT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLONE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMA' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONCAT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONSTANT_ENCAPSED_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONTINUE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CURLY_OPEN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEFAULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOC_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_QUOTES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELLIPSIS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSEIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EMPTY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENCAPSED_AND_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDDECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDSWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDWHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_END_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EVAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXCLAMATION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXTENDS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINALLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNC_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GLOBAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GOTO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_HALT_COMPILER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IMPLEMENTS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INLINE_HTML' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTANCEOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTEADOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INTERFACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ISSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_GREATER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_SMALLER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Includes' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LINE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LIST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_XOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_METHOD_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MOD_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MUL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NAMESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NEW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_SEPARATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NUM_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG_WITH_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PERCENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PIPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRINT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRIVATE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PROTECTED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PUBLIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_QUESTION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_RETURN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SEMICOLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SPACESHIP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_START_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STATIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_VARNAME' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Stream' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream.php', 'PHP_Token_Stream_CachingFactory' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', 'PHP_Token_THROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TILDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Util' => $vendorDir . '/phpunit/php-token-stream/src/Token/Util.php', 'PHP_Token_VAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VARIABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XOR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD_FROM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PharIo\\Manifest\\Application' => $vendorDir . '/phar-io/manifest/src/values/Application.php', 'PharIo\\Manifest\\ApplicationName' => $vendorDir . '/phar-io/manifest/src/values/ApplicationName.php', 'PharIo\\Manifest\\Author' => $vendorDir . '/phar-io/manifest/src/values/Author.php', 'PharIo\\Manifest\\AuthorCollection' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollection.php', 'PharIo\\Manifest\\AuthorCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', 'PharIo\\Manifest\\AuthorElement' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElement.php', 'PharIo\\Manifest\\AuthorElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElementCollection.php', 'PharIo\\Manifest\\BundledComponent' => $vendorDir . '/phar-io/manifest/src/values/BundledComponent.php', 'PharIo\\Manifest\\BundledComponentCollection' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollection.php', 'PharIo\\Manifest\\BundledComponentCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', 'PharIo\\Manifest\\BundlesElement' => $vendorDir . '/phar-io/manifest/src/xml/BundlesElement.php', 'PharIo\\Manifest\\ComponentElement' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElement.php', 'PharIo\\Manifest\\ComponentElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElementCollection.php', 'PharIo\\Manifest\\ContainsElement' => $vendorDir . '/phar-io/manifest/src/xml/ContainsElement.php', 'PharIo\\Manifest\\CopyrightElement' => $vendorDir . '/phar-io/manifest/src/xml/CopyrightElement.php', 'PharIo\\Manifest\\CopyrightInformation' => $vendorDir . '/phar-io/manifest/src/values/CopyrightInformation.php', 'PharIo\\Manifest\\ElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ElementCollection.php', 'PharIo\\Manifest\\ElementCollectionException' => $vendorDir . '/phar-io/manifest/src/exceptions/ElementCollectionException.php', 'PharIo\\Manifest\\Email' => $vendorDir . '/phar-io/manifest/src/values/Email.php', 'PharIo\\Manifest\\Exception' => $vendorDir . '/phar-io/manifest/src/exceptions/Exception.php', 'PharIo\\Manifest\\ExtElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtElement.php', 'PharIo\\Manifest\\ExtElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ExtElementCollection.php', 'PharIo\\Manifest\\Extension' => $vendorDir . '/phar-io/manifest/src/values/Extension.php', 'PharIo\\Manifest\\ExtensionElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtensionElement.php', 'PharIo\\Manifest\\InvalidApplicationNameException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', 'PharIo\\Manifest\\InvalidEmailException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', 'PharIo\\Manifest\\InvalidUrlException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', 'PharIo\\Manifest\\Library' => $vendorDir . '/phar-io/manifest/src/values/Library.php', 'PharIo\\Manifest\\License' => $vendorDir . '/phar-io/manifest/src/values/License.php', 'PharIo\\Manifest\\LicenseElement' => $vendorDir . '/phar-io/manifest/src/xml/LicenseElement.php', 'PharIo\\Manifest\\Manifest' => $vendorDir . '/phar-io/manifest/src/values/Manifest.php', 'PharIo\\Manifest\\ManifestDocument' => $vendorDir . '/phar-io/manifest/src/xml/ManifestDocument.php', 'PharIo\\Manifest\\ManifestDocumentException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', 'PharIo\\Manifest\\ManifestDocumentLoadingException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', 'PharIo\\Manifest\\ManifestDocumentMapper' => $vendorDir . '/phar-io/manifest/src/ManifestDocumentMapper.php', 'PharIo\\Manifest\\ManifestDocumentMapperException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', 'PharIo\\Manifest\\ManifestElement' => $vendorDir . '/phar-io/manifest/src/xml/ManifestElement.php', 'PharIo\\Manifest\\ManifestElementException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestElementException.php', 'PharIo\\Manifest\\ManifestLoader' => $vendorDir . '/phar-io/manifest/src/ManifestLoader.php', 'PharIo\\Manifest\\ManifestLoaderException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', 'PharIo\\Manifest\\ManifestSerializer' => $vendorDir . '/phar-io/manifest/src/ManifestSerializer.php', 'PharIo\\Manifest\\PhpElement' => $vendorDir . '/phar-io/manifest/src/xml/PhpElement.php', 'PharIo\\Manifest\\PhpExtensionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', 'PharIo\\Manifest\\PhpVersionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpVersionRequirement.php', 'PharIo\\Manifest\\Requirement' => $vendorDir . '/phar-io/manifest/src/values/Requirement.php', 'PharIo\\Manifest\\RequirementCollection' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollection.php', 'PharIo\\Manifest\\RequirementCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', 'PharIo\\Manifest\\RequiresElement' => $vendorDir . '/phar-io/manifest/src/xml/RequiresElement.php', 'PharIo\\Manifest\\Type' => $vendorDir . '/phar-io/manifest/src/values/Type.php', 'PharIo\\Manifest\\Url' => $vendorDir . '/phar-io/manifest/src/values/Url.php', 'PharIo\\Version\\AbstractVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/AbstractVersionConstraint.php', 'PharIo\\Version\\AndVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/constraints/AndVersionConstraintGroup.php', 'PharIo\\Version\\AnyVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/AnyVersionConstraint.php', 'PharIo\\Version\\BuildMetaData' => $vendorDir . '/phar-io/version/src/BuildMetaData.php', 'PharIo\\Version\\ExactVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/ExactVersionConstraint.php', 'PharIo\\Version\\Exception' => $vendorDir . '/phar-io/version/src/exceptions/Exception.php', 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', 'PharIo\\Version\\InvalidPreReleaseSuffixException' => $vendorDir . '/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', 'PharIo\\Version\\InvalidVersionException' => $vendorDir . '/phar-io/version/src/exceptions/InvalidVersionException.php', 'PharIo\\Version\\NoBuildMetaDataException' => $vendorDir . '/phar-io/version/src/exceptions/NoBuildMetaDataException.php', 'PharIo\\Version\\NoPreReleaseSuffixException' => $vendorDir . '/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', 'PharIo\\Version\\OrVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/constraints/OrVersionConstraintGroup.php', 'PharIo\\Version\\PreReleaseSuffix' => $vendorDir . '/phar-io/version/src/PreReleaseSuffix.php', 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', 'PharIo\\Version\\SpecificMajorVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', 'PharIo\\Version\\UnsupportedVersionConstraintException' => $vendorDir . '/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', 'PharIo\\Version\\Version' => $vendorDir . '/phar-io/version/src/Version.php', 'PharIo\\Version\\VersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/VersionConstraint.php', 'PharIo\\Version\\VersionConstraintParser' => $vendorDir . '/phar-io/version/src/VersionConstraintParser.php', 'PharIo\\Version\\VersionConstraintValue' => $vendorDir . '/phar-io/version/src/VersionConstraintValue.php', 'PharIo\\Version\\VersionNumber' => $vendorDir . '/phar-io/version/src/VersionNumber.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php', 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PCOV' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PCOV.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', 'SebastianBergmann\\CodeCoverage\\Exception' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Exception.php', 'SebastianBergmann\\CodeCoverage\\Filter' => $vendorDir . '/phpunit/php-code-coverage/src/Filter.php', 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => $vendorDir . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Builder.php', 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Directory.php', 'SebastianBergmann\\CodeCoverage\\Node\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Node/File.php', 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Iterator.php', 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Clover.php', 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Crap4j.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => $vendorDir . '/phpunit/php-code-coverage/src/Report/PHP.php', 'SebastianBergmann\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Text.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', 'SebastianBergmann\\CodeCoverage\\RuntimeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', 'SebastianBergmann\\CodeCoverage\\Util' => $vendorDir . '/phpunit/php-code-coverage/src/Util.php', 'SebastianBergmann\\CodeCoverage\\Version' => $vendorDir . '/phpunit/php-code-coverage/src/Version.php', 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => $vendorDir . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', 'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php', 'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php', 'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php', 'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php', 'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php', 'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php', 'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php', 'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php', 'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php', 'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php', 'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php', 'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php', 'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php', 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php', 'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php', 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', 'SebastianBergmann\\Diff\\ConfigurationException' => $vendorDir . '/sebastian/diff/src/Exception/ConfigurationException.php', 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', 'SebastianBergmann\\Diff\\Exception' => $vendorDir . '/sebastian/diff/src/Exception/Exception.php', 'SebastianBergmann\\Diff\\InvalidArgumentException' => $vendorDir . '/sebastian/diff/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php', 'SebastianBergmann\\Environment\\OperatingSystem' => $vendorDir . '/sebastian/environment/src/OperatingSystem.php', 'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php', 'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php', 'SebastianBergmann\\FileIterator\\Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php', 'SebastianBergmann\\FileIterator\\Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', 'SebastianBergmann\\FileIterator\\Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php', 'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php', 'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php', 'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/exceptions/Exception.php', 'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php', 'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/exceptions/RuntimeException.php', 'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php', 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => $vendorDir . '/sebastian/object-enumerator/src/Enumerator.php', 'SebastianBergmann\\ObjectEnumerator\\Exception' => $vendorDir . '/sebastian/object-enumerator/src/Exception.php', 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => $vendorDir . '/sebastian/object-enumerator/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\Exception' => $vendorDir . '/sebastian/object-reflector/src/Exception.php', 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => $vendorDir . '/sebastian/object-reflector/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => $vendorDir . '/sebastian/object-reflector/src/ObjectReflector.php', 'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php', 'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php', 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php', 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => $vendorDir . '/sebastian/resource-operations/src/ResourceOperations.php', 'SebastianBergmann\\Timer\\Exception' => $vendorDir . '/phpunit/php-timer/src/Exception.php', 'SebastianBergmann\\Timer\\RuntimeException' => $vendorDir . '/phpunit/php-timer/src/RuntimeException.php', 'SebastianBergmann\\Timer\\Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php', 'SebastianBergmann\\Type\\CallableType' => $vendorDir . '/sebastian/type/src/CallableType.php', 'SebastianBergmann\\Type\\Exception' => $vendorDir . '/sebastian/type/src/exception/Exception.php', 'SebastianBergmann\\Type\\GenericObjectType' => $vendorDir . '/sebastian/type/src/GenericObjectType.php', 'SebastianBergmann\\Type\\IterableType' => $vendorDir . '/sebastian/type/src/IterableType.php', 'SebastianBergmann\\Type\\NullType' => $vendorDir . '/sebastian/type/src/NullType.php', 'SebastianBergmann\\Type\\ObjectType' => $vendorDir . '/sebastian/type/src/ObjectType.php', 'SebastianBergmann\\Type\\RuntimeException' => $vendorDir . '/sebastian/type/src/exception/RuntimeException.php', 'SebastianBergmann\\Type\\SimpleType' => $vendorDir . '/sebastian/type/src/SimpleType.php', 'SebastianBergmann\\Type\\Type' => $vendorDir . '/sebastian/type/src/Type.php', 'SebastianBergmann\\Type\\TypeName' => $vendorDir . '/sebastian/type/src/TypeName.php', 'SebastianBergmann\\Type\\UnknownType' => $vendorDir . '/sebastian/type/src/UnknownType.php', 'SebastianBergmann\\Type\\VoidType' => $vendorDir . '/sebastian/type/src/VoidType.php', 'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php', 'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php', 'TheSeer\\Tokenizer\\NamespaceUri' => $vendorDir . '/theseer/tokenizer/src/NamespaceUri.php', 'TheSeer\\Tokenizer\\NamespaceUriException' => $vendorDir . '/theseer/tokenizer/src/NamespaceUriException.php', 'TheSeer\\Tokenizer\\Token' => $vendorDir . '/theseer/tokenizer/src/Token.php', 'TheSeer\\Tokenizer\\TokenCollection' => $vendorDir . '/theseer/tokenizer/src/TokenCollection.php', 'TheSeer\\Tokenizer\\TokenCollectionException' => $vendorDir . '/theseer/tokenizer/src/TokenCollectionException.php', 'TheSeer\\Tokenizer\\Tokenizer' => $vendorDir . '/theseer/tokenizer/src/Tokenizer.php', 'TheSeer\\Tokenizer\\XMLSerializer' => $vendorDir . '/theseer/tokenizer/src/XMLSerializer.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); vendorDir = $vendorDir; } 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; } public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } 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 ); } } public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { $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) { $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } 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; } } public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } public function getUseIncludePath() { return $this->useIncludePath; } public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } public function getApcuPrefix() { return $this->apcuPrefix; } 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; } } public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } return null; } public function findFile($class) { 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'); if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { $this->missingClasses[$class] = true; } return $file; } public static function getRegisteredLoaders() { return self::$registeredLoaders; } private function findFileWithExtension($class, $ext) { $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; } } } } } foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } if (false !== $pos = strrpos($class, '\\')) { $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { $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; } } } } } foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } function includeFile($file) { include $file; } __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', 'e88992873b7765f9b5710cab95ba5dd7' => __DIR__ . '/..' . '/hoa/consistency/Prelude.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', 'e3b2795a8a512b6083af088fb53afe6c' => __DIR__ . '/..' . '/codeception/codeception/functions.php', '3e76f7f02b41af8cea96018933f6b7e3' => __DIR__ . '/..' . '/hoa/protocol/Wrapper.php', 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', 'd9d39f82a605ebe5918f683dd402334c' => __DIR__ . '/..' . '/padraic/humbug_get_contents/src/function.php', '3a50d90d85c7fe889a94ae1114b921ce' => __DIR__ . '/..' . '/padraic/humbug_get_contents/src/functions.php', '2a3c2110e8e0295330dc3d11a4cbc4cb' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php', ); public static $prefixLengthsPsr4 = array ( 'p' => array ( 'phpseclib\\' => 10, 'phpDocumentor\\Reflection\\' => 25, ), 'W' => array ( 'Webmozart\\Assert\\' => 17, ), 'S' => array ( 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Contracts\\Service\\' => 26, 'Symfony\\Contracts\\EventDispatcher\\' => 34, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\DomCrawler\\' => 29, 'Symfony\\Component\\CssSelector\\' => 30, 'Symfony\\Component\\Console\\' => 26, 'Symfony\\Component\\BrowserKit\\' => 29, ), 'P' => array ( 'Psr\\Http\\Message\\' => 17, 'Psr\\Container\\' => 14, 'Prophecy\\' => 9, 'Predis\\' => 7, 'PhpAmqpLib\\' => 11, ), 'J' => array ( 'JsonSchema\\' => 11, ), 'H' => array ( 'Humbug\\SelfUpdate\\' => 18, 'Humbug\\' => 7, 'Hoa\\Ustring\\' => 12, 'Hoa\\Stream\\' => 11, 'Hoa\\Protocol\\' => 13, 'Hoa\\Iterator\\' => 13, 'Hoa\\File\\' => 9, 'Hoa\\Exception\\' => 14, 'Hoa\\Event\\' => 10, 'Hoa\\Console\\' => 12, 'Hoa\\Consistency\\' => 16, ), 'G' => array ( 'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\' => 11, ), 'F' => array ( 'Flow\\JSONPath\\' => 14, 'Facebook\\WebDriver\\' => 19, ), 'D' => array ( 'Doctrine\\Instantiator\\' => 22, 'DeepCopy\\' => 9, ), 'C' => array ( 'Composer\\CaBundle\\' => 18, 'Codeception\\PHPUnit\\' => 20, 'Codeception\\Extension\\' => 22, 'Codeception\\' => 12, ), ); public static $prefixDirsPsr4 = array ( 'phpseclib\\' => array ( 0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib', ), 'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', 2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', ), 'Webmozart\\Assert\\' => array ( 0 => __DIR__ . '/..' . '/webmozart/assert/src', ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), 'Symfony\\Polyfill\\Php73\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', ), 'Symfony\\Polyfill\\Php72\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', ), 'Symfony\\Polyfill\\Intl\\Idn\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'Symfony\\Contracts\\Service\\' => array ( 0 => __DIR__ . '/..' . '/symfony/service-contracts', ), 'Symfony\\Contracts\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', ), 'Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), 'Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), 'Symfony\\Component\\DomCrawler\\' => array ( 0 => __DIR__ . '/..' . '/symfony/dom-crawler', ), 'Symfony\\Component\\CssSelector\\' => array ( 0 => __DIR__ . '/..' . '/symfony/css-selector', ), 'Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), 'Symfony\\Component\\BrowserKit\\' => array ( 0 => __DIR__ . '/..' . '/symfony/browser-kit', ), 'Psr\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-message/src', ), 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), 'Prophecy\\' => array ( 0 => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy', ), 'Predis\\' => array ( 0 => __DIR__ . '/..' . '/predis/predis/src', ), 'PhpAmqpLib\\' => array ( 0 => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib', ), 'JsonSchema\\' => array ( 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', ), 'Humbug\\SelfUpdate\\' => array ( 0 => __DIR__ . '/..' . '/laravel-zero/phar-updater/src', ), 'Humbug\\' => array ( 0 => __DIR__ . '/..' . '/padraic/humbug_get_contents/src', ), 'Hoa\\Ustring\\' => array ( 0 => __DIR__ . '/..' . '/hoa/ustring', ), 'Hoa\\Stream\\' => array ( 0 => __DIR__ . '/..' . '/hoa/stream', ), 'Hoa\\Protocol\\' => array ( 0 => __DIR__ . '/..' . '/hoa/protocol', ), 'Hoa\\Iterator\\' => array ( 0 => __DIR__ . '/..' . '/hoa/iterator', ), 'Hoa\\File\\' => array ( 0 => __DIR__ . '/..' . '/hoa/file', ), 'Hoa\\Exception\\' => array ( 0 => __DIR__ . '/..' . '/hoa/exception', ), 'Hoa\\Event\\' => array ( 0 => __DIR__ . '/..' . '/hoa/event', ), 'Hoa\\Console\\' => array ( 0 => __DIR__ . '/..' . '/hoa/console', ), 'Hoa\\Consistency\\' => array ( 0 => __DIR__ . '/..' . '/hoa/consistency', ), 'GuzzleHttp\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', ), 'GuzzleHttp\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', ), 'GuzzleHttp\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', ), 'Flow\\JSONPath\\' => array ( 0 => __DIR__ . '/..' . '/softcreatr/jsonpath/src', ), 'Facebook\\WebDriver\\' => array ( 0 => __DIR__ . '/..' . '/php-webdriver/webdriver/lib', ), 'Doctrine\\Instantiator\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', ), 'DeepCopy\\' => array ( 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', ), 'Composer\\CaBundle\\' => array ( 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', ), 'Codeception\\PHPUnit\\' => array ( 0 => __DIR__ . '/..' . '/codeception/phpunit-wrapper/src', ), 'Codeception\\Extension\\' => array ( 0 => __DIR__ . '/..' . '/codeception/codeception/ext', ), 'Codeception\\' => array ( 0 => __DIR__ . '/..' . '/codeception/codeception/src/Codeception', 1 => __DIR__ . '/..' . '/codeception/stub/src', ), ); public static $prefixesPsr0 = array ( 'B' => array ( 'Behat\\Gherkin' => array ( 0 => __DIR__ . '/..' . '/behat/gherkin/src', ), ), ); public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Codeception\\Exception\\ConnectionException' => __DIR__ . '/..' . '/codeception/module-webdriver/src/Codeception/Exception/ConnectionException.php', 'Codeception\\Exception\\ExternalUrlException' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Exception/ExternalUrlException.php', 'Codeception\\Lib\\Connector\\Guzzle' => __DIR__ . '/..' . '/codeception/module-phpbrowser/src/Codeception/Lib/Connector/Guzzle.php', 'Codeception\\Lib\\DbPopulator' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/DbPopulator.php', 'Codeception\\Lib\\Driver\\AmazonSQS' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Lib/Driver/AmazonSQS.php', 'Codeception\\Lib\\Driver\\Beanstalk' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Lib/Driver/Beanstalk.php', 'Codeception\\Lib\\Driver\\Db' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/Db.php', 'Codeception\\Lib\\Driver\\Iron' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Lib/Driver/Iron.php', 'Codeception\\Lib\\Driver\\MongoDb' => __DIR__ . '/..' . '/codeception/module-mongodb/src/Codeception/Lib/Driver/MongoDb.php', 'Codeception\\Lib\\Driver\\MySql' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/MySql.php', 'Codeception\\Lib\\Driver\\Oci' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/Oci.php', 'Codeception\\Lib\\Driver\\Pheanstalk4' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Lib/Driver/Pheanstalk4.php', 'Codeception\\Lib\\Driver\\PostgreSql' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/PostgreSql.php', 'Codeception\\Lib\\Driver\\SqlSrv' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/SqlSrv.php', 'Codeception\\Lib\\Driver\\Sqlite' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Driver/Sqlite.php', 'Codeception\\Lib\\Framework' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Lib/Framework.php', 'Codeception\\Lib\\InnerBrowser' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Lib/InnerBrowser.php', 'Codeception\\Lib\\Interfaces\\Db' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Lib/Interfaces/Db.php', 'Codeception\\Lib\\Interfaces\\Queue' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Lib/Interfaces/Queue.php', 'Codeception\\Lib\\Interfaces\\ScreenshotSaver' => __DIR__ . '/..' . '/codeception/module-webdriver/src/Codeception/Lib/Interfaces/ScreenshotSaver.php', 'Codeception\\Lib\\Interfaces\\SessionSnapshot' => __DIR__ . '/..' . '/codeception/module-webdriver/src/Codeception/Lib/Interfaces/SessionSnapshot.php', 'Codeception\\Module\\AMQP' => __DIR__ . '/..' . '/codeception/module-amqp/src/Codeception/Module/AMQP.php', 'Codeception\\Module\\AbstractAsserts' => __DIR__ . '/..' . '/codeception/module-asserts/src/Codeception/Module/AbstractAsserts.php', 'Codeception\\Module\\Apc' => __DIR__ . '/..' . '/codeception/module-apc/src/Codeception/Module/Apc.php', 'Codeception\\Module\\Asserts' => __DIR__ . '/..' . '/codeception/module-asserts/src/Codeception/Module/Asserts.php', 'Codeception\\Module\\Cli' => __DIR__ . '/..' . '/codeception/module-cli/src/Codeception/Module/Cli.php', 'Codeception\\Module\\Db' => __DIR__ . '/..' . '/codeception/module-db/src/Codeception/Module/Db.php', 'Codeception\\Module\\FTP' => __DIR__ . '/..' . '/codeception/module-ftp/src/Codeception/Module/FTP.php', 'Codeception\\Module\\Filesystem' => __DIR__ . '/..' . '/codeception/module-filesystem/src/Codeception/Module/Filesystem.php', 'Codeception\\Module\\Memcache' => __DIR__ . '/..' . '/codeception/module-memcache/src/Codeception/Module/Memcache.php', 'Codeception\\Module\\MongoDb' => __DIR__ . '/..' . '/codeception/module-mongodb/src/Codeception/Module/MongoDb.php', 'Codeception\\Module\\PhpBrowser' => __DIR__ . '/..' . '/codeception/module-phpbrowser/src/Codeception/Module/PhpBrowser.php', 'Codeception\\Module\\Queue' => __DIR__ . '/..' . '/codeception/module-queue/src/Codeception/Module/Queue.php', 'Codeception\\Module\\REST' => __DIR__ . '/..' . '/codeception/module-rest/src/Codeception/Module/REST.php', 'Codeception\\Module\\Redis' => __DIR__ . '/..' . '/codeception/module-redis/src/Codeception/Module/Redis.php', 'Codeception\\Module\\SOAP' => __DIR__ . '/..' . '/codeception/module-soap/src/Codeception/Module/SOAP.php', 'Codeception\\Module\\Sequence' => __DIR__ . '/..' . '/codeception/module-sequence/src/Codeception/Module/Sequence.php', 'Codeception\\Module\\WebDriver' => __DIR__ . '/..' . '/codeception/module-webdriver/src/Codeception/Module/WebDriver.php', 'Codeception\\Step\\AsJson' => __DIR__ . '/..' . '/codeception/module-rest/src/Codeception/Step/AsJson.php', 'Codeception\\Util\\HttpCode' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Util/HttpCode.php', 'Codeception\\Util\\JsonArray' => __DIR__ . '/..' . '/codeception/module-rest/src/Codeception/Util/JsonArray.php', 'Codeception\\Util\\JsonType' => __DIR__ . '/..' . '/codeception/module-rest/src/Codeception/Util/JsonType.php', 'Codeception\\Util\\Shared\\Asserts' => __DIR__ . '/..' . '/codeception/lib-asserts/src/Codeception/Util/Shared/Asserts.php', 'Codeception\\Util\\Shared\\InheritedAsserts' => __DIR__ . '/..' . '/codeception/lib-asserts/src/Codeception/Util/Shared/InheritedAsserts.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', 'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php', 'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php', 'PHPUnit\\Framework\\CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php', 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', 'PHPUnit\\Framework\\Constraint\\ArraySubset' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', 'PHPUnit\\Framework\\Constraint\\Attribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', 'PHPUnit\\Framework\\Constraint\\Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', 'PHPUnit\\Framework\\Constraint\\Composite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', 'PHPUnit\\Framework\\Constraint\\Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', 'PHPUnit\\Framework\\Constraint\\Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Count.php', 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', 'PHPUnit\\Framework\\Constraint\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.php', 'PHPUnit\\Framework\\Constraint\\FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', 'PHPUnit\\Framework\\Constraint\\GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', 'PHPUnit\\Framework\\Constraint\\IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', 'PHPUnit\\Framework\\Constraint\\IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', 'PHPUnit\\Framework\\Constraint\\IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', 'PHPUnit\\Framework\\Constraint\\IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', 'PHPUnit\\Framework\\Constraint\\IsFinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', 'PHPUnit\\Framework\\Constraint\\IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', 'PHPUnit\\Framework\\Constraint\\IsInfinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', 'PHPUnit\\Framework\\Constraint\\IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', 'PHPUnit\\Framework\\Constraint\\IsNan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', 'PHPUnit\\Framework\\Constraint\\IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', 'PHPUnit\\Framework\\Constraint\\IsReadable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', 'PHPUnit\\Framework\\Constraint\\IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', 'PHPUnit\\Framework\\Constraint\\IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', 'PHPUnit\\Framework\\Constraint\\IsWritable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', 'PHPUnit\\Framework\\Constraint\\JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', 'PHPUnit\\Framework\\Constraint\\LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php', 'PHPUnit\\Framework\\Constraint\\LogicalNot' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalNot.php', 'PHPUnit\\Framework\\Constraint\\LogicalOr' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalOr.php', 'PHPUnit\\Framework\\Constraint\\LogicalXor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php', 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php', 'PHPUnit\\Framework\\Constraint\\SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', 'PHPUnit\\Framework\\Constraint\\StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.php', 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', 'PHPUnit\\Framework\\Constraint\\TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsEqual.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsIdentical.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php', 'PHPUnit\\Framework\\DataProviderTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', 'PHPUnit\\Framework\\Error\\Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', 'PHPUnit\\Framework\\Error\\Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Error.php', 'PHPUnit\\Framework\\Error\\Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php', 'PHPUnit\\Framework\\Error\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php', 'PHPUnit\\Framework\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/Exception.php', 'PHPUnit\\Framework\\ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'PHPUnit\\Framework\\ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php', 'PHPUnit\\Framework\\IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php', 'PHPUnit\\Framework\\IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', 'PHPUnit\\Framework\\IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php', 'PHPUnit\\Framework\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php', 'PHPUnit\\Framework\\InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php', 'PHPUnit\\Framework\\InvalidDataProviderException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php', 'PHPUnit\\Framework\\InvalidParameterGroupException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php', 'PHPUnit\\Framework\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php', 'PHPUnit\\Framework\\MockObject\\Api' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Api/Api.php', 'PHPUnit\\Framework\\MockObject\\BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Identity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php', 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php', 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationStubber' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Match_' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/Match_.php', 'PHPUnit\\Framework\\MockObject\\Builder\\MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php', 'PHPUnit\\Framework\\MockObject\\Builder\\ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php', 'PHPUnit\\Framework\\MockObject\\Builder\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php', 'PHPUnit\\Framework\\MockObject\\ConfigurableMethod' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php', 'PHPUnit\\Framework\\MockObject\\ConfigurableMethodsAlreadyInitializedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php', 'PHPUnit\\Framework\\MockObject\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php', 'PHPUnit\\Framework\\MockObject\\Generator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Generator.php', 'PHPUnit\\Framework\\MockObject\\IncompatibleReturnValueException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php', 'PHPUnit\\Framework\\MockObject\\Invocation' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Invocation.php', 'PHPUnit\\Framework\\MockObject\\InvocationHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php', 'PHPUnit\\Framework\\MockObject\\Matcher' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Matcher.php', 'PHPUnit\\Framework\\MockObject\\Method' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Api/Method.php', 'PHPUnit\\Framework\\MockObject\\MethodNameConstraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php', 'PHPUnit\\Framework\\MockObject\\MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php', 'PHPUnit\\Framework\\MockObject\\MockClass' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockClass.php', 'PHPUnit\\Framework\\MockObject\\MockMethod' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockMethod.php', 'PHPUnit\\Framework\\MockObject\\MockMethodSet' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php', 'PHPUnit\\Framework\\MockObject\\MockObject' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockObject.php', 'PHPUnit\\Framework\\MockObject\\MockTrait' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockTrait.php', 'PHPUnit\\Framework\\MockObject\\MockType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockType.php', 'PHPUnit\\Framework\\MockObject\\Rule\\AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvocationOrder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php', 'PHPUnit\\Framework\\MockObject\\Rule\\MethodName' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php', 'PHPUnit\\Framework\\MockObject\\Rule\\Parameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php', 'PHPUnit\\Framework\\MockObject\\Rule\\ParametersRule' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php', 'PHPUnit\\Framework\\MockObject\\RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php', 'PHPUnit\\Framework\\MockObject\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php', 'PHPUnit\\Framework\\MockObject\\Stub\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnReference' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnStub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php', 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php', 'PHPUnit\\Framework\\MockObject\\Stub\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php', 'PHPUnit\\Framework\\MockObject\\Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Verifiable.php', 'PHPUnit\\Framework\\NoChildTestSuiteException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php', 'PHPUnit\\Framework\\OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/OutputError.php', 'PHPUnit\\Framework\\PHPTAssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php', 'PHPUnit\\Framework\\RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php', 'PHPUnit\\Framework\\SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php', 'PHPUnit\\Framework\\SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php', 'PHPUnit\\Framework\\SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', 'PHPUnit\\Framework\\SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php', 'PHPUnit\\Framework\\SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php', 'PHPUnit\\Framework\\SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SyntheticError.php', 'PHPUnit\\Framework\\SyntheticSkippedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php', 'PHPUnit\\Framework\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php', 'PHPUnit\\Framework\\TestBuilder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestBuilder.php', 'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php', 'PHPUnit\\Framework\\TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php', 'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php', 'PHPUnit\\Framework\\TestListenerDefaultImplementation' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php', 'PHPUnit\\Framework\\TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php', 'PHPUnit\\Framework\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php', 'PHPUnit\\Framework\\TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php', 'PHPUnit\\Framework\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/Warning.php', 'PHPUnit\\Framework\\WarningTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/WarningTestCase.php', 'PHPUnit\\Runner\\AfterIncompleteTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php', 'PHPUnit\\Runner\\AfterLastTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php', 'PHPUnit\\Runner\\AfterRiskyTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php', 'PHPUnit\\Runner\\AfterSkippedTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php', 'PHPUnit\\Runner\\AfterSuccessfulTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php', 'PHPUnit\\Runner\\AfterTestErrorHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php', 'PHPUnit\\Runner\\AfterTestFailureHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php', 'PHPUnit\\Runner\\AfterTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php', 'PHPUnit\\Runner\\AfterTestWarningHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php', 'PHPUnit\\Runner\\BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', 'PHPUnit\\Runner\\BeforeFirstTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php', 'PHPUnit\\Runner\\BeforeTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php', 'PHPUnit\\Runner\\DefaultTestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/DefaultTestResultCache.php', 'PHPUnit\\Runner\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php', 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php', 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', 'PHPUnit\\Runner\\Hook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/Hook.php', 'PHPUnit\\Runner\\NullTestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/NullTestResultCache.php', 'PHPUnit\\Runner\\PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/PhptTestCase.php', 'PHPUnit\\Runner\\ResultCacheExtension' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/ResultCacheExtension.php', 'PHPUnit\\Runner\\StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', 'PHPUnit\\Runner\\TestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/TestHook.php', 'PHPUnit\\Runner\\TestListenerAdapter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php', 'PHPUnit\\Runner\\TestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestResultCache.php', 'PHPUnit\\Runner\\TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', 'PHPUnit\\Runner\\TestSuiteSorter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteSorter.php', 'PHPUnit\\Runner\\Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php', 'PHPUnit\\TextUI\\Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php', 'PHPUnit\\TextUI\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception.php', 'PHPUnit\\TextUI\\Help' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Help.php', 'PHPUnit\\TextUI\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', 'PHPUnit\\TextUI\\TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php', 'PHPUnit\\Util\\Annotation\\DocBlock' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Annotation/DocBlock.php', 'PHPUnit\\Util\\Annotation\\Registry' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Annotation/Registry.php', 'PHPUnit\\Util\\Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php', 'PHPUnit\\Util\\Color' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Color.php', 'PHPUnit\\Util\\Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Configuration.php', 'PHPUnit\\Util\\ConfigurationGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', 'PHPUnit\\Util\\ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php', 'PHPUnit\\Util\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Exception.php', 'PHPUnit\\Util\\FileLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/FileLoader.php', 'PHPUnit\\Util\\Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php', 'PHPUnit\\Util\\Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php', 'PHPUnit\\Util\\Getopt' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Getopt.php', 'PHPUnit\\Util\\GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php', 'PHPUnit\\Util\\InvalidDataSetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidDataSetException.php', 'PHPUnit\\Util\\Json' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Json.php', 'PHPUnit\\Util\\Log\\JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php', 'PHPUnit\\Util\\Log\\TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TeamCity.php', 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php', 'PHPUnit\\Util\\Reflection' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Reflection.php', 'PHPUnit\\Util\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php', 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php', 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', 'PHPUnit\\Util\\TestDox\\NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', 'PHPUnit\\Util\\TestDox\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', 'PHPUnit\\Util\\TestDox\\TestDoxPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php', 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', 'PHPUnit\\Util\\TextTestListRenderer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TextTestListRenderer.php', 'PHPUnit\\Util\\Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php', 'PHPUnit\\Util\\VersionComparisonOperator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/VersionComparisonOperator.php', 'PHPUnit\\Util\\XdebugFilterScriptGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php', 'PHPUnit\\Util\\Xml' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml.php', 'PHPUnit\\Util\\XmlTestListRenderer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XmlTestListRenderer.php', 'PHP_Token' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScope' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScopeAndVisibility' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ABSTRACT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AMPERSAND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AND_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BACKTICK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BAD_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOL_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BREAK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CALLABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CARET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CASE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CATCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_NAME_CONSTANT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLONE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMA' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONCAT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONSTANT_ENCAPSED_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONTINUE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CURLY_OPEN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEFAULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOC_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_QUOTES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELLIPSIS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSEIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EMPTY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENCAPSED_AND_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDDECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDSWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDWHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_END_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EVAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXCLAMATION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXTENDS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINALLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNC_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GLOBAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GOTO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_HALT_COMPILER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IMPLEMENTS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INLINE_HTML' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTANCEOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTEADOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INTERFACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ISSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_GREATER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_SMALLER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Includes' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LINE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LIST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_XOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_METHOD_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MOD_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MUL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NAMESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NEW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_SEPARATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NUM_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG_WITH_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PERCENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PIPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRINT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRIVATE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PROTECTED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PUBLIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_QUESTION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_RETURN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SEMICOLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SPACESHIP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_START_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STATIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_VARNAME' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Stream' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream.php', 'PHP_Token_Stream_CachingFactory' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', 'PHP_Token_THROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TILDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Util' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Util.php', 'PHP_Token_VAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VARIABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XOR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD_FROM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PharIo\\Manifest\\Application' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Application.php', 'PharIo\\Manifest\\ApplicationName' => __DIR__ . '/..' . '/phar-io/manifest/src/values/ApplicationName.php', 'PharIo\\Manifest\\Author' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Author.php', 'PharIo\\Manifest\\AuthorCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollection.php', 'PharIo\\Manifest\\AuthorCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', 'PharIo\\Manifest\\AuthorElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElement.php', 'PharIo\\Manifest\\AuthorElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElementCollection.php', 'PharIo\\Manifest\\BundledComponent' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponent.php', 'PharIo\\Manifest\\BundledComponentCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollection.php', 'PharIo\\Manifest\\BundledComponentCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', 'PharIo\\Manifest\\BundlesElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/BundlesElement.php', 'PharIo\\Manifest\\ComponentElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElement.php', 'PharIo\\Manifest\\ComponentElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElementCollection.php', 'PharIo\\Manifest\\ContainsElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ContainsElement.php', 'PharIo\\Manifest\\CopyrightElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/CopyrightElement.php', 'PharIo\\Manifest\\CopyrightInformation' => __DIR__ . '/..' . '/phar-io/manifest/src/values/CopyrightInformation.php', 'PharIo\\Manifest\\ElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ElementCollection.php', 'PharIo\\Manifest\\ElementCollectionException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ElementCollectionException.php', 'PharIo\\Manifest\\Email' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Email.php', 'PharIo\\Manifest\\Exception' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/Exception.php', 'PharIo\\Manifest\\ExtElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElement.php', 'PharIo\\Manifest\\ExtElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElementCollection.php', 'PharIo\\Manifest\\Extension' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Extension.php', 'PharIo\\Manifest\\ExtensionElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtensionElement.php', 'PharIo\\Manifest\\InvalidApplicationNameException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', 'PharIo\\Manifest\\InvalidEmailException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', 'PharIo\\Manifest\\InvalidUrlException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', 'PharIo\\Manifest\\Library' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Library.php', 'PharIo\\Manifest\\License' => __DIR__ . '/..' . '/phar-io/manifest/src/values/License.php', 'PharIo\\Manifest\\LicenseElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/LicenseElement.php', 'PharIo\\Manifest\\Manifest' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Manifest.php', 'PharIo\\Manifest\\ManifestDocument' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestDocument.php', 'PharIo\\Manifest\\ManifestDocumentException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', 'PharIo\\Manifest\\ManifestDocumentLoadingException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', 'PharIo\\Manifest\\ManifestDocumentMapper' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestDocumentMapper.php', 'PharIo\\Manifest\\ManifestDocumentMapperException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', 'PharIo\\Manifest\\ManifestElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestElement.php', 'PharIo\\Manifest\\ManifestElementException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestElementException.php', 'PharIo\\Manifest\\ManifestLoader' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestLoader.php', 'PharIo\\Manifest\\ManifestLoaderException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', 'PharIo\\Manifest\\ManifestSerializer' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestSerializer.php', 'PharIo\\Manifest\\PhpElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/PhpElement.php', 'PharIo\\Manifest\\PhpExtensionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', 'PharIo\\Manifest\\PhpVersionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpVersionRequirement.php', 'PharIo\\Manifest\\Requirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Requirement.php', 'PharIo\\Manifest\\RequirementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollection.php', 'PharIo\\Manifest\\RequirementCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', 'PharIo\\Manifest\\RequiresElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/RequiresElement.php', 'PharIo\\Manifest\\Type' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Type.php', 'PharIo\\Manifest\\Url' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Url.php', 'PharIo\\Version\\AbstractVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AbstractVersionConstraint.php', 'PharIo\\Version\\AndVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AndVersionConstraintGroup.php', 'PharIo\\Version\\AnyVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AnyVersionConstraint.php', 'PharIo\\Version\\BuildMetaData' => __DIR__ . '/..' . '/phar-io/version/src/BuildMetaData.php', 'PharIo\\Version\\ExactVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/ExactVersionConstraint.php', 'PharIo\\Version\\Exception' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/Exception.php', 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', 'PharIo\\Version\\InvalidPreReleaseSuffixException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', 'PharIo\\Version\\InvalidVersionException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/InvalidVersionException.php', 'PharIo\\Version\\NoBuildMetaDataException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/NoBuildMetaDataException.php', 'PharIo\\Version\\NoPreReleaseSuffixException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', 'PharIo\\Version\\OrVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/constraints/OrVersionConstraintGroup.php', 'PharIo\\Version\\PreReleaseSuffix' => __DIR__ . '/..' . '/phar-io/version/src/PreReleaseSuffix.php', 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', 'PharIo\\Version\\SpecificMajorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', 'PharIo\\Version\\UnsupportedVersionConstraintException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', 'PharIo\\Version\\Version' => __DIR__ . '/..' . '/phar-io/version/src/Version.php', 'PharIo\\Version\\VersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/VersionConstraint.php', 'PharIo\\Version\\VersionConstraintParser' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintParser.php', 'PharIo\\Version\\VersionConstraintValue' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintValue.php', 'PharIo\\Version\\VersionNumber' => __DIR__ . '/..' . '/phar-io/version/src/VersionNumber.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php', 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PCOV' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PCOV.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', 'SebastianBergmann\\CodeCoverage\\Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Exception.php', 'SebastianBergmann\\CodeCoverage\\Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Filter.php', 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Builder.php', 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Directory.php', 'SebastianBergmann\\CodeCoverage\\Node\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/File.php', 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Iterator.php', 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Clover.php', 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Crap4j.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/PHP.php', 'SebastianBergmann\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Text.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', 'SebastianBergmann\\CodeCoverage\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', 'SebastianBergmann\\CodeCoverage\\Util' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util.php', 'SebastianBergmann\\CodeCoverage\\Version' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Version.php', 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => __DIR__ . '/..' . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', 'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php', 'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php', 'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php', 'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php', 'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php', 'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php', 'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php', 'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php', 'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php', 'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php', 'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php', 'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php', 'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php', 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php', 'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php', 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', 'SebastianBergmann\\Diff\\ConfigurationException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/ConfigurationException.php', 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', 'SebastianBergmann\\Diff\\Exception' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/Exception.php', 'SebastianBergmann\\Diff\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php', 'SebastianBergmann\\Environment\\OperatingSystem' => __DIR__ . '/..' . '/sebastian/environment/src/OperatingSystem.php', 'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php', 'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php', 'SebastianBergmann\\FileIterator\\Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', 'SebastianBergmann\\FileIterator\\Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', 'SebastianBergmann\\FileIterator\\Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', 'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php', 'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php', 'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/Exception.php', 'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php', 'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/RuntimeException.php', 'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php', 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Enumerator.php', 'SebastianBergmann\\ObjectEnumerator\\Exception' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Exception.php', 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\Exception' => __DIR__ . '/..' . '/sebastian/object-reflector/src/Exception.php', 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-reflector/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => __DIR__ . '/..' . '/sebastian/object-reflector/src/ObjectReflector.php', 'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php', 'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php', 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php', 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => __DIR__ . '/..' . '/sebastian/resource-operations/src/ResourceOperations.php', 'SebastianBergmann\\Timer\\Exception' => __DIR__ . '/..' . '/phpunit/php-timer/src/Exception.php', 'SebastianBergmann\\Timer\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-timer/src/RuntimeException.php', 'SebastianBergmann\\Timer\\Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php', 'SebastianBergmann\\Type\\CallableType' => __DIR__ . '/..' . '/sebastian/type/src/CallableType.php', 'SebastianBergmann\\Type\\Exception' => __DIR__ . '/..' . '/sebastian/type/src/exception/Exception.php', 'SebastianBergmann\\Type\\GenericObjectType' => __DIR__ . '/..' . '/sebastian/type/src/GenericObjectType.php', 'SebastianBergmann\\Type\\IterableType' => __DIR__ . '/..' . '/sebastian/type/src/IterableType.php', 'SebastianBergmann\\Type\\NullType' => __DIR__ . '/..' . '/sebastian/type/src/NullType.php', 'SebastianBergmann\\Type\\ObjectType' => __DIR__ . '/..' . '/sebastian/type/src/ObjectType.php', 'SebastianBergmann\\Type\\RuntimeException' => __DIR__ . '/..' . '/sebastian/type/src/exception/RuntimeException.php', 'SebastianBergmann\\Type\\SimpleType' => __DIR__ . '/..' . '/sebastian/type/src/SimpleType.php', 'SebastianBergmann\\Type\\Type' => __DIR__ . '/..' . '/sebastian/type/src/Type.php', 'SebastianBergmann\\Type\\TypeName' => __DIR__ . '/..' . '/sebastian/type/src/TypeName.php', 'SebastianBergmann\\Type\\UnknownType' => __DIR__ . '/..' . '/sebastian/type/src/UnknownType.php', 'SebastianBergmann\\Type\\VoidType' => __DIR__ . '/..' . '/sebastian/type/src/VoidType.php', 'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php', 'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php', 'TheSeer\\Tokenizer\\NamespaceUri' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUri.php', 'TheSeer\\Tokenizer\\NamespaceUriException' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUriException.php', 'TheSeer\\Tokenizer\\Token' => __DIR__ . '/..' . '/theseer/tokenizer/src/Token.php', 'TheSeer\\Tokenizer\\TokenCollection' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollection.php', 'TheSeer\\Tokenizer\\TokenCollectionException' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollectionException.php', 'TheSeer\\Tokenizer\\Tokenizer' => __DIR__ . '/..' . '/theseer/tokenizer/src/Tokenizer.php', 'TheSeer\\Tokenizer\\XMLSerializer' => __DIR__ . '/..' . '/theseer/tokenizer/src/XMLSerializer.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 = ComposerStaticInit62a680ddf9b2a2f37d125b4c7fbcd759::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit62a680ddf9b2a2f37d125b4c7fbcd759::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit62a680ddf9b2a2f37d125b4c7fbcd759::$prefixesPsr0; $loader->classMap = ComposerStaticInit62a680ddf9b2a2f37d125b4c7fbcd759::$classMap; }, null, ClassLoader::class); } } = 70200)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } if (!ini_get('display_errors')) { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); } elseif (!headers_sent()) { echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; } } trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); } array($vendorDir . '/phpseclib/phpseclib/phpseclib'), 'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'), 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'), 'Predis\\' => array($vendorDir . '/predis/predis/src'), 'PhpAmqpLib\\' => array($vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), 'Humbug\\SelfUpdate\\' => array($vendorDir . '/laravel-zero/phar-updater/src'), 'Humbug\\' => array($vendorDir . '/padraic/humbug_get_contents/src'), 'Hoa\\Ustring\\' => array($vendorDir . '/hoa/ustring'), 'Hoa\\Stream\\' => array($vendorDir . '/hoa/stream'), 'Hoa\\Protocol\\' => array($vendorDir . '/hoa/protocol'), 'Hoa\\Iterator\\' => array($vendorDir . '/hoa/iterator'), 'Hoa\\File\\' => array($vendorDir . '/hoa/file'), 'Hoa\\Exception\\' => array($vendorDir . '/hoa/exception'), 'Hoa\\Event\\' => array($vendorDir . '/hoa/event'), 'Hoa\\Console\\' => array($vendorDir . '/hoa/console'), 'Hoa\\Consistency\\' => array($vendorDir . '/hoa/consistency'), 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'Flow\\JSONPath\\' => array($vendorDir . '/softcreatr/jsonpath/src'), 'Facebook\\WebDriver\\' => array($vendorDir . '/php-webdriver/webdriver/lib'), 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), 'Codeception\\PHPUnit\\' => array($vendorDir . '/codeception/phpunit-wrapper/src'), 'Codeception\\Extension\\' => array($vendorDir . '/codeception/codeception/ext'), 'Codeception\\' => array($vendorDir . '/codeception/codeception/src/Codeception', $vendorDir . '/codeception/stub/src'), ); $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); } } return false; } public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints($constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); } return implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } public static function getAllRawData() { return self::getInstalled(); } public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } 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) { if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = require __DIR__ . '/installed.php'; } else { self::$installed = array(); } } $installed[] = self::$installed; return $installed; } } array($vendorDir . '/behat/gherkin/src'), ); 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) { $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; } public static function isOpensslParseSafe() { if (null !== self::$useOpensslParse) { return self::$useOpensslParse; } if (PHP_VERSION_ID >= 50600) { return self::$useOpensslParse = true; } if ( (PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328) || (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423) || PHP_VERSION_ID >= 50507 ) { return self::$useOpensslParse = true; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { 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; }; if ( $compareDistroVersionPrefix('5.3.3-7+squeeze', 18) || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) ) { return self::$useOpensslParse = true; } if (!class_exists('Symfony\Component\Process\PhpProcess')) { return self::$useOpensslParse = false; } $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) { 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) ) { return self::$useOpensslParse = true; } return self::$useOpensslParse = false; } public static function reset() { self::$caFileValidity = array(); self::$caPath = null; self::$useOpensslParse = null; } 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; } private static function caFileUsable($certFile, LoggerInterface $logger = null) { return $certFile && static::isFile($certFile, $logger) && static::isReadable($certFile, $logger) && static::validateCaFile($certFile, $logger); } private static function caDirUsable($certDir, LoggerInterface $logger = null) { return $certDir && static::isDir($certDir, $logger) && static::isReadable($certDir, $logger) && static::glob($certDir . '/*', $logger); } 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; } 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; } 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; } 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; } } offsetExists($key); } if (is_object($collection)) { return property_exists($collection, (string)$key); } return false; } public static function getValue($collection, $key, bool $magicIsAllowed = false) { $return = null; if ( $magicIsAllowed && is_object($collection) && !$collection instanceof ArrayAccess && method_exists($collection, '__get') ) { $return = $collection->__get($key); } elseif (is_object($collection) && !$collection instanceof ArrayAccess) { $return = $collection->$key; } elseif ($collection instanceof ArrayAccess) { $return = $collection->offsetGet($key); } elseif (is_array($collection)) { if (is_int($key) && $key < 0) { $return = array_slice($collection, $key, 1, false)[0]; } else { $return = $collection[$key]; } } elseif (is_int($key)) { $return = self::getValueByIndex($collection, $key); } else { $return = $collection[$key]; } return $return; } private static function getValueByIndex($collection, $key) { $i = 0; foreach ($collection as $val) { if ($i === $key) { return $val; } ++$i; } if ($key < 0) { $total = $i; $i = 0; foreach ($collection as $val) { if ($i - $total === $key) { return $val; } ++$i; } } return null; } public static function setValue(&$collection, $key, $value) { if (is_object($collection) && !$collection instanceof ArrayAccess) { return $collection->$key = $value; } if ($collection instanceof ArrayAccess) { return $collection->offsetSet($key, $value); } return $collection[$key] = $value; } public static function unsetValue(&$collection, $key): void { if (is_object($collection) && !$collection instanceof ArrayAccess) { unset($collection->$key); } if ($collection instanceof ArrayAccess) { $collection->offsetUnset($key); } if (is_array($collection)) { unset($collection[$key]); } } public static function arrayValues($collection): array { if (is_array($collection)) { return array_values($collection); } if (is_object($collection)) { return array_values((array)$collection); } throw new JSONPathException("Invalid variable type for arrayValues"); } } token->value)) { $result = []; foreach ($this->token->value as $value) { if (AccessHelper::keyExists($collection, $value, $this->magicIsAllowed)) { $result[] = AccessHelper::getValue($collection, $value, $this->magicIsAllowed); } } return $result; } if (AccessHelper::keyExists($collection, $this->token->value, $this->magicIsAllowed)) { return [ AccessHelper::getValue($collection, $this->token->value, $this->magicIsAllowed), ]; } if ($this->token->value === '*') { return AccessHelper::arrayValues($collection); } if ($this->token->value === 'length') { return [ count($collection), ]; } return []; } } token->value['start']; $end = $this->token->value['end']; $step = $this->token->value['step'] ?: 1; if ($start === null) { $start = 0; } if ($start < 0) { $start = $length + $start; if ($start < 0) { $start = 0; } } if ($end === null) { $end = $length; } if ($end < 0) { $end = $length + $end; } $result = []; for ($i = $start; $i < $end; $i += $step) { $index = $i; if ($i < 0) { $index = $length + $i; } if (AccessHelper::keyExists($collection, $index, $this->magicIsAllowed)) { $result[] = $collection[$index]; } } return $result; } } [^\s<>!=]+)|\[["\']?(?.*?)["\']?\]) (\s*(?==|=~|=|<>|!==|!=|>=|<=|>|<|in|!in|nin)\s*(?.+))? '; public function filter($collection): array { preg_match('/^' . static::MATCH_QUERY_OPERATORS . '$/x', $this->token->value, $matches); if (!isset($matches[1])) { throw new RuntimeException('Malformed filter query'); } $key = $matches['key'] ?: $matches['keySquare']; if ($key === '') { throw new RuntimeException('Malformed filter query: key was not set'); } $operator = $matches['operator'] ?? null; $comparisonValue = $matches['comparisonValue'] ?? null; if (is_string($comparisonValue)) { if (strpos($comparisonValue, "[") === 0 && substr($comparisonValue, -1) === "]") { $comparisonValue = substr($comparisonValue, 1, -1); $comparisonValue = preg_replace('/^[\'"]/', '', $comparisonValue); $comparisonValue = preg_replace('/[\'"]$/', '', $comparisonValue); $comparisonValue = preg_replace('/[\'"],[ ]*[\'"]/', ',', $comparisonValue); $comparisonValue = array_map('trim', explode(",", $comparisonValue)); } else { $comparisonValue = preg_replace('/^[\'"]/', '', $comparisonValue); $comparisonValue = preg_replace('/[\'"]$/', '', $comparisonValue); if (strtolower($comparisonValue) === 'false') { $comparisonValue = false; } elseif (strtolower($comparisonValue) === 'true') { $comparisonValue = true; } elseif (strtolower($comparisonValue) === 'null') { $comparisonValue = null; } } } $return = []; foreach ($collection as $value) { if (AccessHelper::keyExists($value, $key, $this->magicIsAllowed)) { $value1 = AccessHelper::getValue($value, $key, $this->magicIsAllowed); if ($operator === null && $value1) { $return[] = $value; } if (($operator === '=' || $operator === '==') && $value1 == $comparisonValue) { $return[] = $value; } if (($operator === '!=' || $operator === '!==' || $operator === '<>') && $value1 != $comparisonValue) { $return[] = $value; } if ($operator === '=~' && @preg_match($comparisonValue, $value1)) { $return[] = $value; } if ($operator === '>' && $value1 > $comparisonValue) { $return[] = $value; } if ($operator === '>=' && $value1 >= $comparisonValue) { $return[] = $value; } if ($operator === '<' && $value1 < $comparisonValue) { $return[] = $value; } if ($operator === '<=' && $value1 <= $comparisonValue) { $return[] = $value; } if ($operator === 'in' && is_array($comparisonValue) && in_array($value1, $comparisonValue)) { $return[] = $value; } if ( ($operator === 'nin' || $operator === '!in') && is_array($comparisonValue) && !in_array($value1, $comparisonValue) ) { $return[] = $value; } } } return $return; } } token = $token; $this->magicIsAllowed = (bool)($options & JSONPath::ALLOW_MAGIC); } abstract public function filter($collection): array; } \w+)\s*(?[-+*\/])\s*(?\d+)/', $this->token->value, $matches); $matchKey = $matches['key']; if (AccessHelper::keyExists($collection, $matchKey, $this->magicIsAllowed)) { $value = AccessHelper::getValue($collection, $matchKey, $this->magicIsAllowed); } elseif ($matches['key'] === 'length') { $value = count($collection); } else { return []; } switch ($matches['operator']) { case '+': $resultKey = $value + $matches['numeric']; break; case '*': $resultKey = $value * $matches['numeric']; break; case '-': $resultKey = $value - $matches['numeric']; break; case '/': $resultKey = $value / $matches['numeric']; break; default: throw new JSONPathException('Unsupported operator in expression'); } $result = []; if (AccessHelper::keyExists($collection, $resultKey, $this->magicIsAllowed)) { $result[] = AccessHelper::getValue($collection, $resultKey, $this->magicIsAllowed); } return $result; } } token->value as $index) { if (AccessHelper::keyExists($collection, $index, $this->magicIsAllowed)) { $return[] = AccessHelper::getValue($collection, $index, $this->magicIsAllowed); } } return $return; } } recurse($result, $collection); return $result; } private function recurse(array &$result, $data): void { $result[] = $data; if (AccessHelper::isCollectionType($data)) { foreach (AccessHelper::arrayValues($data) as $key => $value) { $results[] = $value; if (AccessHelper::isCollectionType($value)) { $this->recurse($result, $value); } } } } } 1) { if ($expression[0] === '$') { $expression = substr($expression, 1); $len--; } if ($expression[0] !== '.' && $expression[0] !== '[') { $expression = '.' . $expression; $len++; } $this->expression = $expression; $this->expressionLength = $len; } } public function parseExpressionTokens(): array { $dotIndexDepth = 0; $squareBracketDepth = 0; $tokenValue = ''; $tokens = []; for ($i = 0; $i < $this->expressionLength; $i++) { $char = $this->expression[$i]; if (($squareBracketDepth === 0) && $char === '.') { if ($this->lookAhead($i) === '.') { $tokens[] = new JSONPathToken(JSONPathToken::T_RECURSIVE, null); } continue; } if ($char === '[') { ++$squareBracketDepth; if ($squareBracketDepth === 1) { continue; } } if ($char === ']') { --$squareBracketDepth; if ($squareBracketDepth === 0) { continue; } } if ($squareBracketDepth > 0) { $tokenValue .= $char; if ($squareBracketDepth === 1 && $this->lookAhead($i) === ']') { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; } } if ($squareBracketDepth === 0) { $tokenValue .= $char; if ($char === '.' && $dotIndexDepth > 1) { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; continue; } if ($this->atEnd($i) || in_array($this->lookAhead($i), ['.', '['])) { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; --$dotIndexDepth; } } } if ($tokenValue !== '') { $tokens[] = $this->createToken($tokenValue); } return $tokens; } protected function lookAhead(int $pos, int $forward = 1): ?string { return $this->expression[$pos + $forward] ?? null; } protected function atEnd(int $pos): bool { return $pos === $this->expressionLength; } public function parseExpression(): array { return $this->parseExpressionTokens(); } protected function createToken(string $value): JSONPathToken { $tokenValue = $value; $ret = null; if (preg_match('/^(' . static::MATCH_INDEX . ')$/xu', $tokenValue, $matches)) { if (preg_match('/^-?\d+$/', $tokenValue)) { $tokenValue = (int)$tokenValue; } $ret = new JSONPathToken(JSONPathToken::T_INDEX, $tokenValue); } elseif (preg_match('/^' . static::MATCH_INDEXES . '$/xu', $tokenValue, $matches)) { $tokenValue = explode(',', trim($tokenValue, ',')); foreach ($tokenValue as $i => $v) { $tokenValue[$i] = (int)trim($v); } $ret = new JSONPathToken(JSONPathToken::T_INDEXES, $tokenValue); } elseif (preg_match('/^' . static::MATCH_SLICE . '$/xu', $tokenValue, $matches)) { $parts = explode(':', $tokenValue); $tokenValue = [ 'start' => isset($parts[0]) && $parts[0] !== '' ? (int)$parts[0] : null, 'end' => isset($parts[1]) && $parts[1] !== '' ? (int)$parts[1] : null, 'step' => isset($parts[2]) && $parts[2] !== '' ? (int)$parts[2] : null, ]; $ret = new JSONPathToken(JSONPathToken::T_SLICE, $tokenValue); } elseif (preg_match('/^' . static::MATCH_QUERY_RESULT . '$/xu', $tokenValue)) { $tokenValue = substr($tokenValue, 1, -1); $ret = new JSONPathToken(JSONPathToken::T_QUERY_RESULT, $tokenValue); } elseif (preg_match('/^' . static::MATCH_QUERY_MATCH . '$/xu', $tokenValue)) { $tokenValue = substr($tokenValue, 2, -1); $ret = new JSONPathToken(JSONPathToken::T_QUERY_MATCH, $tokenValue); } elseif ( preg_match('/^' . static::MATCH_INDEX_IN_SINGLE_QUOTES . '$/xu', $tokenValue, $matches) || preg_match('/^' . static::MATCH_INDEX_IN_DOUBLE_QUOTES . '$/xu', $tokenValue, $matches) ) { if (isset($matches[1])) { $tokenValue = $matches[1]; $tokenValue = trim($tokenValue); $possibleArray = false; if ($matches[0][0] === '"') { $possibleArray = explode('","', $tokenValue); } elseif ($matches[0][0] === "'") { $possibleArray = explode("','", $tokenValue); } if ($possibleArray !== false && count($possibleArray) > 1) { $tokenValue = $possibleArray; } } else { $tokenValue = ''; } $ret = new JSONPathToken(JSONPathToken::T_INDEX, $tokenValue); } if ($ret !== null) { return $ret; } throw new JSONPathException("Unable to parse token {$tokenValue} in expression: $this->expression"); } } data = $data; $this->options = $options; } public function find(string $expression): self { $tokens = $this->parseTokens($expression); $collectionData = [$this->data]; foreach ($tokens as $token) { $filter = $token->buildFilter($this->options); $filteredDataList = []; foreach ($collectionData as $value) { if (AccessHelper::isCollectionType($value)) { $filteredDataList[] = $filter->filter($value); } } if (!empty($filteredDataList)) { $collectionData = array_merge(...$filteredDataList); } else { $collectionData = []; } } return new static($collectionData, $this->options); } public function first() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } $value = $this->data[$keys[0]] ?? null; return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function last() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } $value = $this->data[end($keys)] ?: null; return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function firstKey() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } return $keys[0]; } public function lastKey() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys) || end($keys) === false) { return null; } return end($keys); } public function parseTokens(string $expression): array { $cacheKey = crc32($expression); if (isset(static::$tokenCache[$cacheKey])) { return static::$tokenCache[$cacheKey]; } $lexer = new JSONPathLexer($expression); $tokens = $lexer->parseExpression(); static::$tokenCache[$cacheKey] = $tokens; return $tokens; } public function getData(): array { return $this->data; } public function data(): array { trigger_error( 'Calling JSONPath::data() is deprecated, please use JSONPath::getData() instead.', E_USER_DEPRECATED ); return $this->getData(); } public function __get($key) { return $this->offsetExists($key) ? $this->offsetGet($key) : null; } public function offsetExists($offset): bool { return AccessHelper::keyExists($this->data, $offset); } public function offsetGet($offset) { $value = AccessHelper::getValue($this->data, $offset); return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function offsetSet($offset, $value): void { if ($offset === null) { $this->data[] = $value; } else { AccessHelper::setValue($this->data, $offset, $value); } } public function offsetUnset($offset): void { AccessHelper::unsetValue($this->data, $offset); } public function jsonSerialize(): array { return $this->getData(); } public function current() { $value = current($this->data); return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function next(): void { next($this->data); } public function key() { return key($this->data); } public function valid(): bool { return key($this->data) !== null; } public function rewind(): void { reset($this->data); } public function count(): int { return count($this->data); } } validateType($type); $this->type = $type; $this->value = $value; } public function validateType(string $type): void { if (!in_array($type, static::getTypes(), true)) { throw new JSONPathException('Invalid token: ' . $type); } } public static function getTypes(): array { return [ static::T_INDEX, static::T_RECURSIVE, static::T_QUERY_RESULT, static::T_QUERY_MATCH, static::T_SLICE, static::T_INDEXES, ]; } public function buildFilter(bool $options) { $filterClass = 'Flow\\JSONPath\\Filters\\' . ucfirst($this->type) . 'Filter'; if (!class_exists($filterClass)) { throw new JSONPathException("No filter class exists for token [{$this->type}]"); } return new $filterClass($this, $options); } } iterable = Create::iterFor($iterable); if (isset($config['concurrency'])) { $this->concurrency = $config['concurrency']; } if (isset($config['fulfilled'])) { $this->onFulfilled = $config['fulfilled']; } if (isset($config['rejected'])) { $this->onRejected = $config['rejected']; } } public function promise() { if ($this->aggregate) { return $this->aggregate; } try { $this->createPromise(); $this->iterable->rewind(); $this->refillPending(); } catch (\Throwable $e) { $this->aggregate->reject($e); } catch (\Exception $e) { $this->aggregate->reject($e); } return $this->aggregate; } private function createPromise() { $this->mutex = false; $this->aggregate = new Promise(function () { if ($this->checkIfFinished()) { return; } reset($this->pending); while ($promise = current($this->pending)) { next($this->pending); $promise->wait(); if (Is::settled($this->aggregate)) { return; } } }); $clearFn = function () { $this->iterable = $this->concurrency = $this->pending = null; $this->onFulfilled = $this->onRejected = null; $this->nextPendingIndex = 0; }; $this->aggregate->then($clearFn, $clearFn); } private function refillPending() { if (!$this->concurrency) { while ($this->addPending() && $this->advanceIterator()); return; } $concurrency = is_callable($this->concurrency) ? call_user_func($this->concurrency, count($this->pending)) : $this->concurrency; $concurrency = max($concurrency - count($this->pending), 0); if (!$concurrency) { return; } $this->addPending(); while (--$concurrency && $this->advanceIterator() && $this->addPending()); } private function addPending() { if (!$this->iterable || !$this->iterable->valid()) { return false; } $promise = Create::promiseFor($this->iterable->current()); $key = $this->iterable->key(); $idx = $this->nextPendingIndex++; $this->pending[$idx] = $promise->then( function ($value) use ($idx, $key) { if ($this->onFulfilled) { call_user_func( $this->onFulfilled, $value, $key, $this->aggregate ); } $this->step($idx); }, function ($reason) use ($idx, $key) { if ($this->onRejected) { call_user_func( $this->onRejected, $reason, $key, $this->aggregate ); } $this->step($idx); } ); return true; } private function advanceIterator() { if ($this->mutex) { return false; } $this->mutex = true; try { $this->iterable->next(); $this->mutex = false; return true; } catch (\Throwable $e) { $this->aggregate->reject($e); $this->mutex = false; return false; } catch (\Exception $e) { $this->aggregate->reject($e); $this->mutex = false; return false; } } private function step($idx) { if (Is::settled($this->aggregate)) { return; } unset($this->pending[$idx]); if ($this->advanceIterator() && !$this->checkIfFinished()) { $this->refillPending(); } } private function checkIfFinished() { if (!$this->pending && !$this->iterable->valid()) { $this->aggregate->resolve(null); return true; } return false; } } add(function () use ($task, $promise) { try { if (Is::pending($promise)) { $promise->resolve($task()); } } catch (\Throwable $e) { $promise->reject($e); } catch (\Exception $e) { $promise->reject($e); } }); return $promise; } public static function inspect(PromiseInterface $promise) { try { return [ 'state' => PromiseInterface::FULFILLED, 'value' => $promise->wait() ]; } catch (RejectionException $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; } catch (\Throwable $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; } catch (\Exception $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; } } public static function inspectAll($promises) { $results = []; foreach ($promises as $key => $promise) { $results[$key] = inspect($promise); } return $results; } public static function unwrap($promises) { $results = []; foreach ($promises as $key => $promise) { $results[$key] = $promise->wait(); } return $results; } public static function all($promises, $recursive = false) { $results = []; $promise = Each::of( $promises, function ($value, $idx) use (&$results) { $results[$idx] = $value; }, function ($reason, $idx, Promise $aggregate) { $aggregate->reject($reason); } )->then(function () use (&$results) { ksort($results); return $results; }); if (true === $recursive) { $promise = $promise->then(function ($results) use ($recursive, &$promises) { foreach ($promises as $promise) { if (Is::pending($promise)) { return self::all($promises, $recursive); } } return $results; }); } return $promise; } public static function some($count, $promises) { $results = []; $rejections = []; return Each::of( $promises, function ($value, $idx, PromiseInterface $p) use (&$results, $count) { if (Is::settled($p)) { return; } $results[$idx] = $value; if (count($results) >= $count) { $p->resolve(null); } }, function ($reason) use (&$rejections) { $rejections[] = $reason; } )->then( function () use (&$results, &$rejections, $count) { if (count($results) !== $count) { throw new AggregateException( 'Not enough promises to fulfill count', $rejections ); } ksort($results); return array_values($results); } ); } public static function any($promises) { return self::some(1, $promises)->then(function ($values) { return $values[0]; }); } public static function settle($promises) { $results = []; return Each::of( $promises, function ($value, $idx) use (&$results) { $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; }, function ($reason, $idx) use (&$results) { $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; } )->then(function () use (&$results) { ksort($results); return $results; }); } } waitFn = $waitFn; $this->cancelFn = $cancelFn; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if ($this->state === self::PENDING) { $p = new Promise(null, [$this, 'cancel']); $this->handlers[] = [$p, $onFulfilled, $onRejected]; $p->waitList = $this->waitList; $p->waitList[] = $this; return $p; } if ($this->state === self::FULFILLED) { $promise = Create::promiseFor($this->result); return $onFulfilled ? $promise->then($onFulfilled) : $promise; } $rejection = Create::rejectionFor($this->result); return $onRejected ? $rejection->then(null, $onRejected) : $rejection; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true) { $this->waitIfPending(); if ($this->result instanceof PromiseInterface) { return $this->result->wait($unwrap); } if ($unwrap) { if ($this->state === self::FULFILLED) { return $this->result; } throw Create::exceptionFor($this->result); } } public function getState() { return $this->state; } public function cancel() { if ($this->state !== self::PENDING) { return; } $this->waitFn = $this->waitList = null; if ($this->cancelFn) { $fn = $this->cancelFn; $this->cancelFn = null; try { $fn(); } catch (\Throwable $e) { $this->reject($e); } catch (\Exception $e) { $this->reject($e); } } if ($this->state === self::PENDING) { $this->reject(new CancellationException('Promise has been cancelled')); } } public function resolve($value) { $this->settle(self::FULFILLED, $value); } public function reject($reason) { $this->settle(self::REJECTED, $reason); } private function settle($state, $value) { if ($this->state !== self::PENDING) { if ($state === $this->state && $value === $this->result) { return; } throw $this->state === $state ? new \LogicException("The promise is already {$state}.") : new \LogicException("Cannot change a {$this->state} promise to {$state}"); } if ($value === $this) { throw new \LogicException('Cannot fulfill or reject a promise with itself'); } $this->state = $state; $this->result = $value; $handlers = $this->handlers; $this->handlers = null; $this->waitList = $this->waitFn = null; $this->cancelFn = null; if (!$handlers) { return; } if (!is_object($value) || !method_exists($value, 'then')) { $id = $state === self::FULFILLED ? 1 : 2; Utils::queue()->add(static function () use ($id, $value, $handlers) { foreach ($handlers as $handler) { self::callHandler($id, $value, $handler); } }); } elseif ($value instanceof Promise && Is::pending($value)) { $value->handlers = array_merge($value->handlers, $handlers); } else { $value->then( static function ($value) use ($handlers) { foreach ($handlers as $handler) { self::callHandler(1, $value, $handler); } }, static function ($reason) use ($handlers) { foreach ($handlers as $handler) { self::callHandler(2, $reason, $handler); } } ); } } private static function callHandler($index, $value, array $handler) { $promise = $handler[0]; if (Is::settled($promise)) { return; } try { if (isset($handler[$index])) { $f = $handler[$index]; unset($handler); $promise->resolve($f($value)); } elseif ($index === 1) { $promise->resolve($value); } else { $promise->reject($value); } } catch (\Throwable $reason) { $promise->reject($reason); } catch (\Exception $reason) { $promise->reject($reason); } } private function waitIfPending() { if ($this->state !== self::PENDING) { return; } elseif ($this->waitFn) { $this->invokeWaitFn(); } elseif ($this->waitList) { $this->invokeWaitList(); } else { $this->reject('Cannot wait on a promise that has ' . 'no internal wait function. You must provide a wait ' . 'function when constructing the promise to be able to ' . 'wait on a promise.'); } Utils::queue()->run(); if ($this->state === self::PENDING) { $this->reject('Invoking the wait callback did not resolve the promise'); } } private function invokeWaitFn() { try { $wfn = $this->waitFn; $this->waitFn = null; $wfn(true); } catch (\Exception $reason) { if ($this->state === self::PENDING) { $this->reject($reason); } else { throw $reason; } } } private function invokeWaitList() { $waitList = $this->waitList; $this->waitList = null; foreach ($waitList as $result) { do { $result->waitIfPending(); $result = $result->result; } while ($result instanceof Promise); if ($result instanceof PromiseInterface) { $result->wait(false); } } } } generator = $generatorFn(); $this->result = new Promise(function () { while (isset($this->currentPromise)) { $this->currentPromise->wait(); } }); try { $this->nextCoroutine($this->generator->current()); } catch (\Exception $exception) { $this->result->reject($exception); } catch (Throwable $throwable) { $this->result->reject($throwable); } } public static function of(callable $generatorFn) { return new self($generatorFn); } public function then( callable $onFulfilled = null, callable $onRejected = null ) { return $this->result->then($onFulfilled, $onRejected); } public function otherwise(callable $onRejected) { return $this->result->otherwise($onRejected); } public function wait($unwrap = true) { return $this->result->wait($unwrap); } public function getState() { return $this->result->getState(); } public function resolve($value) { $this->result->resolve($value); } public function reject($reason) { $this->result->reject($reason); } public function cancel() { $this->currentPromise->cancel(); $this->result->cancel(); } private function nextCoroutine($yielded) { $this->currentPromise = Create::promiseFor($yielded) ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); } public function _handleSuccess($value) { unset($this->currentPromise); try { $next = $this->generator->send($value); if ($this->generator->valid()) { $this->nextCoroutine($next); } else { $this->result->resolve($value); } } catch (Exception $exception) { $this->result->reject($exception); } catch (Throwable $throwable) { $this->result->reject($throwable); } } public function _handleFailure($reason) { unset($this->currentPromise); try { $nextYield = $this->generator->throw(Create::exceptionFor($reason)); $this->nextCoroutine($nextYield); } catch (Exception $exception) { $this->result->reject($exception); } catch (Throwable $throwable) { $this->result->reject($throwable); } } } reason = $reason; $message = 'The promise was rejected'; if ($description) { $message .= ' with reason: ' . $description; } elseif (is_string($reason) || (is_object($reason) && method_exists($reason, '__toString')) ) { $message .= ' with reason: ' . $this->reason; } elseif ($reason instanceof \JsonSerializable) { $message .= ' with reason: ' . json_encode($this->reason, JSON_PRETTY_PRINT); } parent::__construct($message); } public function getReason() { return $this->reason; } } getState() === PromiseInterface::PENDING; } public static function settled(PromiseInterface $promise) { return $promise->getState() !== PromiseInterface::PENDING; } public static function fulfilled(PromiseInterface $promise) { return $promise->getState() === PromiseInterface::FULFILLED; } public static function rejected(PromiseInterface $promise) { return $promise->getState() === PromiseInterface::REJECTED; } } reason = $reason; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if (!$onRejected) { return $this; } $queue = Utils::queue(); $reason = $this->reason; $p = new Promise([$queue, 'run']); $queue->add(static function () use ($p, $reason, $onRejected) { if (Is::pending($p)) { try { $p->resolve($onRejected($reason)); } catch (\Throwable $e) { $p->reject($e); } catch (\Exception $e) { $p->reject($e); } } }); return $p; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true, $defaultDelivery = null) { if ($unwrap) { throw Create::exceptionFor($this->reason); } return null; } public function getState() { return self::REJECTED; } public function resolve($value) { throw new \LogicException("Cannot resolve a rejected promise"); } public function reject($reason) { if ($reason !== $this->reason) { throw new \LogicException("Cannot reject a rejected promise"); } } public function cancel() { } } value = $value; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if (!$onFulfilled) { return $this; } $queue = Utils::queue(); $p = new Promise([$queue, 'run']); $value = $this->value; $queue->add(static function () use ($p, $value, $onFulfilled) { if (Is::pending($p)) { try { $p->resolve($onFulfilled($value)); } catch (\Throwable $e) { $p->reject($e); } catch (\Exception $e) { $p->reject($e); } } }); return $p; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true, $defaultDelivery = null) { return $unwrap ? $this->value : null; } public function getState() { return self::FULFILLED; } public function resolve($value) { if ($value !== $this->value) { throw new \LogicException("Cannot resolve a fulfilled promise"); } } public function reject($reason) { throw new \LogicException("Cannot reject a fulfilled promise"); } public function cancel() { } } $onFulfilled, 'rejected' => $onRejected ]))->promise(); } public static function ofLimit( $iterable, $concurrency, callable $onFulfilled = null, callable $onRejected = null ) { return (new EachPromise($iterable, [ 'fulfilled' => $onFulfilled, 'rejected' => $onRejected, 'concurrency' => $concurrency ]))->promise(); } public static function ofLimitAll( $iterable, $concurrency, callable $onFulfilled = null ) { return each_limit( $iterable, $concurrency, $onFulfilled, function ($reason, $idx, PromiseInterface $aggregate) { $aggregate->reject($reason); } ); } } then([$promise, 'resolve'], [$promise, 'reject']); return $promise; } return new FulfilledPromise($value); } public static function rejectionFor($reason) { if ($reason instanceof PromiseInterface) { return $reason; } return new RejectedPromise($reason); } public static function exceptionFor($reason) { if ($reason instanceof \Exception || $reason instanceof \Throwable) { return $reason; } return new RejectionException($reason); } public static function iterFor($value) { if ($value instanceof \Iterator) { return $value; } if (is_array($value)) { return new \ArrayIterator($value); } return new \ArrayIterator([$value]); } } enableShutdown) { $err = error_get_last(); if (!$err || ($err['type'] ^ E_ERROR)) { $this->run(); } } }); } } public function isEmpty() { return !$this->queue; } public function add(callable $task) { $this->queue[] = $task; } public function run() { while ($task = array_shift($this->queue)) { $task(); } } public function disableShutdown() { $this->enableShutdown = false; } } protocol; } public function withProtocolVersion($version) { if ($this->protocol === $version) { return $this; } $new = clone $this; $new->protocol = $version; return $new; } public function getHeaders() { return $this->headers; } public function hasHeader($header) { return isset($this->headerNames[strtolower($header)]); } public function getHeader($header) { $header = strtolower($header); if (!isset($this->headerNames[$header])) { return []; } $header = $this->headerNames[$header]; return $this->headers[$header]; } public function getHeaderLine($header) { return implode(', ', $this->getHeader($header)); } public function withHeader($header, $value) { $this->assertHeader($header); $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; if (isset($new->headerNames[$normalized])) { unset($new->headers[$new->headerNames[$normalized]]); } $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; return $new; } public function withAddedHeader($header, $value) { $this->assertHeader($header); $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; if (isset($new->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $new->headers[$header] = array_merge($this->headers[$header], $value); } else { $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; } return $new; } public function withoutHeader($header) { $normalized = strtolower($header); if (!isset($this->headerNames[$normalized])) { return $this; } $header = $this->headerNames[$normalized]; $new = clone $this; unset($new->headers[$header], $new->headerNames[$normalized]); return $new; } public function getBody() { if (!$this->stream) { $this->stream = Utils::streamFor(''); } return $this->stream; } public function withBody(StreamInterface $body) { if ($body === $this->stream) { return $this; } $new = clone $this; $new->stream = $body; return $new; } private function setHeaders(array $headers) { $this->headerNames = $this->headers = []; foreach ($headers as $header => $value) { if (is_int($header)) { $header = (string) $header; } $this->assertHeader($header); $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $this->headers[$header] = array_merge($this->headers[$header], $value); } else { $this->headerNames[$normalized] = $header; $this->headers[$header] = $value; } } } private function normalizeHeaderValue($value) { if (!is_array($value)) { return $this->trimAndValidateHeaderValues([$value]); } if (count($value) === 0) { throw new \InvalidArgumentException('Header value can not be an empty array.'); } return $this->trimAndValidateHeaderValues($value); } private function trimAndValidateHeaderValues(array $values) { return array_map(function ($value) { if (!is_scalar($value) && null !== $value) { throw new \InvalidArgumentException(sprintf( 'Header value must be scalar or null but %s provided.', is_object($value) ? get_class($value) : gettype($value) )); } $trimmed = trim((string) $value, " \t"); $this->assertValue($trimmed); return $trimmed; }, array_values($values)); } private function assertHeader($header) { if (!is_string($header)) { throw new \InvalidArgumentException(sprintf( 'Header name must be a string but %s provided.', is_object($header) ? get_class($header) : gettype($header) )); } if ($header === '') { throw new \InvalidArgumentException('Header name can not be empty.'); } if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { throw new \InvalidArgumentException( sprintf( '"%s" is not valid header name', $header ) ); } } private function assertValue($value) { if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value)) { throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); } } } stream = $stream; $this->setLimit($limit); $this->setOffset($offset); } public function eof() { if ($this->stream->eof()) { return true; } if ($this->limit == -1) { return false; } return $this->stream->tell() >= $this->offset + $this->limit; } public function getSize() { if (null === ($length = $this->stream->getSize())) { return null; } elseif ($this->limit == -1) { return $length - $this->offset; } else { return min($this->limit, $length - $this->offset); } } public function seek($offset, $whence = SEEK_SET) { if ($whence !== SEEK_SET || $offset < 0) { throw new \RuntimeException(sprintf( 'Cannot seek to offset %s with whence %s', $offset, $whence )); } $offset += $this->offset; if ($this->limit !== -1) { if ($offset > $this->offset + $this->limit) { $offset = $this->offset + $this->limit; } } $this->stream->seek($offset); } public function tell() { return $this->stream->tell() - $this->offset; } public function setOffset($offset) { $current = $this->stream->tell(); if ($current !== $offset) { if ($this->stream->isSeekable()) { $this->stream->seek($offset); } elseif ($current > $offset) { throw new \RuntimeException("Could not seek to stream offset $offset"); } else { $this->stream->read($offset - $current); } } $this->offset = $offset; } public function setLimit($limit) { $this->limit = $limit; } public function read($length) { if ($this->limit == -1) { return $this->stream->read($length); } $remaining = ($this->offset + $this->limit) - $this->stream->tell(); if ($remaining > 0) { return $this->stream->read(min($remaining, $length)); } return ''; } } $v) { if (!in_array(strtolower($k), $keys)) { $result[$k] = $v; } } return $result; } public static function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1) { $bufferSize = 8192; if ($maxLen === -1) { while (!$source->eof()) { if (!$dest->write($source->read($bufferSize))) { break; } } } else { $remaining = $maxLen; while ($remaining > 0 && !$source->eof()) { $buf = $source->read(min($bufferSize, $remaining)); $len = strlen($buf); if (!$len) { break; } $remaining -= $len; $dest->write($buf); } } } public static function copyToString(StreamInterface $stream, $maxLen = -1) { $buffer = ''; if ($maxLen === -1) { while (!$stream->eof()) { $buf = $stream->read(1048576); if ($buf == null) { break; } $buffer .= $buf; } return $buffer; } $len = 0; while (!$stream->eof() && $len < $maxLen) { $buf = $stream->read($maxLen - $len); if ($buf == null) { break; } $buffer .= $buf; $len = strlen($buffer); } return $buffer; } public static function hash(StreamInterface $stream, $algo, $rawOutput = false) { $pos = $stream->tell(); if ($pos > 0) { $stream->rewind(); } $ctx = hash_init($algo); while (!$stream->eof()) { hash_update($ctx, $stream->read(1048576)); } $out = hash_final($ctx, (bool) $rawOutput); $stream->seek($pos); return $out; } public static function modifyRequest(RequestInterface $request, array $changes) { if (!$changes) { return $request; } $headers = $request->getHeaders(); if (!isset($changes['uri'])) { $uri = $request->getUri(); } else { if ($host = $changes['uri']->getHost()) { $changes['set_headers']['Host'] = $host; if ($port = $changes['uri']->getPort()) { $standardPorts = ['http' => 80, 'https' => 443]; $scheme = $changes['uri']->getScheme(); if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { $changes['set_headers']['Host'] .= ':' . $port; } } } $uri = $changes['uri']; } if (!empty($changes['remove_headers'])) { $headers = self::caselessRemove($changes['remove_headers'], $headers); } if (!empty($changes['set_headers'])) { $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); $headers = $changes['set_headers'] + $headers; } if (isset($changes['query'])) { $uri = $uri->withQuery($changes['query']); } if ($request instanceof ServerRequestInterface) { $new = (new ServerRequest( isset($changes['method']) ? $changes['method'] : $request->getMethod(), $uri, $headers, isset($changes['body']) ? $changes['body'] : $request->getBody(), isset($changes['version']) ? $changes['version'] : $request->getProtocolVersion(), $request->getServerParams() )) ->withParsedBody($request->getParsedBody()) ->withQueryParams($request->getQueryParams()) ->withCookieParams($request->getCookieParams()) ->withUploadedFiles($request->getUploadedFiles()); foreach ($request->getAttributes() as $key => $value) { $new = $new->withAttribute($key, $value); } return $new; } return new Request( isset($changes['method']) ? $changes['method'] : $request->getMethod(), $uri, $headers, isset($changes['body']) ? $changes['body'] : $request->getBody(), isset($changes['version']) ? $changes['version'] : $request->getProtocolVersion() ); } public static function readLine(StreamInterface $stream, $maxLength = null) { $buffer = ''; $size = 0; while (!$stream->eof()) { if (null == ($byte = $stream->read(1))) { return $buffer; } $buffer .= $byte; if ($byte === "\n" || ++$size === $maxLength - 1) { break; } } return $buffer; } public static function streamFor($resource = '', array $options = []) { if (is_scalar($resource)) { $stream = self::tryFopen('php://temp', 'r+'); if ($resource !== '') { fwrite($stream, $resource); fseek($stream, 0); } return new Stream($stream, $options); } switch (gettype($resource)) { case 'resource': $metaData = \stream_get_meta_data($resource); if (isset($metaData['uri']) && $metaData['uri'] === 'php://input') { $stream = self::tryFopen('php://temp', 'w+'); fwrite($stream, stream_get_contents($resource)); fseek($stream, 0); $resource = $stream; } return new Stream($resource, $options); case 'object': if ($resource instanceof StreamInterface) { return $resource; } elseif ($resource instanceof \Iterator) { return new PumpStream(function () use ($resource) { if (!$resource->valid()) { return false; } $result = $resource->current(); $resource->next(); return $result; }, $options); } elseif (method_exists($resource, '__toString')) { return Utils::streamFor((string) $resource, $options); } break; case 'NULL': return new Stream(self::tryFopen('php://temp', 'r+'), $options); } if (is_callable($resource)) { return new PumpStream($resource, $options); } throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); } public static function tryFopen($filename, $mode) { $ex = null; set_error_handler(function () use ($filename, $mode, &$ex) { $ex = new \RuntimeException(sprintf( 'Unable to open "%s" using mode "%s": %s', $filename, $mode, func_get_args()[1] )); return true; }); try { $handle = fopen($filename, $mode); } catch (\Throwable $e) { $ex = new \RuntimeException(sprintf( 'Unable to open "%s" using mode "%s": %s', $filename, $mode, $e->getMessage() ), 0, $e); } restore_error_handler(); if ($ex) { throw $ex; } return $handle; } public static function uriFor($uri) { if ($uri instanceof UriInterface) { return $uri; } if (is_string($uri)) { return new Uri($uri); } throw new \InvalidArgumentException('URI must be a string or UriInterface'); } } addStream($stream); } } public function __toString() { try { $this->rewind(); return $this->getContents(); } catch (\Exception $e) { return ''; } } public function addStream(StreamInterface $stream) { if (!$stream->isReadable()) { throw new \InvalidArgumentException('Each stream must be readable'); } if (!$stream->isSeekable()) { $this->seekable = false; } $this->streams[] = $stream; } public function getContents() { return Utils::copyToString($this); } public function close() { $this->pos = $this->current = 0; $this->seekable = true; foreach ($this->streams as $stream) { $stream->close(); } $this->streams = []; } public function detach() { $this->pos = $this->current = 0; $this->seekable = true; foreach ($this->streams as $stream) { $stream->detach(); } $this->streams = []; return null; } public function tell() { return $this->pos; } public function getSize() { $size = 0; foreach ($this->streams as $stream) { $s = $stream->getSize(); if ($s === null) { return null; } $size += $s; } return $size; } public function eof() { return !$this->streams || ($this->current >= count($this->streams) - 1 && $this->streams[$this->current]->eof()); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { if (!$this->seekable) { throw new \RuntimeException('This AppendStream is not seekable'); } elseif ($whence !== SEEK_SET) { throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); } $this->pos = $this->current = 0; foreach ($this->streams as $i => $stream) { try { $stream->rewind(); } catch (\Exception $e) { throw new \RuntimeException('Unable to seek stream ' . $i . ' of the AppendStream', 0, $e); } } while ($this->pos < $offset && !$this->eof()) { $result = $this->read(min(8096, $offset - $this->pos)); if ($result === '') { break; } } } public function read($length) { $buffer = ''; $total = count($this->streams) - 1; $remaining = $length; $progressToNext = false; while ($remaining > 0) { if ($progressToNext || $this->streams[$this->current]->eof()) { $progressToNext = false; if ($this->current === $total) { break; } $this->current++; } $result = $this->streams[$this->current]->read($remaining); if ($result == null) { $progressToNext = true; continue; } $buffer .= $result; $remaining = $length - strlen($buffer); } $this->pos += strlen($buffer); return $buffer; } public function isReadable() { return true; } public function isWritable() { return false; } public function isSeekable() { return $this->seekable; } public function write($string) { throw new \RuntimeException('Cannot write to an AppendStream'); } public function getMetadata($key = null) { return $key ? null : []; } } getScheme() != '') { return $rel->withPath(self::removeDotSegments($rel->getPath())); } if ($rel->getAuthority() != '') { $targetAuthority = $rel->getAuthority(); $targetPath = self::removeDotSegments($rel->getPath()); $targetQuery = $rel->getQuery(); } else { $targetAuthority = $base->getAuthority(); if ($rel->getPath() === '') { $targetPath = $base->getPath(); $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); } else { if ($rel->getPath()[0] === '/') { $targetPath = $rel->getPath(); } else { if ($targetAuthority != '' && $base->getPath() === '') { $targetPath = '/' . $rel->getPath(); } else { $lastSlashPos = strrpos($base->getPath(), '/'); if ($lastSlashPos === false) { $targetPath = $rel->getPath(); } else { $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); } } } $targetPath = self::removeDotSegments($targetPath); $targetQuery = $rel->getQuery(); } } return new Uri(Uri::composeComponents( $base->getScheme(), $targetAuthority, $targetPath, $targetQuery, $rel->getFragment() )); } public static function relativize(UriInterface $base, UriInterface $target) { if ($target->getScheme() !== '' && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') ) { return $target; } if (Uri::isRelativePathReference($target)) { return $target; } if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { return $target->withScheme(''); } $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); if ($base->getPath() !== $target->getPath()) { return $emptyPathUri->withPath(self::getRelativePath($base, $target)); } if ($base->getQuery() === $target->getQuery()) { return $emptyPathUri->withQuery(''); } if ($target->getQuery() === '') { $segments = explode('/', $target->getPath()); $lastSegment = end($segments); return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); } return $emptyPathUri; } private static function getRelativePath(UriInterface $base, UriInterface $target) { $sourceSegments = explode('/', $base->getPath()); $targetSegments = explode('/', $target->getPath()); array_pop($sourceSegments); $targetLastSegment = array_pop($targetSegments); foreach ($sourceSegments as $i => $segment) { if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { unset($sourceSegments[$i], $targetSegments[$i]); } else { break; } } $targetSegments[] = $targetLastSegment; $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { $relativePath = "./$relativePath"; } elseif ('/' === $relativePath[0]) { if ($base->getAuthority() != '' && $base->getPath() === '') { $relativePath = ".$relativePath"; } else { $relativePath = "./$relativePath"; } } return $relativePath; } private function __construct() { } } 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, 'nntp' => 119, 'news' => 119, 'telnet' => 23, 'tn3270' => 23, 'imap' => 143, 'pop' => 110, 'ldap' => 389, ]; private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; private static $charSubDelims = '!\$&\'\(\)\*\+,;='; private static $replaceQuery = ['=' => '%3D', '&' => '%26']; private $scheme = ''; private $userInfo = ''; private $host = ''; private $port; private $path = ''; private $query = ''; private $fragment = ''; public function __construct($uri = '') { if ($uri != '') { $parts = self::parse($uri); if ($parts === false) { throw new \InvalidArgumentException("Unable to parse URI: $uri"); } $this->applyParts($parts); } } private static function parse($url) { $prefix = ''; if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { $prefix = $matches[1]; $url = $matches[2]; } $encodedUrl = preg_replace_callback( '%[^:/@?&=#]+%usD', static function ($matches) { return urlencode($matches[0]); }, $url ); $result = parse_url($prefix . $encodedUrl); if ($result === false) { return false; } return array_map('urldecode', $result); } public function __toString() { return self::composeComponents( $this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment ); } public static function composeComponents($scheme, $authority, $path, $query, $fragment) { $uri = ''; if ($scheme != '') { $uri .= $scheme . ':'; } if ($authority != ''|| $scheme === 'file') { $uri .= '//' . $authority; } $uri .= $path; if ($query != '') { $uri .= '?' . $query; } if ($fragment != '') { $uri .= '#' . $fragment; } return $uri; } public static function isDefaultPort(UriInterface $uri) { return $uri->getPort() === null || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); } public static function isAbsolute(UriInterface $uri) { return $uri->getScheme() !== ''; } public static function isNetworkPathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() !== ''; } public static function isAbsolutePathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() === '' && isset($uri->getPath()[0]) && $uri->getPath()[0] === '/'; } public static function isRelativePathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() === '' && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); } public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) { if ($base !== null) { $uri = UriResolver::resolve($base, $uri); return ($uri->getScheme() === $base->getScheme()) && ($uri->getAuthority() === $base->getAuthority()) && ($uri->getPath() === $base->getPath()) && ($uri->getQuery() === $base->getQuery()); } return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; } public static function removeDotSegments($path) { return UriResolver::removeDotSegments($path); } public static function resolve(UriInterface $base, $rel) { if (!($rel instanceof UriInterface)) { $rel = new self($rel); } return UriResolver::resolve($base, $rel); } public static function withoutQueryValue(UriInterface $uri, $key) { $result = self::getFilteredQueryString($uri, [$key]); return $uri->withQuery(implode('&', $result)); } public static function withQueryValue(UriInterface $uri, $key, $value) { $result = self::getFilteredQueryString($uri, [$key]); $result[] = self::generateQueryString($key, $value); return $uri->withQuery(implode('&', $result)); } public static function withQueryValues(UriInterface $uri, array $keyValueArray) { $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); foreach ($keyValueArray as $key => $value) { $result[] = self::generateQueryString($key, $value); } return $uri->withQuery(implode('&', $result)); } public static function fromParts(array $parts) { $uri = new self(); $uri->applyParts($parts); $uri->validateState(); return $uri; } public function getScheme() { return $this->scheme; } public function getAuthority() { $authority = $this->host; if ($this->userInfo !== '') { $authority = $this->userInfo . '@' . $authority; } if ($this->port !== null) { $authority .= ':' . $this->port; } return $authority; } public function getUserInfo() { return $this->userInfo; } public function getHost() { return $this->host; } public function getPort() { return $this->port; } public function getPath() { return $this->path; } public function getQuery() { return $this->query; } public function getFragment() { return $this->fragment; } public function withScheme($scheme) { $scheme = $this->filterScheme($scheme); if ($this->scheme === $scheme) { return $this; } $new = clone $this; $new->scheme = $scheme; $new->removeDefaultPort(); $new->validateState(); return $new; } public function withUserInfo($user, $password = null) { $info = $this->filterUserInfoComponent($user); if ($password !== null) { $info .= ':' . $this->filterUserInfoComponent($password); } if ($this->userInfo === $info) { return $this; } $new = clone $this; $new->userInfo = $info; $new->validateState(); return $new; } public function withHost($host) { $host = $this->filterHost($host); if ($this->host === $host) { return $this; } $new = clone $this; $new->host = $host; $new->validateState(); return $new; } public function withPort($port) { $port = $this->filterPort($port); if ($this->port === $port) { return $this; } $new = clone $this; $new->port = $port; $new->removeDefaultPort(); $new->validateState(); return $new; } public function withPath($path) { $path = $this->filterPath($path); if ($this->path === $path) { return $this; } $new = clone $this; $new->path = $path; $new->validateState(); return $new; } public function withQuery($query) { $query = $this->filterQueryAndFragment($query); if ($this->query === $query) { return $this; } $new = clone $this; $new->query = $query; return $new; } public function withFragment($fragment) { $fragment = $this->filterQueryAndFragment($fragment); if ($this->fragment === $fragment) { return $this; } $new = clone $this; $new->fragment = $fragment; return $new; } private function applyParts(array $parts) { $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; $this->userInfo = isset($parts['user']) ? $this->filterUserInfoComponent($parts['user']) : ''; $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : ''; $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); } $this->removeDefaultPort(); } private function filterScheme($scheme) { if (!is_string($scheme)) { throw new \InvalidArgumentException('Scheme must be a string'); } return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); } private function filterUserInfoComponent($component) { if (!is_string($component)) { throw new \InvalidArgumentException('User info must be a string'); } return preg_replace_callback( '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $component ); } private function filterHost($host) { if (!is_string($host)) { throw new \InvalidArgumentException('Host must be a string'); } return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); } private function filterPort($port) { if ($port === null) { return null; } $port = (int) $port; if (0 > $port || 0xffff < $port) { throw new \InvalidArgumentException( sprintf('Invalid port: %d. Must be between 0 and 65535', $port) ); } return $port; } private static function getFilteredQueryString(UriInterface $uri, array $keys) { $current = $uri->getQuery(); if ($current === '') { return []; } $decodedKeys = array_map('rawurldecode', $keys); return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); }); } private static function generateQueryString($key, $value) { $queryString = strtr($key, self::$replaceQuery); if ($value !== null) { $queryString .= '=' . strtr($value, self::$replaceQuery); } return $queryString; } private function removeDefaultPort() { if ($this->port !== null && self::isDefaultPort($this)) { $this->port = null; } } private function filterPath($path) { if (!is_string($path)) { throw new \InvalidArgumentException('Path must be a string'); } return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $path ); } private function filterQueryAndFragment($str) { if (!is_string($str)) { throw new \InvalidArgumentException('Query and fragment must be a string'); } return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $str ); } private function rawurlencodeMatchZero(array $match) { return rawurlencode($match[0]); } private function validateState() { if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { $this->host = self::HTTP_DEFAULT_HOST; } if ($this->getAuthority() === '') { if (0 === strpos($this->path, '//')) { throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); } if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); } } elseif (isset($this->path[0]) && $this->path[0] !== '/') { @trigger_error( 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', E_USER_DEPRECATED ); $this->path = '/' . $this->path; } } } assertMethod($method); if (!($uri instanceof UriInterface)) { $uri = new Uri($uri); } $this->method = strtoupper($method); $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; if (!isset($this->headerNames['host'])) { $this->updateHostFromUri(); } if ($body !== '' && $body !== null) { $this->stream = Utils::streamFor($body); } } public function getRequestTarget() { if ($this->requestTarget !== null) { return $this->requestTarget; } $target = $this->uri->getPath(); if ($target == '') { $target = '/'; } if ($this->uri->getQuery() != '') { $target .= '?' . $this->uri->getQuery(); } return $target; } public function withRequestTarget($requestTarget) { if (preg_match('#\s#', $requestTarget)) { throw new InvalidArgumentException( 'Invalid request target provided; cannot contain whitespace' ); } $new = clone $this; $new->requestTarget = $requestTarget; return $new; } public function getMethod() { return $this->method; } public function withMethod($method) { $this->assertMethod($method); $new = clone $this; $new->method = strtoupper($method); return $new; } public function getUri() { return $this->uri; } public function withUri(UriInterface $uri, $preserveHost = false) { if ($uri === $this->uri) { return $this; } $new = clone $this; $new->uri = $uri; if (!$preserveHost || !isset($this->headerNames['host'])) { $new->updateHostFromUri(); } return $new; } private function updateHostFromUri() { $host = $this->uri->getHost(); if ($host == '') { return; } if (($port = $this->uri->getPort()) !== null) { $host .= ':' . $port; } if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else { $header = 'Host'; $this->headerNames['host'] = 'Host'; } $this->headers = [$header => [$host]] + $this->headers; } private function assertMethod($method) { if (!is_string($method) || $method === '') { throw new \InvalidArgumentException('Method must be a non-empty string.'); } } } stream = $stream; } public function __get($name) { if ($name == 'stream') { $this->stream = $this->createStream(); return $this->stream; } throw new \UnexpectedValueException("$name not found on class"); } public function __toString() { try { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } catch (\Exception $e) { trigger_error('StreamDecorator::__toString exception: ' . (string) $e, E_USER_ERROR); return ''; } } public function getContents() { return Utils::copyToString($this); } public function __call($method, array $args) { $result = call_user_func_array([$this->stream, $method], $args); return $result === $this->stream ? $this : $result; } public function close() { $this->stream->close(); } public function getMetadata($key = null) { return $this->stream->getMetadata($key); } public function detach() { return $this->stream->detach(); } public function getSize() { return $this->stream->getSize(); } public function eof() { return $this->stream->eof(); } public function tell() { return $this->stream->tell(); } public function isReadable() { return $this->stream->isReadable(); } public function isWritable() { return $this->stream->isWritable(); } public function isSeekable() { return $this->stream->isSeekable(); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { $this->stream->seek($offset, $whence); } public function read($length) { return $this->stream->read($length); } public function write($string) { return $this->stream->write($string); } protected function createStream() { throw new \BadMethodCallException('Not implemented'); } } remoteStream = $stream; $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); } public function getSize() { $remoteSize = $this->remoteStream->getSize(); if (null === $remoteSize) { return null; } return max($this->stream->getSize(), $remoteSize); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { if ($whence == SEEK_SET) { $byte = $offset; } elseif ($whence == SEEK_CUR) { $byte = $offset + $this->tell(); } elseif ($whence == SEEK_END) { $size = $this->remoteStream->getSize(); if ($size === null) { $size = $this->cacheEntireStream(); } $byte = $size + $offset; } else { throw new \InvalidArgumentException('Invalid whence'); } $diff = $byte - $this->stream->getSize(); if ($diff > 0) { while ($diff > 0 && !$this->remoteStream->eof()) { $this->read($diff); $diff = $byte - $this->stream->getSize(); } } else { $this->stream->seek($byte); } } public function read($length) { $data = $this->stream->read($length); $remaining = $length - strlen($data); if ($remaining) { $remoteData = $this->remoteStream->read( $remaining + $this->skipReadBytes ); if ($this->skipReadBytes) { $len = strlen($remoteData); $remoteData = substr($remoteData, $this->skipReadBytes); $this->skipReadBytes = max(0, $this->skipReadBytes - $len); } $data .= $remoteData; $this->stream->write($remoteData); } return $data; } public function write($string) { $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); if ($overflow > 0) { $this->skipReadBytes += $overflow; } return $this->stream->write($string); } public function eof() { return $this->stream->eof() && $this->remoteStream->eof(); } public function close() { $this->remoteStream->close() && $this->stream->close(); } private function cacheEntireStream() { $target = new FnStream(['write' => 'strlen']); Utils::copyToStream($this, $target); return $this->tell(); } } read(10); $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); $resource = StreamWrapper::getResource($stream); stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); } private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) { $filename_header_length = 0; if (substr(bin2hex($header), 6, 2) === '08') { $filename_header_length = 1; while ($stream->read(1) !== chr(0)) { $filename_header_length++; } } return $filename_header_length; } } hwm = $hwm; } public function __toString() { return $this->getContents(); } public function getContents() { $buffer = $this->buffer; $this->buffer = ''; return $buffer; } public function close() { $this->buffer = ''; } public function detach() { $this->close(); return null; } public function getSize() { return strlen($this->buffer); } public function isReadable() { return true; } public function isWritable() { return true; } public function isSeekable() { return false; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { throw new \RuntimeException('Cannot seek a BufferStream'); } public function eof() { return strlen($this->buffer) === 0; } public function tell() { throw new \RuntimeException('Cannot determine the position of a BufferStream'); } public function read($length) { $currentLength = strlen($this->buffer); if ($length >= $currentLength) { $result = $this->buffer; $this->buffer = ''; } else { $result = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); } return $result; } public function write($string) { $this->buffer .= $string; if (strlen($this->buffer) >= $this->hwm) { return false; } return strlen($string); } public function getMetadata($key = null) { if ($key == 'hwm') { return $this->hwm; } return $key ? null : []; } } source = $source; $this->size = isset($options['size']) ? $options['size'] : null; $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; $this->buffer = new BufferStream(); } public function __toString() { try { return Utils::copyToString($this); } catch (\Exception $e) { return ''; } } public function close() { $this->detach(); } public function detach() { $this->tellPos = false; $this->source = null; return null; } public function getSize() { return $this->size; } public function tell() { return $this->tellPos; } public function eof() { return !$this->source; } public function isSeekable() { return false; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { throw new \RuntimeException('Cannot seek a PumpStream'); } public function isWritable() { return false; } public function write($string) { throw new \RuntimeException('Cannot write to a PumpStream'); } public function isReadable() { return true; } public function read($length) { $data = $this->buffer->read($length); $readLen = strlen($data); $this->tellPos += $readLen; $remaining = $length - $readLen; if ($remaining) { $this->pump($remaining); $data .= $this->buffer->read($remaining); $this->tellPos += strlen($data) - $readLen; } return $data; } public function getContents() { $result = ''; while (!$this->eof()) { $result .= $this->read(1000000); } return $result; } public function getMetadata($key = null) { if (!$key) { return $this->metadata; } return isset($this->metadata[$key]) ? $this->metadata[$key] : null; } private function pump($length) { if ($this->source) { do { $data = call_user_func($this->source, $length); if ($data === false || $data === null) { $this->source = null; return; } $this->buffer->write($data); $length -= strlen($data); } while ($length > 0); } } } ]+>|[^=]+/', $kvp, $matches)) { $m = $matches[0]; if (isset($m[1])) { $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); } else { $part[] = trim($m[0], $trimmed); } } } if ($part) { $params[] = $part; } } return $params; } public static function normalize($header) { if (!is_array($header)) { return array_map('trim', explode(',', $header)); } $result = []; foreach ($header as $value) { foreach ((array) $value as $v) { if (strpos($v, ',') === false) { $result[] = $v; continue; } foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { $result[] = trim($vv); } } } return $result; } } boundary = $boundary ?: sha1(uniqid('', true)); $this->stream = $this->createStream($elements); } public function getBoundary() { return $this->boundary; } public function isWritable() { return false; } private function getHeaders(array $headers) { $str = ''; foreach ($headers as $key => $value) { $str .= "{$key}: {$value}\r\n"; } return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; } protected function createStream(array $elements) { $stream = new AppendStream(); foreach ($elements as $element) { $this->addElement($stream, $element); } $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); return $stream; } private function addElement(AppendStream $stream, array $element) { foreach (['contents', 'name'] as $key) { if (!array_key_exists($key, $element)) { throw new \InvalidArgumentException("A '{$key}' key is required"); } } $element['contents'] = Utils::streamFor($element['contents']); if (empty($element['filename'])) { $uri = $element['contents']->getMetadata('uri'); if (substr($uri, 0, 6) !== 'php://') { $element['filename'] = $uri; } } list($body, $headers) = $this->createElement( $element['name'], $element['contents'], isset($element['filename']) ? $element['filename'] : null, isset($element['headers']) ? $element['headers'] : [] ); $stream->addStream(Utils::streamFor($this->getHeaders($headers))); $stream->addStream($body); $stream->addStream(Utils::streamFor("\r\n")); } private function createElement($name, StreamInterface $stream, $filename, array $headers) { $disposition = $this->getHeader($headers, 'content-disposition'); if (!$disposition) { $headers['Content-Disposition'] = ($filename === '0' || $filename) ? sprintf( 'form-data; name="%s"; filename="%s"', $name, basename($filename) ) : "form-data; name=\"{$name}\""; } $length = $this->getHeader($headers, 'content-length'); if (!$length) { if ($length = $stream->getSize()) { $headers['Content-Length'] = (string) $length; } } $type = $this->getHeader($headers, 'content-type'); if (!$type && ($filename === '0' || $filename)) { if ($type = MimeType::fromFilename($filename)) { $headers['Content-Type'] = $type; } } return [$stream, $headers]; } private function getHeader(array $headers, $key) { $lowercaseHeader = strtolower($key); foreach ($headers as $k => $v) { if (strtolower($k) === $lowercaseHeader) { return $v; } } return null; } } $v) { $k = $encoder($k); if (!is_array($v)) { $qs .= $k; if ($v !== null) { $qs .= '=' . $encoder($v); } $qs .= '&'; } else { foreach ($v as $vv) { $qs .= $k; if ($vv !== null) { $qs .= '=' . $encoder($vv); } $qs .= '&'; } } } return $qs ? (string) substr($qs, 0, -1) : ''; } } serverParams = $serverParams; parent::__construct($method, $uri, $headers, $body, $version); } public static function normalizeFiles(array $files) { $normalized = []; foreach ($files as $key => $value) { if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; } elseif (is_array($value) && isset($value['tmp_name'])) { $normalized[$key] = self::createUploadedFileFromSpec($value); } elseif (is_array($value)) { $normalized[$key] = self::normalizeFiles($value); continue; } else { throw new InvalidArgumentException('Invalid value in files specification'); } } return $normalized; } private static function createUploadedFileFromSpec(array $value) { if (is_array($value['tmp_name'])) { return self::normalizeNestedFileSpec($value); } return new UploadedFile( $value['tmp_name'], (int) $value['size'], (int) $value['error'], $value['name'], $value['type'] ); } private static function normalizeNestedFileSpec(array $files = []) { $normalizedFiles = []; foreach (array_keys($files['tmp_name']) as $key) { $spec = [ 'tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key], 'type' => $files['type'][$key], ]; $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); } return $normalizedFiles; } public static function fromGlobals() { $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; $headers = getallheaders(); $uri = self::getUriFromGlobals(); $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); return $serverRequest ->withCookieParams($_COOKIE) ->withQueryParams($_GET) ->withParsedBody($_POST) ->withUploadedFiles(self::normalizeFiles($_FILES)); } private static function extractHostAndPortFromAuthority($authority) { $uri = 'http://' . $authority; $parts = parse_url($uri); if (false === $parts) { return [null, null]; } $host = isset($parts['host']) ? $parts['host'] : null; $port = isset($parts['port']) ? $parts['port'] : null; return [$host, $port]; } public static function getUriFromGlobals() { $uri = new Uri(''); $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); $hasPort = false; if (isset($_SERVER['HTTP_HOST'])) { list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); if ($host !== null) { $uri = $uri->withHost($host); } if ($port !== null) { $hasPort = true; $uri = $uri->withPort($port); } } elseif (isset($_SERVER['SERVER_NAME'])) { $uri = $uri->withHost($_SERVER['SERVER_NAME']); } elseif (isset($_SERVER['SERVER_ADDR'])) { $uri = $uri->withHost($_SERVER['SERVER_ADDR']); } if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { $uri = $uri->withPort($_SERVER['SERVER_PORT']); } $hasQuery = false; if (isset($_SERVER['REQUEST_URI'])) { $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); $uri = $uri->withPath($requestUriParts[0]); if (isset($requestUriParts[1])) { $hasQuery = true; $uri = $uri->withQuery($requestUriParts[1]); } } if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { $uri = $uri->withQuery($_SERVER['QUERY_STRING']); } return $uri; } public function getServerParams() { return $this->serverParams; } public function getUploadedFiles() { return $this->uploadedFiles; } public function withUploadedFiles(array $uploadedFiles) { $new = clone $this; $new->uploadedFiles = $uploadedFiles; return $new; } public function getCookieParams() { return $this->cookieParams; } public function withCookieParams(array $cookies) { $new = clone $this; $new->cookieParams = $cookies; return $new; } public function getQueryParams() { return $this->queryParams; } public function withQueryParams(array $query) { $new = clone $this; $new->queryParams = $query; return $new; } public function getParsedBody() { return $this->parsedBody; } public function withParsedBody($data) { $new = clone $this; $new->parsedBody = $data; return $new; } public function getAttributes() { return $this->attributes; } public function getAttribute($attribute, $default = null) { if (false === array_key_exists($attribute, $this->attributes)) { return $default; } return $this->attributes[$attribute]; } public function withAttribute($attribute, $value) { $new = clone $this; $new->attributes[$attribute] = $value; return $new; } public function withoutAttribute($attribute) { if (false === array_key_exists($attribute, $this->attributes)) { return $this; } $new = clone $this; unset($new->attributes[$attribute]); return $new; } } @,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; } setError($errorStatus); $this->setSize($size); $this->setClientFilename($clientFilename); $this->setClientMediaType($clientMediaType); if ($this->isOk()) { $this->setStreamOrFile($streamOrFile); } } private function setStreamOrFile($streamOrFile) { if (is_string($streamOrFile)) { $this->file = $streamOrFile; } elseif (is_resource($streamOrFile)) { $this->stream = new Stream($streamOrFile); } elseif ($streamOrFile instanceof StreamInterface) { $this->stream = $streamOrFile; } else { throw new InvalidArgumentException( 'Invalid stream or file provided for UploadedFile' ); } } private function setError($error) { if (false === is_int($error)) { throw new InvalidArgumentException( 'Upload file error status must be an integer' ); } if (false === in_array($error, UploadedFile::$errors)) { throw new InvalidArgumentException( 'Invalid error status for UploadedFile' ); } $this->error = $error; } private function setSize($size) { if (false === is_int($size)) { throw new InvalidArgumentException( 'Upload file size must be an integer' ); } $this->size = $size; } private function isStringOrNull($param) { return in_array(gettype($param), ['string', 'NULL']); } private function isStringNotEmpty($param) { return is_string($param) && false === empty($param); } private function setClientFilename($clientFilename) { if (false === $this->isStringOrNull($clientFilename)) { throw new InvalidArgumentException( 'Upload file client filename must be a string or null' ); } $this->clientFilename = $clientFilename; } private function setClientMediaType($clientMediaType) { if (false === $this->isStringOrNull($clientMediaType)) { throw new InvalidArgumentException( 'Upload file client media type must be a string or null' ); } $this->clientMediaType = $clientMediaType; } private function isOk() { return $this->error === UPLOAD_ERR_OK; } public function isMoved() { return $this->moved; } private function validateActive() { if (false === $this->isOk()) { throw new RuntimeException('Cannot retrieve stream due to upload error'); } if ($this->isMoved()) { throw new RuntimeException('Cannot retrieve stream after it has already been moved'); } } public function getStream() { $this->validateActive(); if ($this->stream instanceof StreamInterface) { return $this->stream; } return new LazyOpenStream($this->file, 'r+'); } public function moveTo($targetPath) { $this->validateActive(); if (false === $this->isStringNotEmpty($targetPath)) { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); } if ($this->file) { $this->moved = php_sapi_name() == 'cli' ? rename($this->file, $targetPath) : move_uploaded_file($this->file, $targetPath); } else { Utils::copyToStream( $this->getStream(), new LazyOpenStream($targetPath, 'w') ); $this->moved = true; } if (false === $this->moved) { throw new RuntimeException( sprintf('Uploaded file could not be moved to %s', $targetPath) ); } } public function getSize() { return $this->size; } public function getError() { return $this->error; } public function getClientFilename() { return $this->clientFilename; } public function getClientMediaType() { return $this->clientMediaType; } } getHost(), $modified->getHost()) !== 0) { return true; } if ($original->getScheme() !== $modified->getScheme()) { return true; } if (self::computePort($original) !== self::computePort($modified)) { return true; } return false; } private static function computePort(UriInterface $uri) { $port = $uri->getPort(); if (null !== $port) { return $port; } return 'https' === $uri->getScheme() ? 443 : 80; } private function __construct() { } } getPath() === '' && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') ) { $uri = $uri->withPath('/'); } if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { $uri = $uri->withHost(''); } if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { $uri = $uri->withPort(null); } if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); } if ($flags & self::REMOVE_DUPLICATE_SLASHES) { $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); } if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { $queryKeyValues = explode('&', $uri->getQuery()); sort($queryKeyValues); $uri = $uri->withQuery(implode('&', $queryKeyValues)); } return $uri; } public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) { return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); } private static function capitalizePercentEncoding(UriInterface $uri) { $regex = '/(?:%[A-Fa-f0-9]{2})++/'; $callback = function (array $match) { return strtoupper($match[0]); }; return $uri->withPath( preg_replace_callback($regex, $callback, $uri->getPath()) )->withQuery( preg_replace_callback($regex, $callback, $uri->getQuery()) ); } private static function decodeUnreservedCharacters(UriInterface $uri) { $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; $callback = function (array $match) { return rawurldecode($match[0]); }; return $uri->withPath( preg_replace_callback($regex, $callback, $uri->getPath()) )->withQuery( preg_replace_callback($regex, $callback, $uri->getQuery()) ); } private function __construct() { } } getMethod() . ' ' . $message->getRequestTarget()) . ' HTTP/' . $message->getProtocolVersion(); if (!$message->hasHeader('host')) { $msg .= "\r\nHost: " . $message->getUri()->getHost(); } } elseif ($message instanceof ResponseInterface) { $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' . $message->getStatusCode() . ' ' . $message->getReasonPhrase(); } else { throw new \InvalidArgumentException('Unknown message type'); } foreach ($message->getHeaders() as $name => $values) { if (strtolower($name) === 'set-cookie') { foreach ($values as $value) { $msg .= "\r\n{$name}: " . $value; } } else { $msg .= "\r\n{$name}: " . implode(', ', $values); } } return "{$msg}\r\n\r\n" . $message->getBody(); } public static function bodySummary(MessageInterface $message, $truncateAt = 120) { $body = $message->getBody(); if (!$body->isSeekable() || !$body->isReadable()) { return null; } $size = $body->getSize(); if ($size === 0) { return null; } $summary = $body->read($truncateAt); $body->rewind(); if ($size > $truncateAt) { $summary .= ' (truncated...)'; } if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { return null; } return $summary; } public static function rewindBody(MessageInterface $message) { $body = $message->getBody(); if ($body->tell()) { $body->rewind(); } } public static function parseMessage($message) { if (!$message) { throw new \InvalidArgumentException('Invalid message'); } $message = ltrim($message, "\r\n"); $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); if ($messageParts === false || count($messageParts) !== 2) { throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); } list($rawHeaders, $body) = $messageParts; $rawHeaders .= "\r\n"; $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); if ($headerParts === false || count($headerParts) !== 2) { throw new \InvalidArgumentException('Invalid message: Missing status line'); } list($startLine, $rawHeaders) = $headerParts; if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); } $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); if ($count !== substr_count($rawHeaders, "\n")) { if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); } throw new \InvalidArgumentException('Invalid header syntax'); } $headers = []; foreach ($headerLines as $headerLine) { $headers[$headerLine[1]][] = $headerLine[2]; } return [ 'start-line' => $startLine, 'headers' => $headers, 'body' => $body, ]; } public static function parseRequestUri($path, array $headers) { $hostKey = array_filter(array_keys($headers), function ($k) { return strtolower($k) === 'host'; }); if (!$hostKey) { return $path; } $host = $headers[reset($hostKey)][0]; $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; return $scheme . '://' . $host . '/' . ltrim($path, '/'); } public static function parseRequest($message) { $data = self::parseMessage($message); $matches = []; if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { throw new \InvalidArgumentException('Invalid request string'); } $parts = explode(' ', $data['start-line'], 3); $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; $request = new Request( $parts[0], $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], $data['headers'], $data['body'], $version ); return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); } public static function parseResponse($message) { $data = self::parseMessage($message); if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); } $parts = explode(' ', $data['start-line'], 3); return new Response( (int) $parts[1], $data['headers'], $data['body'], explode('/', $parts[0])[1], isset($parts[2]) ? $parts[2] : null ); } } isReadable()) { $mode = $stream->isWritable() ? 'r+' : 'r'; } elseif ($stream->isWritable()) { $mode = 'w'; } else { throw new \InvalidArgumentException('The stream must be readable, ' . 'writable, or both.'); } return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream)); } public static function createStreamContext(StreamInterface $stream) { return stream_context_create([ 'guzzle' => ['stream' => $stream] ]); } public static function register() { if (!in_array('guzzle', stream_get_wrappers())) { stream_wrapper_register('guzzle', __CLASS__); } } public function stream_open($path, $mode, $options, &$opened_path) { $options = stream_context_get_options($this->context); if (!isset($options['guzzle']['stream'])) { return false; } $this->mode = $mode; $this->stream = $options['guzzle']['stream']; return true; } public function stream_read($count) { return $this->stream->read($count); } public function stream_write($data) { return (int) $this->stream->write($data); } public function stream_tell() { return $this->stream->tell(); } public function stream_eof() { return $this->stream->eof(); } public function stream_seek($offset, $whence) { $this->stream->seek($offset, $whence); return true; } public function stream_cast($cast_as) { $stream = clone($this->stream); return $stream->detach(); } public function stream_stat() { static $modeMap = [ 'r' => 33060, 'rb' => 33060, 'r+' => 33206, 'w' => 33188, 'wb' => 33188 ]; return [ 'dev' => 0, 'ino' => 0, 'mode' => $modeMap[$this->mode], 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => $this->stream->getSize() ?: 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ]; } public function url_stat($path, $flags) { return [ 'dev' => 0, 'ino' => 0, 'mode' => 0, 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ]; } } 'video/3gpp', '7z' => 'application/x-7z-compressed', 'aac' => 'audio/x-aac', 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'asc' => 'text/plain', 'asf' => 'video/x-ms-asf', 'atom' => 'application/atom+xml', 'avi' => 'video/x-msvideo', 'bmp' => 'image/bmp', 'bz2' => 'application/x-bzip2', 'cer' => 'application/pkix-cert', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'css' => 'text/css', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'deb' => 'application/x-debian-package', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dvi' => 'application/x-dvi', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'etx' => 'text/x-setext', 'flac' => 'audio/flac', 'flv' => 'video/x-flv', 'gif' => 'image/gif', 'gz' => 'application/gzip', 'htm' => 'text/html', 'html' => 'text/html', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ini' => 'text/plain', 'iso' => 'application/x-iso9660-image', 'jar' => 'application/java-archive', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'js' => 'text/javascript', 'json' => 'application/json', 'latex' => 'application/x-latex', 'log' => 'text/plain', 'm4a' => 'audio/mp4', 'm4v' => 'video/mp4', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mov' => 'video/quicktime', 'mkv' => 'video/x-matroska', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4v' => 'video/mp4', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'oga' => 'audio/ogg', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'pbm' => 'image/x-portable-bitmap', 'pdf' => 'application/pdf', 'pgm' => 'image/x-portable-graymap', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'ppm' => 'image/x-portable-pixmap', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ps' => 'application/postscript', 'qt' => 'video/quicktime', 'rar' => 'application/x-rar-compressed', 'ras' => 'image/x-cmu-raster', 'rss' => 'application/rss+xml', 'rtf' => 'application/rtf', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'svg' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash', 'tar' => 'application/x-tar', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'torrent' => 'application/x-bittorrent', 'ttf' => 'application/x-font-ttf', 'txt' => 'text/plain', 'wav' => 'audio/x-wav', 'webm' => 'video/webm', 'webp' => 'image/webp', 'wma' => 'audio/x-ms-wma', 'wmv' => 'video/x-ms-wmv', 'woff' => 'application/x-font-woff', 'wsdl' => 'application/wsdl+xml', 'xbm' => 'image/x-xbitmap', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xml' => 'application/xml', 'xpm' => 'image/x-xpixmap', 'xwd' => 'image/x-xwindowdump', 'yaml' => 'text/yaml', 'yml' => 'text/yaml', 'zip' => 'application/zip', ]; $extension = strtolower($extension); return isset($mimetypes[$extension]) ? $mimetypes[$extension] : null; } } size = $options['size']; } $this->customMetadata = isset($options['metadata']) ? $options['metadata'] : []; $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); $this->uri = $this->getMetadata('uri'); } public function __destruct() { $this->close(); } public function __toString() { try { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } catch (\Exception $e) { return ''; } } public function getContents() { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } $contents = stream_get_contents($this->stream); if ($contents === false) { throw new \RuntimeException('Unable to read stream contents'); } return $contents; } public function close() { if (isset($this->stream)) { if (is_resource($this->stream)) { fclose($this->stream); } $this->detach(); } } public function detach() { if (!isset($this->stream)) { return null; } $result = $this->stream; unset($this->stream); $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = false; return $result; } public function getSize() { if ($this->size !== null) { return $this->size; } if (!isset($this->stream)) { return null; } if ($this->uri) { clearstatcache(true, $this->uri); } $stats = fstat($this->stream); if (isset($stats['size'])) { $this->size = $stats['size']; return $this->size; } return null; } public function isReadable() { return $this->readable; } public function isWritable() { return $this->writable; } public function isSeekable() { return $this->seekable; } public function eof() { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } return feof($this->stream); } public function tell() { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } $result = ftell($this->stream); if ($result === false) { throw new \RuntimeException('Unable to determine stream position'); } return $result; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { $whence = (int) $whence; if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->seekable) { throw new \RuntimeException('Stream is not seekable'); } if (fseek($this->stream, $offset, $whence) === -1) { throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . var_export($whence, true)); } } public function read($length) { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->readable) { throw new \RuntimeException('Cannot read from non-readable stream'); } if ($length < 0) { throw new \RuntimeException('Length parameter cannot be negative'); } if (0 === $length) { return ''; } $string = fread($this->stream, $length); if (false === $string) { throw new \RuntimeException('Unable to read from stream'); } return $string; } public function write($string) { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->writable) { throw new \RuntimeException('Cannot write to a non-writable stream'); } $this->size = null; $result = fwrite($this->stream, $string); if ($result === false) { throw new \RuntimeException('Unable to write to stream'); } return $result; } public function getMetadata($key = null) { if (!isset($this->stream)) { return $key ? null : []; } elseif (!$key) { return $this->customMetadata + stream_get_meta_data($this->stream); } elseif (isset($this->customMetadata[$key])) { return $this->customMetadata[$key]; } $meta = stream_get_meta_data($this->stream); return isset($meta[$key]) ? $meta[$key] : null; } } stream = $stream; $this->maxLength = $maxLength; } public function write($string) { $diff = $this->maxLength - $this->stream->getSize(); if ($diff <= 0) { return 0; } if (strlen($string) < $diff) { return $this->stream->write($string); } return $this->stream->write(substr($string, 0, $diff)); } } methods = $methods; foreach ($methods as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } public function __get($name) { throw new \BadMethodCallException(str_replace('_fn_', '', $name) . '() is not implemented in the FnStream'); } public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } } public function __wakeup() { throw new \LogicException('FnStream should never be unserialized'); } public static function decorate(StreamInterface $stream, array $methods) { foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { $methods[$diff] = [$stream, $diff]; } return new self($methods); } public function __toString() { return call_user_func($this->_fn___toString); } public function close() { return call_user_func($this->_fn_close); } public function detach() { return call_user_func($this->_fn_detach); } public function getSize() { return call_user_func($this->_fn_getSize); } public function tell() { return call_user_func($this->_fn_tell); } public function eof() { return call_user_func($this->_fn_eof); } public function isSeekable() { return call_user_func($this->_fn_isSeekable); } public function rewind() { call_user_func($this->_fn_rewind); } public function seek($offset, $whence = SEEK_SET) { call_user_func($this->_fn_seek, $offset, $whence); } public function isWritable() { return call_user_func($this->_fn_isWritable); } public function write($string) { return call_user_func($this->_fn_write, $string); } public function isReadable() { return call_user_func($this->_fn_isReadable); } public function read($length) { return call_user_func($this->_fn_read, $length); } public function getContents() { return call_user_func($this->_fn_getContents); } public function getMetadata($key = null) { return call_user_func($this->_fn_getMetadata, $key); } } 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', ]; private $reasonPhrase = ''; private $statusCode = 200; public function __construct( $status = 200, array $headers = [], $body = null, $version = '1.1', $reason = null ) { $this->assertStatusCodeIsInteger($status); $status = (int) $status; $this->assertStatusCodeRange($status); $this->statusCode = $status; if ($body !== '' && $body !== null) { $this->stream = Utils::streamFor($body); } $this->setHeaders($headers); if ($reason == '' && isset(self::$phrases[$this->statusCode])) { $this->reasonPhrase = self::$phrases[$this->statusCode]; } else { $this->reasonPhrase = (string) $reason; } $this->protocol = $version; } public function getStatusCode() { return $this->statusCode; } public function getReasonPhrase() { return $this->reasonPhrase; } public function withStatus($code, $reasonPhrase = '') { $this->assertStatusCodeIsInteger($code); $code = (int) $code; $this->assertStatusCodeRange($code); $new = clone $this; $new->statusCode = $code; if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { $reasonPhrase = self::$phrases[$new->statusCode]; } $new->reasonPhrase = (string) $reasonPhrase; return $new; } private function assertStatusCodeIsInteger($statusCode) { if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { throw new \InvalidArgumentException('Status code must be an integer value.'); } } private function assertStatusCodeRange($statusCode) { if ($statusCode < 100 || $statusCode >= 600) { throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); } } } filename = $filename; $this->mode = $mode; } protected function createStream() { return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); } } configureDefaults($config); } public function __call($method, $args) { if (count($args) < 1) { throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); } $uri = $args[0]; $opts = isset($args[1]) ? $args[1] : []; return substr($method, -5) === 'Async' ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) : $this->request($method, $uri, $opts); } public function sendAsync(RequestInterface $request, array $options = []) { $options = $this->prepareDefaults($options); return $this->transfer( $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), $options ); } public function send(RequestInterface $request, array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->sendAsync($request, $options)->wait(); } public function requestAsync($method, $uri = '', array $options = []) { $options = $this->prepareDefaults($options); $headers = isset($options['headers']) ? $options['headers'] : []; $body = isset($options['body']) ? $options['body'] : null; $version = isset($options['version']) ? $options['version'] : '1.1'; $uri = $this->buildUri($uri, $options); if (is_array($body)) { $this->invalidBody(); } $request = new Psr7\Request($method, $uri, $headers, $body, $version); unset($options['headers'], $options['body'], $options['version']); return $this->transfer($request, $options); } public function request($method, $uri = '', array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->requestAsync($method, $uri, $options)->wait(); } public function getConfig($option = null) { return $option === null ? $this->config : (isset($this->config[$option]) ? $this->config[$option] : null); } private function buildUri($uri, array $config) { $uri = Psr7\uri_for($uri === null ? '' : $uri); if (isset($config['base_uri'])) { $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); } if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion']; $uri = Utils::idnUriConvert($uri, $idnOptions); } return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; } private function configureDefaults(array $config) { $defaults = [ 'allow_redirects' => RedirectMiddleware::$defaultSettings, 'http_errors' => true, 'decode_content' => true, 'verify' => true, 'cookies' => false, 'idn_conversion' => true, ]; if (php_sapi_name() === 'cli' && getenv('HTTP_PROXY')) { $defaults['proxy']['http'] = getenv('HTTP_PROXY'); } if ($proxy = getenv('HTTPS_PROXY')) { $defaults['proxy']['https'] = $proxy; } if ($noProxy = getenv('NO_PROXY')) { $cleanedNoProxy = str_replace(' ', '', $noProxy); $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); } $this->config = $config + $defaults; if (!empty($config['cookies']) && $config['cookies'] === true) { $this->config['cookies'] = new CookieJar(); } if (!isset($this->config['headers'])) { $this->config['headers'] = ['User-Agent' => default_user_agent()]; } else { foreach (array_keys($this->config['headers']) as $name) { if (strtolower($name) === 'user-agent') { return; } } $this->config['headers']['User-Agent'] = default_user_agent(); } } private function prepareDefaults(array $options) { $defaults = $this->config; if (!empty($defaults['headers'])) { $defaults['_conditional'] = $defaults['headers']; unset($defaults['headers']); } if (array_key_exists('headers', $options)) { if ($options['headers'] === null) { $defaults['_conditional'] = []; unset($options['headers']); } elseif (!is_array($options['headers'])) { throw new \InvalidArgumentException('headers must be an array'); } } $result = $options + $defaults; foreach ($result as $k => $v) { if ($v === null) { unset($result[$k]); } } return $result; } private function transfer(RequestInterface $request, array $options) { if (isset($options['save_to'])) { $options['sink'] = $options['save_to']; unset($options['save_to']); } if (isset($options['exceptions'])) { $options['http_errors'] = $options['exceptions']; unset($options['exceptions']); } $request = $this->applyOptions($request, $options); $handler = $options['handler']; try { return Promise\promise_for($handler($request, $options)); } catch (\Exception $e) { return Promise\rejection_for($e); } } private function applyOptions(RequestInterface $request, array &$options) { $modify = [ 'set_headers' => [], ]; if (isset($options['headers'])) { $modify['set_headers'] = $options['headers']; unset($options['headers']); } if (isset($options['form_params'])) { if (isset($options['multipart'])) { throw new \InvalidArgumentException('You cannot use ' . 'form_params and multipart at the same time. Use the ' . 'form_params option if you want to send application/' . 'x-www-form-urlencoded requests, and the multipart ' . 'option to send multipart/form-data requests.'); } $options['body'] = http_build_query($options['form_params'], '', '&'); unset($options['form_params']); $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; } if (isset($options['multipart'])) { $options['body'] = new Psr7\MultipartStream($options['multipart']); unset($options['multipart']); } if (isset($options['json'])) { $options['body'] = \GuzzleHttp\json_encode($options['json']); unset($options['json']); $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); $options['_conditional']['Content-Type'] = 'application/json'; } if (!empty($options['decode_content']) && $options['decode_content'] !== true ) { $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']); $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; } if (isset($options['body'])) { if (is_array($options['body'])) { $this->invalidBody(); } $modify['body'] = Psr7\stream_for($options['body']); unset($options['body']); } if (!empty($options['auth']) && is_array($options['auth'])) { $value = $options['auth']; $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; switch ($type) { case 'basic': $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']); $modify['set_headers']['Authorization'] = 'Basic ' . base64_encode("$value[0]:$value[1]"); break; case 'digest': $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; break; case 'ntlm': $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; break; } } if (isset($options['query'])) { $value = $options['query']; if (is_array($value)) { $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); } if (!is_string($value)) { throw new \InvalidArgumentException('query must be a string or array'); } $modify['query'] = $value; unset($options['query']); } if (isset($options['sink'])) { if (is_bool($options['sink'])) { throw new \InvalidArgumentException('sink must not be a boolean'); } } $request = Psr7\modify_request($request, $modify); if ($request->getBody() instanceof Psr7\MultipartStream) { $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' . $request->getBody()->getBoundary(); } if (isset($options['_conditional'])) { $modify = []; foreach ($options['_conditional'] as $k => $v) { if (!$request->hasHeader($k)) { $modify['set_headers'][$k] = $v; } } $request = Psr7\modify_request($request, $modify); unset($options['_conditional']); } return $request; } private function invalidBody() { throw new \InvalidArgumentException('Passing in the "body" request ' . 'option as an array to send a POST request has been deprecated. ' . 'Please use the "form_params" request option to send a ' . 'application/x-www-form-urlencoded request, or the "multipart" ' . 'request option to send a multipart/form-data request.'); } } >>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; private $template; public function __construct($template = self::CLF) { $this->template = $template ?: self::CLF; } public function format( RequestInterface $request, ResponseInterface $response = null, \Exception $error = null ) { $cache = []; return preg_replace_callback( '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', function (array $matches) use ($request, $response, $error, &$cache) { if (isset($cache[$matches[1]])) { return $cache[$matches[1]]; } $result = ''; switch ($matches[1]) { case 'request': $result = Psr7\str($request); break; case 'response': $result = $response ? Psr7\str($response) : ''; break; case 'req_headers': $result = trim($request->getMethod() . ' ' . $request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . "\r\n" . $this->headers($request); break; case 'res_headers': $result = $response ? sprintf( 'HTTP/%s %d %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase() ) . "\r\n" . $this->headers($response) : 'NULL'; break; case 'req_body': $result = $request->getBody(); break; case 'res_body': $result = $response ? $response->getBody() : 'NULL'; break; case 'ts': case 'date_iso_8601': $result = gmdate('c'); break; case 'date_common_log': $result = date('d/M/Y:H:i:s O'); break; case 'method': $result = $request->getMethod(); break; case 'version': $result = $request->getProtocolVersion(); break; case 'uri': case 'url': $result = $request->getUri(); break; case 'target': $result = $request->getRequestTarget(); break; case 'req_version': $result = $request->getProtocolVersion(); break; case 'res_version': $result = $response ? $response->getProtocolVersion() : 'NULL'; break; case 'host': $result = $request->getHeaderLine('Host'); break; case 'hostname': $result = gethostname(); break; case 'code': $result = $response ? $response->getStatusCode() : 'NULL'; break; case 'phrase': $result = $response ? $response->getReasonPhrase() : 'NULL'; break; case 'error': $result = $error ? $error->getMessage() : 'NULL'; break; default: if (strpos($matches[1], 'req_header_') === 0) { $result = $request->getHeaderLine(substr($matches[1], 11)); } elseif (strpos($matches[1], 'res_header_') === 0) { $result = $response ? $response->getHeaderLine(substr($matches[1], 11)) : 'NULL'; } } $cache[$matches[1]] = $result; return $result; }, $this->template ); } private function headers(MessageInterface $message) { $result = ''; foreach ($message->getHeaders() as $name => $values) { $result .= $name . ': ' . implode(', ', $values) . "\r\n"; } return trim($result); } } ['prefix' => '', 'joiner' => ',', 'query' => false], '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] ]; private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=']; private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D']; public function expand($template, array $variables) { if (false === strpos($template, '{')) { return $template; } $this->template = $template; $this->variables = $variables; return preg_replace_callback( '/\{([^\}]+)\}/', [$this, 'expandMatch'], $this->template ); } private function parseExpression($expression) { $result = []; if (isset(self::$operatorHash[$expression[0]])) { $result['operator'] = $expression[0]; $expression = substr($expression, 1); } else { $result['operator'] = ''; } foreach (explode(',', $expression) as $value) { $value = trim($value); $varspec = []; if ($colonPos = strpos($value, ':')) { $varspec['value'] = substr($value, 0, $colonPos); $varspec['modifier'] = ':'; $varspec['position'] = (int) substr($value, $colonPos + 1); } elseif (substr($value, -1) === '*') { $varspec['modifier'] = '*'; $varspec['value'] = substr($value, 0, -1); } else { $varspec['value'] = (string) $value; $varspec['modifier'] = ''; } $result['values'][] = $varspec; } return $result; } private function expandMatch(array $matches) { static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; $replacements = []; $parsed = self::parseExpression($matches[1]); $prefix = self::$operatorHash[$parsed['operator']]['prefix']; $joiner = self::$operatorHash[$parsed['operator']]['joiner']; $useQuery = self::$operatorHash[$parsed['operator']]['query']; foreach ($parsed['values'] as $value) { if (!isset($this->variables[$value['value']])) { continue; } $variable = $this->variables[$value['value']]; $actuallyUseQuery = $useQuery; $expanded = ''; if (is_array($variable)) { $isAssoc = $this->isAssoc($variable); $kvp = []; foreach ($variable as $key => $var) { if ($isAssoc) { $key = rawurlencode($key); $isNestedArray = is_array($var); } else { $isNestedArray = false; } if (!$isNestedArray) { $var = rawurlencode($var); if ($parsed['operator'] === '+' || $parsed['operator'] === '#' ) { $var = $this->decodeReserved($var); } } if ($value['modifier'] === '*') { if ($isAssoc) { if ($isNestedArray) { $var = strtr( http_build_query([$key => $var]), $rfc1738to3986 ); } else { $var = $key . '=' . $var; } } elseif ($key > 0 && $actuallyUseQuery) { $var = $value['value'] . '=' . $var; } } $kvp[$key] = $var; } if (empty($variable)) { $actuallyUseQuery = false; } elseif ($value['modifier'] === '*') { $expanded = implode($joiner, $kvp); if ($isAssoc) { $actuallyUseQuery = false; } } else { if ($isAssoc) { foreach ($kvp as $k => &$v) { $v = $k . ',' . $v; } } $expanded = implode(',', $kvp); } } else { if ($value['modifier'] === ':') { $variable = substr($variable, 0, $value['position']); } $expanded = rawurlencode($variable); if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { $expanded = $this->decodeReserved($expanded); } } if ($actuallyUseQuery) { if (!$expanded && $joiner !== '&') { $expanded = $value['value']; } else { $expanded = $value['value'] . '=' . $expanded; } } $replacements[] = $expanded; } $ret = implode($joiner, $replacements); if ($ret && $prefix) { return $prefix . $ret; } return $ret; } private function isAssoc(array $array) { return $array && array_keys($array)[0] !== 0; } private function decodeReserved($string) { return str_replace(self::$delimsPct, self::$delims, $string); } } getHost()) { $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); if ($asciiHost === false) { $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { return substr($name, 0, 11) === 'IDNA_ERROR_'; }); $errors = []; foreach ($errorConstants as $errorConstant) { if ($errorBitSet & constant($errorConstant)) { $errors[] = $errorConstant; } } $errorMessage = 'IDN conversion failed'; if ($errors) { $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; } throw new InvalidArgumentException($errorMessage); } else { if ($uri->getHost() !== $asciiHost) { $uri = $uri->withHost($asciiHost); } } } return $uri; } private static function idnToAsci($domain, $options, &$info = []) { if (\preg_match('%^[ -~]+$%', $domain) === 1) { return $domain; } if (\extension_loaded('intl') && defined('INTL_IDNA_VARIANT_UTS46')) { return \idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $info); } if (method_exists(Idn::class, 'idn_to_ascii')) { return Idn::idn_to_ascii($domain, $options, Idn::INTL_IDNA_VARIANT_UTS46, $info); } throw new \RuntimeException('ext-intl or symfony/polyfill-intl-idn not loaded or too old'); } } push(Middleware::httpErrors(), 'http_errors'); $stack->push(Middleware::redirect(), 'allow_redirects'); $stack->push(Middleware::cookies(), 'cookies'); $stack->push(Middleware::prepareBody(), 'prepare_body'); return $stack; } public function __construct(callable $handler = null) { $this->handler = $handler; } public function __invoke(RequestInterface $request, array $options) { $handler = $this->resolve(); return $handler($request, $options); } public function __toString() { $depth = 0; $stack = []; if ($this->handler) { $stack[] = "0) Handler: " . $this->debugCallable($this->handler); } $result = ''; foreach (array_reverse($this->stack) as $tuple) { $depth++; $str = "{$depth}) Name: '{$tuple[1]}', "; $str .= "Function: " . $this->debugCallable($tuple[0]); $result = "> {$str}\n{$result}"; $stack[] = $str; } foreach (array_keys($stack) as $k) { $result .= "< {$stack[$k]}\n"; } return $result; } public function setHandler(callable $handler) { $this->handler = $handler; $this->cached = null; } public function hasHandler() { return (bool) $this->handler; } public function unshift(callable $middleware, $name = null) { array_unshift($this->stack, [$middleware, $name]); $this->cached = null; } public function push(callable $middleware, $name = '') { $this->stack[] = [$middleware, $name]; $this->cached = null; } public function before($findName, callable $middleware, $withName = '') { $this->splice($findName, $withName, $middleware, true); } public function after($findName, callable $middleware, $withName = '') { $this->splice($findName, $withName, $middleware, false); } public function remove($remove) { $this->cached = null; $idx = is_callable($remove) ? 0 : 1; $this->stack = array_values(array_filter( $this->stack, function ($tuple) use ($idx, $remove) { return $tuple[$idx] !== $remove; } )); } public function resolve() { if (!$this->cached) { if (!($prev = $this->handler)) { throw new \LogicException('No handler has been specified'); } foreach (array_reverse($this->stack) as $fn) { $prev = $fn[0]($prev); } $this->cached = $prev; } return $this->cached; } private function findByName($name) { foreach ($this->stack as $k => $v) { if ($v[1] === $name) { return $k; } } throw new \InvalidArgumentException("Middleware not found: $name"); } private function splice($findName, $withName, callable $middleware, $before) { $this->cached = null; $idx = $this->findByName($findName); $tuple = [$middleware, $withName]; if ($before) { if ($idx === 0) { array_unshift($this->stack, $tuple); } else { $replacement = [$tuple, $this->stack[$idx]]; array_splice($this->stack, $idx, 1, $replacement); } } elseif ($idx === count($this->stack) - 1) { $this->stack[] = $tuple; } else { $replacement = [$this->stack[$idx], $tuple]; array_splice($this->stack, $idx, 1, $replacement); } } private function debugCallable($fn) { if (is_string($fn)) { return "callable({$fn})"; } if (is_array($fn)) { return is_string($fn[0]) ? "callable({$fn[0]}::{$fn[1]})" : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; } return 'callable(' . spl_object_hash($fn) . ')'; } } null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => false, 'Discard' => false, 'HttpOnly' => false ]; private $data; public static function fromString($cookie) { $data = self::$defaults; $pieces = array_filter(array_map('trim', explode(';', $cookie))); if (empty($pieces[0]) || !strpos($pieces[0], '=')) { return new self($data); } foreach ($pieces as $part) { $cookieParts = explode('=', $part, 2); $key = trim($cookieParts[0]); $value = isset($cookieParts[1]) ? trim($cookieParts[1], " \n\r\t\0\x0B") : true; if (empty($data['Name'])) { $data['Name'] = $key; $data['Value'] = $value; } else { foreach (array_keys(self::$defaults) as $search) { if (!strcasecmp($search, $key)) { $data[$search] = $value; continue 2; } } $data[$key] = $value; } } return new self($data); } public function __construct(array $data = []) { $this->data = array_replace(self::$defaults, $data); if (!$this->getExpires() && $this->getMaxAge()) { $this->setExpires(time() + $this->getMaxAge()); } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { $this->setExpires($this->getExpires()); } } public function __toString() { $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; foreach ($this->data as $k => $v) { if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { if ($k === 'Expires') { $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; } else { $str .= ($v === true ? $k : "{$k}={$v}") . '; '; } } } return rtrim($str, '; '); } public function toArray() { return $this->data; } public function getName() { return $this->data['Name']; } public function setName($name) { $this->data['Name'] = $name; } public function getValue() { return $this->data['Value']; } public function setValue($value) { $this->data['Value'] = $value; } public function getDomain() { return $this->data['Domain']; } public function setDomain($domain) { $this->data['Domain'] = $domain; } public function getPath() { return $this->data['Path']; } public function setPath($path) { $this->data['Path'] = $path; } public function getMaxAge() { return $this->data['Max-Age']; } public function setMaxAge($maxAge) { $this->data['Max-Age'] = $maxAge; } public function getExpires() { return $this->data['Expires']; } public function setExpires($timestamp) { $this->data['Expires'] = is_numeric($timestamp) ? (int) $timestamp : strtotime($timestamp); } public function getSecure() { return $this->data['Secure']; } public function setSecure($secure) { $this->data['Secure'] = $secure; } public function getDiscard() { return $this->data['Discard']; } public function setDiscard($discard) { $this->data['Discard'] = $discard; } public function getHttpOnly() { return $this->data['HttpOnly']; } public function setHttpOnly($httpOnly) { $this->data['HttpOnly'] = $httpOnly; } public function matchesPath($requestPath) { $cookiePath = $this->getPath(); if ($cookiePath === '/' || $cookiePath == $requestPath) { return true; } if (0 !== strpos($requestPath, $cookiePath)) { return false; } if (substr($cookiePath, -1, 1) === '/') { return true; } return substr($requestPath, strlen($cookiePath), 1) === '/'; } public function matchesDomain($domain) { $cookieDomain = $this->getDomain(); if (null === $cookieDomain) { return true; } $cookieDomain = ltrim(strtolower($cookieDomain), '.'); $domain = strtolower($domain); if ('' === $cookieDomain || $domain === $cookieDomain) { return true; } if (filter_var($domain, FILTER_VALIDATE_IP)) { return false; } return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain); } public function isExpired() { return $this->getExpires() !== null && time() > $this->getExpires(); } public function validate() { $name = $this->getName(); if (empty($name) && !is_numeric($name)) { return 'The cookie name must not be empty'; } if (preg_match( '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', $name )) { return 'Cookie name must not contain invalid characters: ASCII ' . 'Control characters (0-31;127), space, tab and the ' . 'following characters: ()<>@,;:\"/?={}'; } $value = $this->getValue(); if (empty($value) && !is_numeric($value)) { return 'The cookie value must not be empty'; } $domain = $this->getDomain(); if (empty($domain) && !is_numeric($domain)) { return 'The cookie domain must not be empty'; } return true; } } strictMode = $strictMode; foreach ($cookieArray as $cookie) { if (!($cookie instanceof SetCookie)) { $cookie = new SetCookie($cookie); } $this->setCookie($cookie); } } public static function fromArray(array $cookies, $domain) { $cookieJar = new self(); foreach ($cookies as $name => $value) { $cookieJar->setCookie(new SetCookie([ 'Domain' => $domain, 'Name' => $name, 'Value' => $value, 'Discard' => true ])); } return $cookieJar; } public static function getCookieValue($value) { return $value; } public static function shouldPersist( SetCookie $cookie, $allowSessionCookies = false ) { if ($cookie->getExpires() || $allowSessionCookies) { if (!$cookie->getDiscard()) { return true; } } return false; } public function getCookieByName($name) { if ($name === null || !is_scalar($name)) { return null; } foreach ($this->cookies as $cookie) { if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { return $cookie; } } return null; } public function toArray() { return array_map(function (SetCookie $cookie) { return $cookie->toArray(); }, $this->getIterator()->getArrayCopy()); } public function clear($domain = null, $path = null, $name = null) { if (!$domain) { $this->cookies = []; return; } elseif (!$path) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($domain) { return !$cookie->matchesDomain($domain); } ); } elseif (!$name) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain) { return !($cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } else { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain, $name) { return !($cookie->getName() == $name && $cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } } public function clearSessionCookies() { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) { return !$cookie->getDiscard() && $cookie->getExpires(); } ); } public function setCookie(SetCookie $cookie) { $name = $cookie->getName(); if (!$name && $name !== '0') { return false; } $result = $cookie->validate(); if ($result !== true) { if ($this->strictMode) { throw new \RuntimeException('Invalid cookie: ' . $result); } else { $this->removeCookieIfEmpty($cookie); return false; } } foreach ($this->cookies as $i => $c) { if ($c->getPath() != $cookie->getPath() || $c->getDomain() != $cookie->getDomain() || $c->getName() != $cookie->getName() ) { continue; } if (!$cookie->getDiscard() && $c->getDiscard()) { unset($this->cookies[$i]); continue; } if ($cookie->getExpires() > $c->getExpires()) { unset($this->cookies[$i]); continue; } if ($cookie->getValue() !== $c->getValue()) { unset($this->cookies[$i]); continue; } return false; } $this->cookies[] = $cookie; return true; } public function count() { return count($this->cookies); } public function getIterator() { return new \ArrayIterator(array_values($this->cookies)); } public function extractCookies( RequestInterface $request, ResponseInterface $response ) { if ($cookieHeader = $response->getHeader('Set-Cookie')) { foreach ($cookieHeader as $cookie) { $sc = SetCookie::fromString($cookie); if (!$sc->getDomain()) { $sc->setDomain($request->getUri()->getHost()); } if (0 !== strpos($sc->getPath(), '/')) { $sc->setPath($this->getCookiePathFromRequest($request)); } if (!$sc->matchesDomain($request->getUri()->getHost())) { continue; } $this->setCookie($sc); } } } private function getCookiePathFromRequest(RequestInterface $request) { $uriPath = $request->getUri()->getPath(); if ('' === $uriPath) { return '/'; } if (0 !== strpos($uriPath, '/')) { return '/'; } if ('/' === $uriPath) { return '/'; } if (0 === $lastSlashPos = strrpos($uriPath, '/')) { return '/'; } return substr($uriPath, 0, $lastSlashPos); } public function withCookieHeader(RequestInterface $request) { $values = []; $uri = $request->getUri(); $scheme = $uri->getScheme(); $host = $uri->getHost(); $path = $uri->getPath() ?: '/'; foreach ($this->cookies as $cookie) { if ($cookie->matchesPath($path) && $cookie->matchesDomain($host) && !$cookie->isExpired() && (!$cookie->getSecure() || $scheme === 'https') ) { $values[] = $cookie->getName() . '=' . $cookie->getValue(); } } return $values ? $request->withHeader('Cookie', implode('; ', $values)) : $request; } private function removeCookieIfEmpty(SetCookie $cookie) { $cookieValue = $cookie->getValue(); if ($cookieValue === null || $cookieValue === '') { $this->clear( $cookie->getDomain(), $cookie->getPath(), $cookie->getName() ); } } } sessionKey = $sessionKey; $this->storeSessionCookies = $storeSessionCookies; $this->load(); } public function __destruct() { $this->save(); } public function save() { $json = []; foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray(); } } $_SESSION[$this->sessionKey] = json_encode($json); } protected function load() { if (!isset($_SESSION[$this->sessionKey])) { return; } $data = json_decode($_SESSION[$this->sessionKey], true); if (is_array($data)) { foreach ($data as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie data"); } } } filename = $cookieFile; $this->storeSessionCookies = $storeSessionCookies; if (file_exists($cookieFile)) { $this->load($cookieFile); } } public function __destruct() { $this->save($this->filename); } public function save($filename) { $json = []; foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray(); } } $jsonStr = \GuzzleHttp\json_encode($json); if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) { throw new \RuntimeException("Unable to save file {$filename}"); } } public function load($filename) { $json = file_get_contents($filename); if (false === $json) { throw new \RuntimeException("Unable to load file {$filename}"); } elseif ($json === '') { return; } $data = \GuzzleHttp\json_decode($json, true); if (is_array($data)) { foreach (json_decode($json, true) as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie file: {$filename}"); } } } withCookieHeader($request); return $handler($request, $options) ->then( function ($response) use ($cookieJar, $request) { $cookieJar->extractCookies($request, $response); return $response; } ); }; }; } public static function httpErrors() { return function (callable $handler) { return function ($request, array $options) use ($handler) { if (empty($options['http_errors'])) { return $handler($request, $options); } return $handler($request, $options)->then( function (ResponseInterface $response) use ($request) { $code = $response->getStatusCode(); if ($code < 400) { return $response; } throw RequestException::create($request, $response); } ); }; }; } public static function history(&$container) { if (!is_array($container) && !$container instanceof \ArrayAccess) { throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); } return function (callable $handler) use (&$container) { return function ($request, array $options) use ($handler, &$container) { return $handler($request, $options)->then( function ($value) use ($request, &$container, $options) { $container[] = [ 'request' => $request, 'response' => $value, 'error' => null, 'options' => $options ]; return $value; }, function ($reason) use ($request, &$container, $options) { $container[] = [ 'request' => $request, 'response' => null, 'error' => $reason, 'options' => $options ]; return \GuzzleHttp\Promise\rejection_for($reason); } ); }; }; } public static function tap(callable $before = null, callable $after = null) { return function (callable $handler) use ($before, $after) { return function ($request, array $options) use ($handler, $before, $after) { if ($before) { $before($request, $options); } $response = $handler($request, $options); if ($after) { $after($request, $options, $response); } return $response; }; }; } public static function redirect() { return function (callable $handler) { return new RedirectMiddleware($handler); }; } public static function retry(callable $decider, callable $delay = null) { return function (callable $handler) use ($decider, $delay) { return new RetryMiddleware($decider, $handler, $delay); }; } public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' ) { return function (callable $handler) use ($logger, $formatter, $logLevel) { return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { return $handler($request, $options)->then( function ($response) use ($logger, $request, $formatter, $logLevel) { $message = $formatter->format($request, $response); $logger->log($logLevel, $message); return $response; }, function ($reason) use ($logger, $request, $formatter) { $response = $reason instanceof RequestException ? $reason->getResponse() : null; $message = $formatter->format($request, $response, $reason); $logger->notice($message); return \GuzzleHttp\Promise\rejection_for($reason); } ); }; }; } public static function prepareBody() { return function (callable $handler) { return new PrepareBodyMiddleware($handler); }; } public static function mapRequest(callable $fn) { return function (callable $handler) use ($fn) { return function ($request, array $options) use ($handler, $fn) { return $handler($fn($request), $options); }; }; } public static function mapResponse(callable $fn) { return function (callable $handler) use ($fn) { return function ($request, array $options) use ($handler, $fn) { return $handler($request, $options)->then($fn); }; }; } } $rfn) { if ($rfn instanceof RequestInterface) { yield $key => $client->sendAsync($rfn, $opts); } elseif (is_callable($rfn)) { yield $key => $rfn($opts); } else { throw new \InvalidArgumentException('Each value yielded by ' . 'the iterator must be a Psr7\Http\Message\RequestInterface ' . 'or a callable that returns a promise that fulfills ' . 'with a Psr7\Message\Http\ResponseInterface object.'); } } }; $this->each = new EachPromise($requests(), $config); } public function promise() { return $this->each->promise(); } public static function batch( ClientInterface $client, $requests, array $options = [] ) { $res = []; self::cmpCallback($options, 'fulfilled', $res); self::cmpCallback($options, 'rejected', $res); $pool = new static($client, $requests, $options); $pool->promise()->wait(); ksort($res); return $res; } private static function cmpCallback(array &$options, $name, array &$results) { if (!isset($options[$name])) { $options[$name] = function ($v, $k) use (&$results) { $results[$k] = $v; }; } else { $currentFn = $options[$name]; $options[$name] = function ($v, $k) use (&$results, $currentFn) { $currentFn($v, $k); $results[$k] = $v; }; } } } decider = $decider; $this->nextHandler = $nextHandler; $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; } public static function exponentialDelay($retries) { return (int) pow(2, $retries - 1) * 1000; } public function __invoke(RequestInterface $request, array $options) { if (!isset($options['retries'])) { $options['retries'] = 0; } $fn = $this->nextHandler; return $fn($request, $options) ->then( $this->onFulfilled($request, $options), $this->onRejected($request, $options) ); } private function onFulfilled(RequestInterface $req, array $options) { return function ($value) use ($req, $options) { if (!call_user_func( $this->decider, $options['retries'], $req, $value, null )) { return $value; } return $this->doRetry($req, $options, $value); }; } private function onRejected(RequestInterface $req, array $options) { return function ($reason) use ($req, $options) { if (!call_user_func( $this->decider, $options['retries'], $req, null, $reason )) { return \GuzzleHttp\Promise\rejection_for($reason); } return $this->doRetry($req, $options); }; } private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) { $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); return $this($request, $options); } } request = $request; $this->response = $response; $this->transferTime = $transferTime; $this->handlerErrorData = $handlerErrorData; $this->handlerStats = $handlerStats; } public function getRequest() { return $this->request; } public function getResponse() { return $this->response; } public function hasResponse() { return $this->response !== null; } public function getHandlerErrorData() { return $this->handlerErrorData; } public function getEffectiveUri() { return $this->request->getUri(); } public function getTransferTime() { return $this->transferTime; } public function getHandlerStats() { return $this->handlerStats; } public function getHandlerStat($stat) { return isset($this->handlerStats[$stat]) ? $this->handlerStats[$stat] : null; } } headers)) { throw new \RuntimeException('No headers have been received'); } $startLine = explode(' ', array_shift($this->headers), 3); $headers = \GuzzleHttp\headers_from_lines($this->headers); $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding']) ) { $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; unset($headers[$normalizedKeys['content-encoding']]); if (isset($normalizedKeys['content-length'])) { $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; $bodyLength = (int) $this->sink->getSize(); if ($bodyLength) { $headers[$normalizedKeys['content-length']] = $bodyLength; } else { unset($headers[$normalizedKeys['content-length']]); } } } $this->response = new Response( $startLine[1], $headers, $this->sink, substr($startLine[0], 5), isset($startLine[2]) ? (string) $startLine[2] : null ); } public function __get($name) { $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; throw new \BadMethodCallException($msg); } } factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(3); } public function __invoke(RequestInterface $request, array $options) { if (isset($options['delay'])) { usleep($options['delay'] * 1000); } $easy = $this->factory->create($request, $options); curl_exec($easy->handle); $easy->errno = curl_errno($easy->handle); return CurlFactory::finish($this, $easy, $this->factory); } } maxHandles = $maxHandles; } public function create(RequestInterface $request, array $options) { if (isset($options['curl']['body_as_string'])) { $options['_body_as_string'] = $options['curl']['body_as_string']; unset($options['curl']['body_as_string']); } $easy = new EasyHandle; $easy->request = $request; $easy->options = $options; $conf = $this->getDefaultConf($easy); $this->applyMethod($easy, $conf); $this->applyHandlerOptions($easy, $conf); $this->applyHeaders($easy, $conf); unset($conf['_headers']); if (isset($options['curl'])) { $conf = array_replace($conf, $options['curl']); } $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); $easy->handle = $this->handles ? array_pop($this->handles) : curl_init(); curl_setopt_array($easy->handle, $conf); return $easy; } public function release(EasyHandle $easy) { $resource = $easy->handle; unset($easy->handle); if (count($this->handles) >= $this->maxHandles) { curl_close($resource); } else { curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); curl_setopt($resource, CURLOPT_READFUNCTION, null); curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); curl_reset($resource); $this->handles[] = $resource; } } public static function finish( callable $handler, EasyHandle $easy, CurlFactoryInterface $factory ) { if (isset($easy->options['on_stats'])) { self::invokeStats($easy); } if (!$easy->response || $easy->errno) { return self::finishError($handler, $easy, $factory); } $factory->release($easy); $body = $easy->response->getBody(); if ($body->isSeekable()) { $body->rewind(); } return new FulfilledPromise($easy->response); } private static function invokeStats(EasyHandle $easy) { $curlStats = curl_getinfo($easy->handle); $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME); $stats = new TransferStats( $easy->request, $easy->response, $curlStats['total_time'], $easy->errno, $curlStats ); call_user_func($easy->options['on_stats'], $stats); } private static function finishError( callable $handler, EasyHandle $easy, CurlFactoryInterface $factory ) { $ctx = [ 'errno' => $easy->errno, 'error' => curl_error($easy->handle), 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME), ] + curl_getinfo($easy->handle); $ctx[self::CURL_VERSION_STR] = curl_version()['version']; $factory->release($easy); if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65) ) { return self::retryFailedRewind($handler, $easy, $ctx); } return self::createRejection($easy, $ctx); } private static function createRejection(EasyHandle $easy, array $ctx) { static $connectionErrors = [ CURLE_OPERATION_TIMEOUTED => true, CURLE_COULDNT_RESOLVE_HOST => true, CURLE_COULDNT_CONNECT => true, CURLE_SSL_CONNECT_ERROR => true, CURLE_GOT_NOTHING => true, ]; if ($easy->onHeadersException) { return \GuzzleHttp\Promise\rejection_for( new RequestException( 'An error was encountered during the on_headers event', $easy->request, $easy->response, $easy->onHeadersException, $ctx ) ); } if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) { $message = sprintf( 'cURL error %s: %s (%s)', $ctx['errno'], $ctx['error'], 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' ); } else { $message = sprintf( 'cURL error %s: %s (%s) for %s', $ctx['errno'], $ctx['error'], 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', $easy->request->getUri() ); } $error = isset($connectionErrors[$easy->errno]) ? new ConnectException($message, $easy->request, null, $ctx) : new RequestException($message, $easy->request, $easy->response, null, $ctx); return \GuzzleHttp\Promise\rejection_for($error); } private function getDefaultConf(EasyHandle $easy) { $conf = [ '_headers' => $easy->request->getHeaders(), CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), CURLOPT_RETURNTRANSFER => false, CURLOPT_HEADER => false, CURLOPT_CONNECTTIMEOUT => 150, ]; if (defined('CURLOPT_PROTOCOLS')) { $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $version = $easy->request->getProtocolVersion(); if ($version == 1.1) { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; } elseif ($version == 2.0) { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; } else { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } return $conf; } private function applyMethod(EasyHandle $easy, array &$conf) { $body = $easy->request->getBody(); $size = $body->getSize(); if ($size === null || $size > 0) { $this->applyBody($easy->request, $easy->options, $conf); return; } $method = $easy->request->getMethod(); if ($method === 'PUT' || $method === 'POST') { if (!$easy->request->hasHeader('Content-Length')) { $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; } } elseif ($method === 'HEAD') { $conf[CURLOPT_NOBODY] = true; unset( $conf[CURLOPT_WRITEFUNCTION], $conf[CURLOPT_READFUNCTION], $conf[CURLOPT_FILE], $conf[CURLOPT_INFILE] ); } } private function applyBody(RequestInterface $request, array $options, array &$conf) { $size = $request->hasHeader('Content-Length') ? (int) $request->getHeaderLine('Content-Length') : null; if (($size !== null && $size < 1000000) || !empty($options['_body_as_string']) ) { $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); $this->removeHeader('Content-Length', $conf); $this->removeHeader('Transfer-Encoding', $conf); } else { $conf[CURLOPT_UPLOAD] = true; if ($size !== null) { $conf[CURLOPT_INFILESIZE] = $size; $this->removeHeader('Content-Length', $conf); } $body = $request->getBody(); if ($body->isSeekable()) { $body->rewind(); } $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { return $body->read($length); }; } if (!$request->hasHeader('Expect')) { $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; } if (!$request->hasHeader('Content-Type')) { $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; } } private function applyHeaders(EasyHandle $easy, array &$conf) { foreach ($conf['_headers'] as $name => $values) { foreach ($values as $value) { $value = (string) $value; if ($value === '') { $conf[CURLOPT_HTTPHEADER][] = "$name;"; } else { $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; } } } if (!$easy->request->hasHeader('Accept')) { $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; } } private function removeHeader($name, array &$options) { foreach (array_keys($options['_headers']) as $key) { if (!strcasecmp($key, $name)) { unset($options['_headers'][$key]); return; } } } private function applyHandlerOptions(EasyHandle $easy, array &$conf) { $options = $easy->options; if (isset($options['verify'])) { if ($options['verify'] === false) { unset($conf[CURLOPT_CAINFO]); $conf[CURLOPT_SSL_VERIFYHOST] = 0; $conf[CURLOPT_SSL_VERIFYPEER] = false; } else { $conf[CURLOPT_SSL_VERIFYHOST] = 2; $conf[CURLOPT_SSL_VERIFYPEER] = true; if (is_string($options['verify'])) { if (!file_exists($options['verify'])) { throw new \InvalidArgumentException( "SSL CA bundle not found: {$options['verify']}" ); } if (is_dir($options['verify']) || (is_link($options['verify']) && is_dir(readlink($options['verify'])))) { $conf[CURLOPT_CAPATH] = $options['verify']; } else { $conf[CURLOPT_CAINFO] = $options['verify']; } } } } if (!empty($options['decode_content'])) { $accept = $easy->request->getHeaderLine('Accept-Encoding'); if ($accept) { $conf[CURLOPT_ENCODING] = $accept; } else { $conf[CURLOPT_ENCODING] = ''; $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; } } if (isset($options['sink'])) { $sink = $options['sink']; if (!is_string($sink)) { $sink = \GuzzleHttp\Psr7\stream_for($sink); } elseif (!is_dir(dirname($sink))) { throw new \RuntimeException(sprintf( 'Directory %s does not exist for sink value of %s', dirname($sink), $sink )); } else { $sink = new LazyOpenStream($sink, 'w+'); } $easy->sink = $sink; $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { return $sink->write($write); }; } else { $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); } $timeoutRequiresNoSignal = false; if (isset($options['timeout'])) { $timeoutRequiresNoSignal |= $options['timeout'] < 1; $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; } if (isset($options['force_ip_resolve'])) { if ('v4' === $options['force_ip_resolve']) { $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; } elseif ('v6' === $options['force_ip_resolve']) { $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6; } } if (isset($options['connect_timeout'])) { $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; } if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { $conf[CURLOPT_NOSIGNAL] = true; } if (isset($options['proxy'])) { if (!is_array($options['proxy'])) { $conf[CURLOPT_PROXY] = $options['proxy']; } else { $scheme = $easy->request->getUri()->getScheme(); if (isset($options['proxy'][$scheme])) { $host = $easy->request->getUri()->getHost(); if (!isset($options['proxy']['no']) || !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) ) { $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; } } } } if (isset($options['cert'])) { $cert = $options['cert']; if (is_array($cert)) { $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; $cert = $cert[0]; } if (!file_exists($cert)) { throw new \InvalidArgumentException( "SSL certificate not found: {$cert}" ); } $conf[CURLOPT_SSLCERT] = $cert; } if (isset($options['ssl_key'])) { if (is_array($options['ssl_key'])) { if (count($options['ssl_key']) === 2) { list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key']; } else { list($sslKey) = $options['ssl_key']; } } $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key']; if (!file_exists($sslKey)) { throw new \InvalidArgumentException( "SSL private key not found: {$sslKey}" ); } $conf[CURLOPT_SSLKEY] = $sslKey; } if (isset($options['progress'])) { $progress = $options['progress']; if (!is_callable($progress)) { throw new \InvalidArgumentException( 'progress client option must be callable' ); } $conf[CURLOPT_NOPROGRESS] = false; $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { $args = func_get_args(); if (is_resource($args[0])) { array_shift($args); } call_user_func_array($progress, $args); }; } if (!empty($options['debug'])) { $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); $conf[CURLOPT_VERBOSE] = true; } } private static function retryFailedRewind( callable $handler, EasyHandle $easy, array $ctx ) { try { $body = $easy->request->getBody(); if ($body->tell() > 0) { $body->rewind(); } } catch (\RuntimeException $e) { $ctx['error'] = 'The connection unexpectedly failed without ' . 'providing an error. The request would have been retried, ' . 'but attempting to rewind the request body failed. ' . 'Exception: ' . $e; return self::createRejection($easy, $ctx); } if (!isset($easy->options['_curl_retries'])) { $easy->options['_curl_retries'] = 1; } elseif ($easy->options['_curl_retries'] == 2) { $ctx['error'] = 'The cURL request was retried 3 times ' . 'and did not succeed. The most likely reason for the failure ' . 'is that cURL was unable to rewind the body of the request ' . 'and subsequent retries resulted in the same error. Turn on ' . 'the debug option to see what went wrong. See ' . 'https://bugs.php.net/bug.php?id=47204 for more information.'; return self::createRejection($easy, $ctx); } else { $easy->options['_curl_retries']++; } return $handler($easy->request, $easy->options); } private function createHeaderFn(EasyHandle $easy) { if (isset($easy->options['on_headers'])) { $onHeaders = $easy->options['on_headers']; if (!is_callable($onHeaders)) { throw new \InvalidArgumentException('on_headers must be callable'); } } else { $onHeaders = null; } return function ($ch, $h) use ( $onHeaders, $easy, &$startingResponse ) { $value = trim($h); if ($value === '') { $startingResponse = true; $easy->createResponse(); if ($onHeaders !== null) { try { $onHeaders($easy->response); } catch (\Exception $e) { $easy->onHeadersException = $e; return -1; } } } elseif ($startingResponse) { $startingResponse = false; $easy->headers = [$value]; } else { $easy->headers[] = $value; } return strlen($h); }; } } withoutHeader('Expect'); if (0 === $request->getBody()->getSize()) { $request = $request->withHeader('Content-Length', '0'); } return $this->createResponse( $request, $options, $this->createStream($request, $options), $startTime ); } catch (\InvalidArgumentException $e) { throw $e; } catch (\Exception $e) { $message = $e->getMessage(); if (strpos($message, 'getaddrinfo') || strpos($message, 'Connection refused') || strpos($message, "couldn't connect to host") || strpos($message, "connection attempt failed") ) { $e = new ConnectException($e->getMessage(), $request, $e); } $e = RequestException::wrapException($request, $e); $this->invokeStats($options, $request, $startTime, null, $e); return \GuzzleHttp\Promise\rejection_for($e); } } private function invokeStats( array $options, RequestInterface $request, $startTime, ResponseInterface $response = null, $error = null ) { if (isset($options['on_stats'])) { $stats = new TransferStats( $request, $response, Utils::currentTime() - $startTime, $error, [] ); call_user_func($options['on_stats'], $stats); } } private function createResponse( RequestInterface $request, array $options, $stream, $startTime ) { $hdrs = $this->lastHeaders; $this->lastHeaders = []; $parts = explode(' ', array_shift($hdrs), 3); $ver = explode('/', $parts[0])[1]; $status = $parts[1]; $reason = isset($parts[2]) ? $parts[2] : null; $headers = \GuzzleHttp\headers_from_lines($hdrs); list($stream, $headers) = $this->checkDecode($options, $headers, $stream); $stream = Psr7\stream_for($stream); $sink = $stream; if (strcasecmp('HEAD', $request->getMethod())) { $sink = $this->createSink($stream, $options); } $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); if (isset($options['on_headers'])) { try { $options['on_headers']($response); } catch (\Exception $e) { $msg = 'An error was encountered during the on_headers event'; $ex = new RequestException($msg, $request, $response, $e); return \GuzzleHttp\Promise\rejection_for($ex); } } if ($sink !== $stream) { $this->drain( $stream, $sink, $response->getHeaderLine('Content-Length') ); } $this->invokeStats($options, $request, $startTime, $response, null); return new FulfilledPromise($response); } private function createSink(StreamInterface $stream, array $options) { if (!empty($options['stream'])) { return $stream; } $sink = isset($options['sink']) ? $options['sink'] : fopen('php://temp', 'r+'); return is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\stream_for($sink); } private function checkDecode(array $options, array $headers, $stream) { if (!empty($options['decode_content'])) { $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); if (isset($normalizedKeys['content-encoding'])) { $encoding = $headers[$normalizedKeys['content-encoding']]; if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { $stream = new Psr7\InflateStream( Psr7\stream_for($stream) ); $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; unset($headers[$normalizedKeys['content-encoding']]); if (isset($normalizedKeys['content-length'])) { $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; $length = (int) $stream->getSize(); if ($length === 0) { unset($headers[$normalizedKeys['content-length']]); } else { $headers[$normalizedKeys['content-length']] = [$length]; } } } } } return [$stream, $headers]; } private function drain( StreamInterface $source, StreamInterface $sink, $contentLength ) { Psr7\copy_to_stream( $source, $sink, (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 ); $sink->seek(0); $source->close(); return $sink; } private function createResource(callable $callback) { $errors = null; set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { $errors[] = [ 'message' => $msg, 'file' => $file, 'line' => $line ]; return true; }); $resource = $callback(); restore_error_handler(); if (!$resource) { $message = 'Error creating resource: '; foreach ($errors as $err) { foreach ($err as $key => $value) { $message .= "[$key] $value" . PHP_EOL; } } throw new \RuntimeException(trim($message)); } return $resource; } private function createStream(RequestInterface $request, array $options) { static $methods; if (!$methods) { $methods = array_flip(get_class_methods(__CLASS__)); } if ($request->getProtocolVersion() == '1.1' && !$request->hasHeader('Connection') ) { $request = $request->withHeader('Connection', 'close'); } if (!isset($options['verify'])) { $options['verify'] = true; } $params = []; $context = $this->getDefaultContext($request); if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { throw new \InvalidArgumentException('on_headers must be callable'); } if (!empty($options)) { foreach ($options as $key => $value) { $method = "add_{$key}"; if (isset($methods[$method])) { $this->{$method}($request, $context, $value, $params); } } } if (isset($options['stream_context'])) { if (!is_array($options['stream_context'])) { throw new \InvalidArgumentException('stream_context must be an array'); } $context = array_replace_recursive( $context, $options['stream_context'] ); } if (isset($options['auth']) && is_array($options['auth']) && isset($options['auth'][2]) && 'ntlm' == $options['auth'][2] ) { throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); } $uri = $this->resolveHost($request, $options); $context = $this->createResource( function () use ($context, $params) { return stream_context_create($context, $params); } ); return $this->createResource( function () use ($uri, &$http_response_header, $context, $options) { $resource = fopen((string) $uri, 'r', null, $context); $this->lastHeaders = $http_response_header; if (isset($options['read_timeout'])) { $readTimeout = $options['read_timeout']; $sec = (int) $readTimeout; $usec = ($readTimeout - $sec) * 100000; stream_set_timeout($resource, $sec, $usec); } return $resource; } ); } private function resolveHost(RequestInterface $request, array $options) { $uri = $request->getUri(); if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { if ('v4' === $options['force_ip_resolve']) { $records = dns_get_record($uri->getHost(), DNS_A); if (!isset($records[0]['ip'])) { throw new ConnectException( sprintf( "Could not resolve IPv4 address for host '%s'", $uri->getHost() ), $request ); } $uri = $uri->withHost($records[0]['ip']); } elseif ('v6' === $options['force_ip_resolve']) { $records = dns_get_record($uri->getHost(), DNS_AAAA); if (!isset($records[0]['ipv6'])) { throw new ConnectException( sprintf( "Could not resolve IPv6 address for host '%s'", $uri->getHost() ), $request ); } $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); } } return $uri; } private function getDefaultContext(RequestInterface $request) { $headers = ''; foreach ($request->getHeaders() as $name => $value) { foreach ($value as $val) { $headers .= "$name: $val\r\n"; } } $context = [ 'http' => [ 'method' => $request->getMethod(), 'header' => $headers, 'protocol_version' => $request->getProtocolVersion(), 'ignore_errors' => true, 'follow_location' => 0, ], ]; $body = (string) $request->getBody(); if (!empty($body)) { $context['http']['content'] = $body; if (!$request->hasHeader('Content-Type')) { $context['http']['header'] .= "Content-Type:\r\n"; } } $context['http']['header'] = rtrim($context['http']['header']); return $context; } private function add_proxy(RequestInterface $request, &$options, $value, &$params) { if (!is_array($value)) { $options['http']['proxy'] = $value; } else { $scheme = $request->getUri()->getScheme(); if (isset($value[$scheme])) { if (!isset($value['no']) || !\GuzzleHttp\is_host_in_noproxy( $request->getUri()->getHost(), $value['no'] ) ) { $options['http']['proxy'] = $value[$scheme]; } } } } private function add_timeout(RequestInterface $request, &$options, $value, &$params) { if ($value > 0) { $options['http']['timeout'] = $value; } } private function add_verify(RequestInterface $request, &$options, $value, &$params) { if ($value === true) { if (PHP_VERSION_ID < 50600) { $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); } } elseif (is_string($value)) { $options['ssl']['cafile'] = $value; if (!file_exists($value)) { throw new \RuntimeException("SSL CA bundle not found: $value"); } } elseif ($value === false) { $options['ssl']['verify_peer'] = false; $options['ssl']['verify_peer_name'] = false; return; } else { throw new \InvalidArgumentException('Invalid verify request option'); } $options['ssl']['verify_peer'] = true; $options['ssl']['verify_peer_name'] = true; $options['ssl']['allow_self_signed'] = false; } private function add_cert(RequestInterface $request, &$options, $value, &$params) { if (is_array($value)) { $options['ssl']['passphrase'] = $value[1]; $value = $value[0]; } if (!file_exists($value)) { throw new \RuntimeException("SSL certificate not found: {$value}"); } $options['ssl']['local_cert'] = $value; } private function add_progress(RequestInterface $request, &$options, $value, &$params) { $this->addNotification( $params, function ($code, $a, $b, $c, $transferred, $total) use ($value) { if ($code == STREAM_NOTIFY_PROGRESS) { $value($total, $transferred, null, null); } } ); } private function add_debug(RequestInterface $request, &$options, $value, &$params) { if ($value === false) { return; } static $map = [ STREAM_NOTIFY_CONNECT => 'CONNECT', STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', STREAM_NOTIFY_PROGRESS => 'PROGRESS', STREAM_NOTIFY_FAILURE => 'FAILURE', STREAM_NOTIFY_COMPLETED => 'COMPLETED', STREAM_NOTIFY_RESOLVE => 'RESOLVE', ]; static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; $value = \GuzzleHttp\debug_resource($value); $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); $this->addNotification( $params, function () use ($ident, $value, $map, $args) { $passed = func_get_args(); $code = array_shift($passed); fprintf($value, '<%s> [%s] ', $ident, $map[$code]); foreach (array_filter($passed) as $i => $v) { fwrite($value, $args[$i] . ': "' . $v . '" '); } fwrite($value, "\n"); } ); } private function addNotification(array &$params, callable $notify) { if (!isset($params['notification'])) { $params['notification'] = $notify; } else { $params['notification'] = $this->callArray([ $params['notification'], $notify ]); } } private function callArray(array $functions) { return function () use ($functions) { $args = func_get_args(); foreach ($functions as $fn) { call_user_func_array($fn, $args); } }; } } onFulfilled = $onFulfilled; $this->onRejected = $onRejected; if ($queue) { call_user_func_array([$this, 'append'], $queue); } } public function __invoke(RequestInterface $request, array $options) { if (!$this->queue) { throw new \OutOfBoundsException('Mock queue is empty'); } if (isset($options['delay']) && is_numeric($options['delay'])) { usleep($options['delay'] * 1000); } $this->lastRequest = $request; $this->lastOptions = $options; $response = array_shift($this->queue); if (isset($options['on_headers'])) { if (!is_callable($options['on_headers'])) { throw new \InvalidArgumentException('on_headers must be callable'); } try { $options['on_headers']($response); } catch (\Exception $e) { $msg = 'An error was encountered during the on_headers event'; $response = new RequestException($msg, $request, $response, $e); } } if (is_callable($response)) { $response = call_user_func($response, $request, $options); } $response = $response instanceof \Exception ? \GuzzleHttp\Promise\rejection_for($response) : \GuzzleHttp\Promise\promise_for($response); return $response->then( function ($value) use ($request, $options) { $this->invokeStats($request, $options, $value); if ($this->onFulfilled) { call_user_func($this->onFulfilled, $value); } if (isset($options['sink'])) { $contents = (string) $value->getBody(); $sink = $options['sink']; if (is_resource($sink)) { fwrite($sink, $contents); } elseif (is_string($sink)) { file_put_contents($sink, $contents); } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { $sink->write($contents); } } return $value; }, function ($reason) use ($request, $options) { $this->invokeStats($request, $options, null, $reason); if ($this->onRejected) { call_user_func($this->onRejected, $reason); } return \GuzzleHttp\Promise\rejection_for($reason); } ); } public function append() { foreach (func_get_args() as $value) { if ($value instanceof ResponseInterface || $value instanceof \Exception || $value instanceof PromiseInterface || is_callable($value) ) { $this->queue[] = $value; } else { throw new \InvalidArgumentException('Expected a response or ' . 'exception. Found ' . \GuzzleHttp\describe_type($value)); } } } public function getLastRequest() { return $this->lastRequest; } public function getLastOptions() { return $this->lastOptions; } public function count() { return count($this->queue); } public function reset() { $this->queue = []; } private function invokeStats( RequestInterface $request, array $options, ResponseInterface $response = null, $reason = null ) { if (isset($options['on_stats'])) { $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0; $stats = new TransferStats($request, $response, $transferTime, $reason); call_user_func($options['on_stats'], $stats); } } } factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(50); if (isset($options['select_timeout'])) { $this->selectTimeout = $options['select_timeout']; } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { $this->selectTimeout = $selectTimeout; } else { $this->selectTimeout = 1; } $this->options = isset($options['options']) ? $options['options'] : []; } public function __get($name) { if ($name === '_mh') { $this->_mh = curl_multi_init(); foreach ($this->options as $option => $value) { curl_multi_setopt($this->_mh, $option, $value); } return $this->_mh; } throw new \BadMethodCallException(); } public function __destruct() { if (isset($this->_mh)) { curl_multi_close($this->_mh); unset($this->_mh); } } public function __invoke(RequestInterface $request, array $options) { $easy = $this->factory->create($request, $options); $id = (int) $easy->handle; $promise = new Promise( [$this, 'execute'], function () use ($id) { return $this->cancel($id); } ); $this->addRequest(['easy' => $easy, 'deferred' => $promise]); return $promise; } public function tick() { if ($this->delays) { $currentTime = Utils::currentTime(); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { unset($this->delays[$id]); curl_multi_add_handle( $this->_mh, $this->handles[$id]['easy']->handle ); } } } P\queue()->run(); if ($this->active && curl_multi_select($this->_mh, $this->selectTimeout) === -1 ) { usleep(250); } while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); } public function execute() { $queue = P\queue(); while ($this->handles || !$queue->isEmpty()) { if (!$this->active && $this->delays) { usleep($this->timeToNext()); } $this->tick(); } } private function addRequest(array $entry) { $easy = $entry['easy']; $id = (int) $easy->handle; $this->handles[$id] = $entry; if (empty($easy->options['delay'])) { curl_multi_add_handle($this->_mh, $easy->handle); } else { $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); } } private function cancel($id) { if (!isset($this->handles[$id])) { return false; } $handle = $this->handles[$id]['easy']->handle; unset($this->delays[$id], $this->handles[$id]); curl_multi_remove_handle($this->_mh, $handle); curl_close($handle); return true; } private function processMessages() { while ($done = curl_multi_info_read($this->_mh)) { $id = (int) $done['handle']; curl_multi_remove_handle($this->_mh, $done['handle']); if (!isset($this->handles[$id])) { continue; } $entry = $this->handles[$id]; unset($this->handles[$id], $this->delays[$id]); $entry['easy']->errno = $done['result']; $entry['deferred']->resolve( CurlFactory::finish( $this, $entry['easy'], $this->factory ) ); } } private function timeToNext() { $currentTime = Utils::currentTime(); $nextTime = PHP_INT_MAX; foreach ($this->delays as $time) { if ($time < $nextTime) { $nextTime = $time; } } return max(0, $nextTime - $currentTime) * 1000000; } } nextHandler = $nextHandler; } public function __invoke(RequestInterface $request, array $options) { $fn = $this->nextHandler; if ($request->getBody()->getSize() === 0) { return $fn($request, $options); } $modify = []; if (!$request->hasHeader('Content-Type')) { if ($uri = $request->getBody()->getMetadata('uri')) { if ($type = Psr7\mimetype_from_filename($uri)) { $modify['set_headers']['Content-Type'] = $type; } } } if (!$request->hasHeader('Content-Length') && !$request->hasHeader('Transfer-Encoding') ) { $size = $request->getBody()->getSize(); if ($size !== null) { $modify['set_headers']['Content-Length'] = $size; } else { $modify['set_headers']['Transfer-Encoding'] = 'chunked'; } } $this->addExpectHeader($request, $options, $modify); return $fn(Psr7\modify_request($request, $modify), $options); } private function addExpectHeader( RequestInterface $request, array $options, array &$modify ) { if ($request->hasHeader('Expect')) { return; } $expect = isset($options['expect']) ? $options['expect'] : null; if ($expect === false || $request->getProtocolVersion() < 1.1) { return; } if ($expect === true) { $modify['set_headers']['Expect'] = '100-Continue'; return; } if ($expect === null) { $expect = 1048576; } $body = $request->getBody(); $size = $body->getSize(); if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { $modify['set_headers']['Expect'] = '100-Continue'; } } } 5, 'protocols' => ['http', 'https'], 'strict' => false, 'referer' => false, 'track_redirects' => false, ]; private $nextHandler; public function __construct(callable $nextHandler) { $this->nextHandler = $nextHandler; } public function __invoke(RequestInterface $request, array $options) { $fn = $this->nextHandler; if (empty($options['allow_redirects'])) { return $fn($request, $options); } if ($options['allow_redirects'] === true) { $options['allow_redirects'] = self::$defaultSettings; } elseif (!is_array($options['allow_redirects'])) { throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); } else { $options['allow_redirects'] += self::$defaultSettings; } if (empty($options['allow_redirects']['max'])) { return $fn($request, $options); } return $fn($request, $options) ->then(function (ResponseInterface $response) use ($request, $options) { return $this->checkRedirect($request, $options, $response); }); } public function checkRedirect( RequestInterface $request, array $options, ResponseInterface $response ) { if (substr($response->getStatusCode(), 0, 1) != '3' || !$response->hasHeader('Location') ) { return $response; } $this->guardMax($request, $options); $nextRequest = $this->modifyRequest($request, $options, $response); if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { unset( $options['curl'][\CURLOPT_HTTPAUTH], $options['curl'][\CURLOPT_USERPWD] ); } if (isset($options['allow_redirects']['on_redirect'])) { call_user_func( $options['allow_redirects']['on_redirect'], $request, $response, $nextRequest->getUri() ); } $promise = $this($nextRequest, $options); if (!empty($options['allow_redirects']['track_redirects'])) { return $this->withTracking( $promise, (string) $nextRequest->getUri(), $response->getStatusCode() ); } return $promise; } private function withTracking(PromiseInterface $promise, $uri, $statusCode) { return $promise->then( function (ResponseInterface $response) use ($uri, $statusCode) { $historyHeader = $response->getHeader(self::HISTORY_HEADER); $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); array_unshift($historyHeader, $uri); array_unshift($statusHeader, $statusCode); return $response->withHeader(self::HISTORY_HEADER, $historyHeader) ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); } ); } private function guardMax(RequestInterface $request, array &$options) { $current = isset($options['__redirect_count']) ? $options['__redirect_count'] : 0; $options['__redirect_count'] = $current + 1; $max = $options['allow_redirects']['max']; if ($options['__redirect_count'] > $max) { throw new TooManyRedirectsException( "Will not follow more than {$max} redirects", $request ); } } public function modifyRequest( RequestInterface $request, array $options, ResponseInterface $response ) { $modify = []; $protocols = $options['allow_redirects']['protocols']; $statusCode = $response->getStatusCode(); if ($statusCode == 303 || ($statusCode <= 302 && !$options['allow_redirects']['strict']) ) { $modify['method'] = 'GET'; $modify['body'] = ''; } $uri = self::redirectUri($request, $response, $protocols); if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion']; $uri = Utils::idnUriConvert($uri, $idnOptions); } $modify['uri'] = $uri; Psr7\rewind_body($request); if ($options['allow_redirects']['referer'] && $modify['uri']->getScheme() === $request->getUri()->getScheme() ) { $uri = $request->getUri()->withUserInfo(''); $modify['set_headers']['Referer'] = (string) $uri; } else { $modify['remove_headers'][] = 'Referer'; } if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { $modify['remove_headers'][] = 'Authorization'; $modify['remove_headers'][] = 'Cookie'; } return Psr7\modify_request($request, $modify); } private static function redirectUri( RequestInterface $request, ResponseInterface $response, array $protocols ) { $location = Psr7\UriResolver::resolve( $request->getUri(), new Psr7\Uri($response->getHeaderLine('Location')) ); if (!in_array($location->getScheme(), $protocols)) { throw new BadResponseException( sprintf( 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, implode(', ', $protocols) ), $request, $response ); } return $location; } } getStatusCode() : 0; parent::__construct($message, $code, $previous); $this->request = $request; $this->response = $response; $this->handlerContext = $handlerContext; } public static function wrapException(RequestInterface $request, \Exception $e) { return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); } public static function create( RequestInterface $request, ResponseInterface $response = null, \Exception $previous = null, array $ctx = [] ) { if (!$response) { return new self( 'Error completing request', $request, null, $previous, $ctx ); } $level = (int) floor($response->getStatusCode() / 100); if ($level === 4) { $label = 'Client error'; $className = ClientException::class; } elseif ($level === 5) { $label = 'Server error'; $className = ServerException::class; } else { $label = 'Unsuccessful request'; $className = __CLASS__; } $uri = $request->getUri(); $uri = static::obfuscateUri($uri); $message = sprintf( '%s: `%s %s` resulted in a `%s %s` response', $label, $request->getMethod(), $uri, $response->getStatusCode(), $response->getReasonPhrase() ); $summary = static::getResponseBodySummary($response); if ($summary !== null) { $message .= ":\n{$summary}\n"; } return new $className($message, $request, $response, $previous, $ctx); } public static function getResponseBodySummary(ResponseInterface $response) { return \GuzzleHttp\Psr7\get_message_body_summary($response); } private static function obfuscateUri(UriInterface $uri) { $userInfo = $uri->getUserInfo(); if (false !== ($pos = strpos($userInfo, ':'))) { return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); } return $uri; } public function getRequest() { return $this->request; } public function getResponse() { return $this->response; } public function hasResponse() { return $this->response !== null; } public function getHandlerContext() { return $this->handlerContext; } } stream = $stream; $msg = $msg ?: 'Could not seek the stream to position ' . $pos; parent::__construct($msg); } public function getStream() { return $this->stream; } } expand($template, $variables); } function describe_type($input) { switch (gettype($input)) { case 'object': return 'object(' . get_class($input) . ')'; case 'array': return 'array(' . count($input) . ')'; default: ob_start(); var_dump($input); return str_replace('double(', 'float(', rtrim(ob_get_clean())); } } function headers_from_lines($lines) { $headers = []; foreach ($lines as $line) { $parts = explode(':', $line, 2); $headers[trim($parts[0])][] = isset($parts[1]) ? trim($parts[1]) : null; } return $headers; } function debug_resource($value = null) { if (is_resource($value)) { return $value; } elseif (defined('STDOUT')) { return STDOUT; } return fopen('php://output', 'w'); } function choose_handler() { $handler = null; if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); } elseif (function_exists('curl_exec')) { $handler = new CurlHandler(); } elseif (function_exists('curl_multi_exec')) { $handler = new CurlMultiHandler(); } if (ini_get('allow_url_fopen')) { $handler = $handler ? Proxy::wrapStreaming($handler, new StreamHandler()) : new StreamHandler(); } elseif (!$handler) { throw new \RuntimeException('GuzzleHttp requires cURL, the ' . 'allow_url_fopen ini setting, or a custom HTTP handler.'); } return $handler; } function default_user_agent() { static $defaultAgent = ''; if (!$defaultAgent) { $defaultAgent = 'GuzzleHttp/' . Client::VERSION; if (extension_loaded('curl') && function_exists('curl_version')) { $defaultAgent .= ' curl/' . \curl_version()['version']; } $defaultAgent .= ' PHP/' . PHP_VERSION; } return $defaultAgent; } function default_ca_bundle() { static $cached = null; static $cafiles = [ '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/certs/ca-certificates.crt', '/usr/local/share/certs/ca-root-nss.crt', '/var/lib/ca-certificates/ca-bundle.pem', '/usr/local/etc/openssl/cert.pem', '/etc/ca-certificates.crt', 'C:\\windows\\system32\\curl-ca-bundle.crt', 'C:\\windows\\curl-ca-bundle.crt', ]; if ($cached) { return $cached; } if ($ca = ini_get('openssl.cafile')) { return $cached = $ca; } if ($ca = ini_get('curl.cainfo')) { return $cached = $ca; } foreach ($cafiles as $filename) { if (file_exists($filename)) { return $cached = $filename; } } throw new \RuntimeException( <<< EOT No system CA bundle could be found in any of the the common system locations. PHP versions earlier than 5.6 are not properly configured to use the system's CA bundle by default. In order to verify peer certificates, you will need to supply the path on disk to a certificate bundle to the 'verify' request option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not need a specific certificate bundle, then Mozilla provides a commonly used CA bundle which can be downloaded here (provided by the maintainer of cURL): https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP ini setting to point to the path to the file, allowing you to omit the 'verify' request option. See http://curl.haxx.se/docs/sslcerts.html for more information. EOT ); } function normalize_header_keys(array $headers) { $result = []; foreach (array_keys($headers) as $key) { $result[strtolower($key)] = $key; } return $result; } function is_host_in_noproxy($host, array $noProxyArray) { if (strlen($host) === 0) { throw new \InvalidArgumentException('Empty host provided'); } if (strpos($host, ':')) { $host = explode($host, ':', 2)[0]; } foreach ($noProxyArray as $area) { if ($area === '*') { return true; } elseif (empty($area)) { continue; } elseif ($area === $host) { return true; } else { $area = '.' . ltrim($area, '.'); if (substr($host, -(strlen($area))) === $area) { return true; } } } return false; } function json_decode($json, $assoc = false, $depth = 512, $options = 0) { $data = \json_decode($json, $assoc, $depth, $options); if (JSON_ERROR_NONE !== json_last_error()) { throw new Exception\InvalidArgumentException( 'json_decode error: ' . json_last_error_msg() ); } return $data; } function json_encode($value, $options = 0, $depth = 512) { $json = \json_encode($value, $options, $depth); if (JSON_ERROR_NONE !== json_last_error()) { throw new Exception\InvalidArgumentException( 'json_encode error: ' . json_last_error_msg() ); } return $json; } basePath = \realpath($basePath); $this->prefixes = $prefixes; $this->suffixes = $suffixes; $this->exclude = \array_filter(\array_map('realpath', $exclude)); parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $current = $this->getInnerIterator()->current(); $filename = $current->getFilename(); $realPath = $current->getRealPath(); return $this->acceptPath($realPath) && $this->acceptPrefix($filename) && $this->acceptSuffix($filename); } private function acceptPath(string $path): bool { if (\preg_match('=/\.[^/]*/=', \str_replace($this->basePath, '', $path))) { return false; } foreach ($this->exclude as $exclude) { if (\strpos($path, $exclude) === 0) { return false; } } return true; } private function acceptPrefix(string $filename): bool { return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); } private function acceptSuffix(string $filename): bool { return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); } private function acceptSubString(string $filename, array $subStrings, int $type): bool { if (empty($subStrings)) { return true; } $matched = false; foreach ($subStrings as $string) { if (($type === self::PREFIX && \strpos($filename, $string) === 0) || ($type === self::SUFFIX && \substr($filename, -1 * \strlen($string)) === $string)) { $matched = true; break; } } return $matched; } } getPathsAfterResolvingWildcards($paths); $exclude = $this->getPathsAfterResolvingWildcards($exclude); if (\is_string($prefixes)) { if ($prefixes !== '') { $prefixes = [$prefixes]; } else { $prefixes = []; } } if (\is_string($suffixes)) { if ($suffixes !== '') { $suffixes = [$suffixes]; } else { $suffixes = []; } } $iterator = new \AppendIterator; foreach ($paths as $path) { if (\is_dir($path)) { $iterator->append( new Iterator( $path, new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS | \RecursiveDirectoryIterator::SKIP_DOTS) ), $suffixes, $prefixes, $exclude ) ); } } return $iterator; } protected function getPathsAfterResolvingWildcards(array $paths): array { $_paths = [[]]; foreach ($paths as $path) { if ($locals = \glob($path, GLOB_ONLYDIR)) { $_paths[] = \array_map('\realpath', $locals); } else { $_paths[] = [\realpath($path)]; } } return \array_filter(\array_merge(...$_paths)); } } getFileIterator($paths, $suffixes, $prefixes, $exclude); $files = []; foreach ($iterator as $file) { $file = $file->getRealPath(); if ($file) { $files[] = $file; } } foreach ($paths as $path) { if (\is_file($path)) { $files[] = \realpath($path); } } $files = \array_unique($files); \sort($files); if ($commonPath) { return [ 'commonPath' => $this->getCommonPath($files), 'files' => $files ]; } return $files; } protected function getCommonPath(array $files): string { $count = \count($files); if ($count === 0) { return ''; } if ($count === 1) { return \dirname($files[0]) . DIRECTORY_SEPARATOR; } $_files = []; foreach ($files as $file) { $_files[] = $_fileParts = \explode(DIRECTORY_SEPARATOR, $file); if (empty($_fileParts[0])) { $_fileParts[0] = DIRECTORY_SEPARATOR; } } $common = ''; $done = false; $j = 0; $count--; while (!$done) { for ($i = 0; $i < $count; $i++) { if ($_files[$i][$j] != $_files[$i + 1][$j]) { $done = true; break; } } if (!$done) { $common .= $_files[0][$j]; if ($j > 0) { $common .= DIRECTORY_SEPARATOR; } } $j++; } return DIRECTORY_SEPARATOR . $common; } } setFile($file); $this->openDelimiter = $openDelimiter; $this->closeDelimiter = $closeDelimiter; } public function setFile($file) { $distFile = $file . '.dist'; if (file_exists($file)) { $this->template = file_get_contents($file); } else if (file_exists($distFile)) { $this->template = file_get_contents($distFile); } else { throw new InvalidArgumentException( 'Template file could not be loaded.' ); } } public function setVar(array $values, $merge = TRUE) { if (!$merge || empty($this->values)) { $this->values = $values; } else { $this->values = array_merge($this->values, $values); } } public function render() { $keys = array(); foreach ($this->values as $key => $value) { $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; } return str_replace($keys, $this->values, $this->template); } public function renderTo($target) { $fp = @fopen($target, 'wt'); if ($fp) { fwrite($fp, $this->render()); fclose($fp); } else { $error = error_get_last(); throw new RuntimeException( sprintf( 'Could not write to %s: %s', $target, substr( $error['message'], strpos($error['message'], ':') + 2 ) ) ); } } } 1073741824, 'MB' => 1048576, 'KB' => 1024, ]; private static $times = [ 'hour' => 3600000, 'minute' => 60000, 'second' => 1000, ]; private static $startTimes = []; public static function start(): void { self::$startTimes[] = \microtime(true); } public static function stop(): float { return \microtime(true) - \array_pop(self::$startTimes); } public static function bytesToString(float $bytes): string { foreach (self::$sizes as $unit => $value) { if ($bytes >= $value) { return \sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); } } return $bytes . ' byte' . ((int) $bytes !== 1 ? 's' : ''); } public static function secondsToTimeString(float $time): string { $ms = \round($time * 1000); foreach (self::$times as $unit => $value) { if ($ms >= $value) { $time = \floor($ms / $value * 100.0) / 100.0; return $time . ' ' . ($time == 1 ? $unit : $unit . 's'); } } return $ms . ' ms'; } public static function timeSinceStartOfRequest(): string { if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { $startOfRequest = $_SERVER['REQUEST_TIME_FLOAT']; } elseif (isset($_SERVER['REQUEST_TIME'])) { $startOfRequest = $_SERVER['REQUEST_TIME']; } else { throw new RuntimeException('Cannot determine time at which the request started'); } return self::secondsToTimeString(\microtime(true) - $startOfRequest); } public static function resourceUsage(): string { return \sprintf( 'Time: %s, Memory: %s', self::timeSinceStartOfRequest(), self::bytesToString(\memory_get_peak_usage(true)) ); } } cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; } public function setState(string $testName, int $state): void { if (!in_array($state, self::ALLOWED_TEST_STATUSES, true)) { return; } $this->defects[$testName] = $state; } public function getState(string $testName): int { return $this->defects[$testName] ?? BaseTestRunner::STATUS_UNKNOWN; } public function setTime(string $testName, float $time): void { $this->times[$testName] = $time; } public function getTime(string $testName): float { return $this->times[$testName] ?? 0.0; } public function load(): void { if (!is_file($this->cacheFilename)) { return; } $data = json_decode( file_get_contents($this->cacheFilename), true ); if ($data === null) { return; } if (!isset($data['version'])) { return; } if ($data['version'] !== self::VERSION) { return; } assert(isset($data['defects']) && is_array($data['defects'])); assert(isset($data['times']) && is_array($data['times'])); $this->defects = $data['defects']; $this->times = $data['times']; } public function persist(): void { if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { throw new Exception( sprintf( 'Cannot create directory "%s" for result cache file', $this->cacheFilename ) ); } file_put_contents( $this->cacheFilename, json_encode( [ 'version' => self::VERSION, 'defects' => $this->defects, 'times' => $this->times, ] ), LOCK_EX ); } } 6, BaseTestRunner::STATUS_FAILURE => 5, BaseTestRunner::STATUS_WARNING => 4, BaseTestRunner::STATUS_INCOMPLETE => 3, BaseTestRunner::STATUS_RISKY => 2, BaseTestRunner::STATUS_SKIPPED => 1, BaseTestRunner::STATUS_UNKNOWN => 0, ]; private const SIZE_SORT_WEIGHT = [ TestUtil::SMALL => 1, TestUtil::MEDIUM => 2, TestUtil::LARGE => 3, TestUtil::UNKNOWN => 4, ]; private $defectSortOrder = []; private $cache; private $originalExecutionOrder = []; private $executionOrder = []; public static function getTestSorterUID(Test $test): string { if ($test instanceof PhptTestCase) { return $test->getName(); } if ($test instanceof TestCase) { $testName = $test->getName(true); if (strpos($testName, '::') === false) { $testName = get_class($test) . '::' . $testName; } return $testName; } return $test->getName(); } public function __construct(?TestResultCache $cache = null) { $this->cache = $cache ?? new NullTestResultCache; } public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = true): void { $allowedOrders = [ self::ORDER_DEFAULT, self::ORDER_REVERSED, self::ORDER_RANDOMIZED, self::ORDER_DURATION, self::ORDER_SIZE, ]; if (!in_array($order, $allowedOrders, true)) { throw new Exception( '$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]' ); } $allowedOrderDefects = [ self::ORDER_DEFAULT, self::ORDER_DEFECTS_FIRST, ]; if (!in_array($orderDefects, $allowedOrderDefects, true)) { throw new Exception( '$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST' ); } if ($isRootTestSuite) { $this->originalExecutionOrder = $this->calculateTestExecutionOrder($suite); } if ($suite instanceof TestSuite) { foreach ($suite as $_suite) { $this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects, false); } if ($orderDefects === self::ORDER_DEFECTS_FIRST) { $this->addSuiteToDefectSortOrder($suite); } $this->sort($suite, $order, $resolveDependencies, $orderDefects); } if ($isRootTestSuite) { $this->executionOrder = $this->calculateTestExecutionOrder($suite); } } public function getOriginalExecutionOrder(): array { return $this->originalExecutionOrder; } public function getExecutionOrder(): array { return $this->executionOrder; } private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void { if (empty($suite->tests())) { return; } if ($order === self::ORDER_REVERSED) { $suite->setTests($this->reverse($suite->tests())); } elseif ($order === self::ORDER_RANDOMIZED) { $suite->setTests($this->randomize($suite->tests())); } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { $suite->setTests($this->sortByDuration($suite->tests())); } elseif ($order === self::ORDER_SIZE) { $suite->setTests($this->sortBySize($suite->tests())); } if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { $suite->setTests($this->sortDefectsFirst($suite->tests())); } if ($resolveDependencies && !($suite instanceof DataProviderTestSuite) && $this->suiteOnlyContainsTests($suite)) { $tests = $suite->tests(); $suite->setTests($this->resolveDependencies($tests)); } } private function addSuiteToDefectSortOrder(TestSuite $suite): void { $max = 0; foreach ($suite->tests() as $test) { $testname = self::getTestSorterUID($test); if (!isset($this->defectSortOrder[$testname])) { $this->defectSortOrder[$testname] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($testname)]; $max = max($max, $this->defectSortOrder[$testname]); } } $this->defectSortOrder[$suite->getName()] = $max; } private function suiteOnlyContainsTests(TestSuite $suite): bool { return array_reduce( $suite->tests(), static function ($carry, $test) { return $carry && ($test instanceof TestCase || $test instanceof DataProviderTestSuite); }, true ); } private function reverse(array $tests): array { return array_reverse($tests); } private function randomize(array $tests): array { shuffle($tests); return $tests; } private function sortDefectsFirst(array $tests): array { usort( $tests, function ($left, $right) { return $this->cmpDefectPriorityAndTime($left, $right); } ); return $tests; } private function sortByDuration(array $tests): array { usort( $tests, function ($left, $right) { return $this->cmpDuration($left, $right); } ); return $tests; } private function sortBySize(array $tests): array { usort( $tests, function ($left, $right) { return $this->cmpSize($left, $right); } ); return $tests; } private function cmpDefectPriorityAndTime(Test $a, Test $b): int { $priorityA = $this->defectSortOrder[self::getTestSorterUID($a)] ?? 0; $priorityB = $this->defectSortOrder[self::getTestSorterUID($b)] ?? 0; if ($priorityB <=> $priorityA) { return $priorityB <=> $priorityA; } if ($priorityA || $priorityB) { return $this->cmpDuration($a, $b); } return 0; } private function cmpDuration(Test $a, Test $b): int { return $this->cache->getTime(self::getTestSorterUID($a)) <=> $this->cache->getTime(self::getTestSorterUID($b)); } private function cmpSize(Test $a, Test $b): int { $sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite) ? $a->getSize() : TestUtil::UNKNOWN; $sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite) ? $b->getSize() : TestUtil::UNKNOWN; return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; } private function resolveDependencies(array $tests): array { $newTestOrder = []; $i = 0; do { $todoNames = array_map( static function ($test) { return self::getTestSorterUID($test); }, $tests ); if (!$tests[$i]->hasDependencies() || empty(array_intersect($this->getNormalizedDependencyNames($tests[$i]), $todoNames))) { $newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1)); $i = 0; } else { $i++; } } while (!empty($tests) && ($i < count($tests))); return array_merge($newTestOrder, $tests); } private function getNormalizedDependencyNames($test): array { if ($test instanceof DataProviderTestSuite) { $testClass = substr($test->getName(), 0, strpos($test->getName(), '::')); } else { $testClass = get_class($test); } $names = array_map( static function ($name) use ($testClass) { return strpos($name, '::') === false ? $testClass . '::' . $name : $name; }, $test->getDependencies() ); return $names; } private function calculateTestExecutionOrder(Test $suite): array { $tests = []; if ($suite instanceof TestSuite) { foreach ($suite->tests() as $test) { if (!($test instanceof TestSuite)) { $tests[] = self::getTestSorterUID($test); } else { $tests = array_merge($tests, $this->calculateTestExecutionOrder($test)); } } } return $tests; } } filename = $filename; $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); } public function count(): int { return 1; } public function run(TestResult $result = null): TestResult { if ($result === null) { $result = new TestResult; } try { $sections = $this->parse(); } catch (Exception $e) { $result->startTest($this); $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); $result->endTest($this, 0); return $result; } $code = $this->render($sections['FILE']); $xfail = false; $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); $result->startTest($this); if (isset($sections['INI'])) { $settings = $this->parseIniSection($sections['INI'], $settings); } if (isset($sections['ENV'])) { $env = $this->parseEnvSection($sections['ENV']); $this->phpUtil->setEnv($env); } $this->phpUtil->setUseStderrRedirection(true); if ($result->enforcesTimeLimit()) { $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); } $skip = $this->runSkip($sections, $result, $settings); if ($skip) { return $result; } if (isset($sections['XFAIL'])) { $xfail = trim($sections['XFAIL']); } if (isset($sections['STDIN'])) { $this->phpUtil->setStdin($sections['STDIN']); } if (isset($sections['ARGS'])) { $this->phpUtil->setArgs($sections['ARGS']); } if ($result->getCollectCodeCoverageInformation()) { $this->renderForCoverage($code); } Timer::start(); $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); $time = Timer::stop(); $this->output = $jobResult['stdout'] ?? ''; if ($result->getCollectCodeCoverageInformation() && ($coverage = $this->cleanupForCoverage())) { $result->getCodeCoverage()->append($coverage, $this, true, [], [], true); } try { $this->assertPhptExpectation($sections, $this->output); } catch (AssertionFailedError $e) { $failure = $e; if ($xfail !== false) { $failure = new IncompleteTestError($xfail, 0, $e); } elseif ($e instanceof ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure) { $diff = $comparisonFailure->getDiff(); } else { $diff = $e->getMessage(); } $hint = $this->getLocationHintFromDiff($diff, $sections); $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); $failure = new PHPTAssertionFailedError( $e->getMessage(), 0, $trace[0]['file'], $trace[0]['line'], $trace, $comparisonFailure ? $diff : '' ); } $result->addFailure($this, $failure, $time); } catch (Throwable $t) { $result->addError($this, $t, $time); } if ($xfail !== false && $result->allCompletelyImplemented()) { $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); } $this->runClean($sections, $result->getCollectCodeCoverageInformation()); $result->endTest($this, $time); return $result; } public function getName(): string { return $this->toString(); } public function toString(): string { return $this->filename; } public function usesDataProvider(): bool { return false; } public function getNumAssertions(): int { return 1; } public function getActualOutput(): string { return $this->output; } public function hasOutput(): bool { return !empty($this->output); } private function parseIniSection($content, $ini = []): array { if (is_string($content)) { $content = explode("\n", trim($content)); } foreach ($content as $setting) { if (strpos($setting, '=') === false) { continue; } $setting = explode('=', $setting, 2); $name = trim($setting[0]); $value = trim($setting[1]); if ($name === 'extension' || $name === 'zend_extension') { if (!isset($ini[$name])) { $ini[$name] = []; } $ini[$name][] = $value; continue; } $ini[$name] = $value; } return $ini; } private function parseEnvSection(string $content): array { $env = []; foreach (explode("\n", trim($content)) as $e) { $e = explode('=', trim($e), 2); if (!empty($e[0]) && isset($e[1])) { $env[$e[0]] = $e[1]; } } return $env; } private function assertPhptExpectation(array $sections, string $output): void { $assertions = [ 'EXPECT' => 'assertEquals', 'EXPECTF' => 'assertStringMatchesFormat', 'EXPECTREGEX' => 'assertRegExp', ]; $actual = preg_replace('/\r\n/', "\n", trim($output)); foreach ($assertions as $sectionName => $sectionAssertion) { if (isset($sections[$sectionName])) { $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; if ($expected === null) { throw new Exception('No PHPT expectation found'); } Assert::$sectionAssertion($expected, $actual); return; } } throw new Exception('No PHPT assertion found'); } private function runSkip(array &$sections, TestResult $result, array $settings): bool { if (!isset($sections['SKIPIF'])) { return false; } $skipif = $this->render($sections['SKIPIF']); $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { $message = ''; if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) { $message = substr($skipMatch[1], 2); } $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); $result->addFailure( $this, new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), 0 ); $result->endTest($this, 0); return true; } return false; } private function runClean(array &$sections, bool $collectCoverage): void { $this->phpUtil->setStdin(''); $this->phpUtil->setArgs(''); if (isset($sections['CLEAN'])) { $cleanCode = $this->render($sections['CLEAN']); $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); } } private function parse(): array { $sections = []; $section = ''; $unsupportedSections = [ 'CGI', 'COOKIE', 'DEFLATE_POST', 'EXPECTHEADERS', 'EXTENSIONS', 'GET', 'GZIP_POST', 'HEADERS', 'PHPDBG', 'POST', 'POST_RAW', 'PUT', 'REDIRECTTEST', 'REQUEST', ]; $lineNr = 0; foreach (file($this->filename) as $line) { $lineNr++; if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { $section = $result[1]; $sections[$section] = ''; $sections[$section . '_offset'] = $lineNr; continue; } if (empty($section)) { throw new Exception('Invalid PHPT file: empty section header'); } $sections[$section] .= $line; } if (isset($sections['FILEEOF'])) { $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); unset($sections['FILEEOF']); } $this->parseExternal($sections); if (!$this->validate($sections)) { throw new Exception('Invalid PHPT file'); } foreach ($unsupportedSections as $section) { if (isset($sections[$section])) { throw new Exception( "PHPUnit does not support PHPT {$section} sections" ); } } return $sections; } private function parseExternal(array &$sections): void { $allowSections = [ 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ]; $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; foreach ($allowSections as $section) { if (isset($sections[$section . '_EXTERNAL'])) { $externalFilename = trim($sections[$section . '_EXTERNAL']); if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) { throw new Exception( sprintf( 'Could not load --%s-- %s for PHPT file', $section . '_EXTERNAL', $testDirectory . $externalFilename ) ); } $sections[$section] = file_get_contents($testDirectory . $externalFilename); } } } private function validate(array &$sections): bool { $requiredSections = [ 'FILE', [ 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ], ]; foreach ($requiredSections as $section) { if (is_array($section)) { $foundSection = false; foreach ($section as $anySection) { if (isset($sections[$anySection])) { $foundSection = true; break; } } if (!$foundSection) { return false; } continue; } if (!isset($sections[$section])) { return false; } } return true; } private function render(string $code): string { return str_replace( [ '__DIR__', '__FILE__', ], [ "'" . dirname($this->filename) . "'", "'" . $this->filename . "'", ], $code ); } private function getCoverageFiles(): array { $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; $basename = basename($this->filename, 'phpt'); return [ 'coverage' => $baseDir . $basename . 'coverage', 'job' => $baseDir . $basename . 'php', ]; } private function renderForCoverage(string &$job): void { $files = $this->getCoverageFiles(); $template = new Text_Template( __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl' ); $composerAutoload = '\'\''; if (defined('PHPUNIT_COMPOSER_INSTALL')) { $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); } $phar = '\'\''; if (defined('__PHPUNIT_PHAR__')) { $phar = var_export(__PHPUNIT_PHAR__, true); } $globals = ''; if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export( $GLOBALS['__PHPUNIT_BOOTSTRAP'], true ) . ";\n"; } $template->setVar( [ 'composerAutoload' => $composerAutoload, 'phar' => $phar, 'globals' => $globals, 'job' => $files['job'], 'coverageFile' => $files['coverage'], ] ); file_put_contents($files['job'], $job); $job = $template->render(); } private function cleanupForCoverage(): array { $coverage = []; $files = $this->getCoverageFiles(); if (file_exists($files['coverage'])) { $buffer = @file_get_contents($files['coverage']); if ($buffer !== false) { $coverage = @unserialize($buffer); if ($coverage === false) { $coverage = []; } } } foreach ($files as $file) { @unlink($file); } return $coverage; } private function stringifyIni(array $ini): array { $settings = []; foreach ($ini as $key => $value) { if (is_array($value)) { foreach ($value as $val) { $settings[] = $key . '=' . $val; } continue; } $settings[] = $key . '=' . $value; } return $settings; } private function getLocationHintFromDiff(string $message, array $sections): array { $needle = ''; $previousLine = ''; $block = 'message'; foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { $line = trim($line); if ($block === 'message' && $line === '--- Expected') { $block = 'expected'; } if ($block === 'expected' && $line === '@@ @@') { $block = 'diff'; } if ($block === 'diff') { if (strpos($line, '+') === 0) { $needle = $this->getCleanDiffLine($previousLine); break; } if (strpos($line, '-') === 0) { $needle = $this->getCleanDiffLine($line); break; } } if (!empty($line)) { $previousLine = $line; } } return $this->getLocationHint($needle, $sections); } private function getCleanDiffLine(string $line): string { if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { $line = $matches[2]; } return $line; } private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array { $needle = trim($needle); if (empty($needle)) { return [[ 'file' => realpath($this->filename), 'line' => 1, ]]; } if ($sectionName) { $search = [$sectionName]; } else { $search = [ 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ]; } foreach ($search as $section) { if (!isset($sections[$section])) { continue; } if (isset($sections[$section . '_EXTERNAL'])) { $externalFile = trim($sections[$section . '_EXTERNAL']); return [ [ 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), 'line' => 1, ], [ 'file' => realpath($this->filename), 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, ], ]; } $sectionOffset = $sections[$section . '_offset'] ?? 0; $offset = $sectionOffset + 1; foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { if (strpos($line, $needle) !== false) { return [[ 'file' => realpath($this->filename), 'line' => $offset, ]]; } $offset++; } } if ($sectionName) { return [[ 'file' => realpath($this->filename), 'line' => $sectionOffset, ]]; } return [[ 'file' => realpath($this->filename), 'line' => 1, ]]; } private function settings(bool $collectCoverage): array { $settings = [ 'allow_url_fopen=1', 'auto_append_file=', 'auto_prepend_file=', 'disable_functions=', 'display_errors=1', 'docref_ext=.html', 'docref_root=', 'error_append_string=', 'error_prepend_string=', 'error_reporting=-1', 'html_errors=0', 'log_errors=0', 'open_basedir=', 'output_buffering=Off', 'output_handler=', 'report_memleaks=0', 'report_zend_debug=0', ]; if (extension_loaded('pcov')) { if ($collectCoverage) { $settings[] = 'pcov.enabled=1'; } else { $settings[] = 'pcov.enabled=0'; } } if (extension_loaded('xdebug')) { if (version_compare(phpversion('xdebug'), '3', '>=')) { if ($collectCoverage) { $settings[] = 'xdebug.mode=coverage'; } else { $settings[] = 'xdebug.mode=off'; } } else { $settings[] = 'xdebug.default_enable=0'; if ($collectCoverage) { $settings[] = 'xdebug.coverage_enable=1'; } } } return $settings; } } getFilesAsArray( $suiteClassName, $suffixes ); $suite = new TestSuite($suiteClassName); $suite->addTestFiles($files); return $suite; } try { $testClass = $this->loadSuiteClass( $suiteClassName, $suiteClassFile ); } catch (Exception $e) { $this->runFailed($e->getMessage()); return null; } try { $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); if (!$suiteMethod->isStatic()) { $this->runFailed( 'suite() method must be static.' ); return null; } $test = $suiteMethod->invoke(null, $testClass->getName()); } catch (ReflectionException $e) { try { $test = new TestSuite($testClass); } catch (Exception $e) { $test = new TestSuite; $test->setName($suiteClassName); } } $this->clearStatus(); return $test; } protected function loadSuiteClass(string $suiteClassName, string $suiteClassFile = ''): ReflectionClass { return $this->getLoader()->load($suiteClassName, $suiteClassFile); } protected function clearStatus(): void { } abstract protected function runFailed(string $message): void; } getVersion(); } return self::$version; } public static function series(): string { if (strpos(self::id(), '-')) { $version = explode('-', self::id())[0]; } else { $version = self::id(); } return implode('.', array_slice(explode('.', $version), 0, 2)); } public static function getVersionString(): string { return 'PHPUnit ' . self::id() . ' #StandWithUkraine'; } public static function getReleaseChannel(): string { if (strpos(self::$pharVersion, '-') !== false) { return '-snapshot'; } return ''; } } getMessage(), (int) $e->getCode(), $e ); } if (substr($loadedClass, $offset) === $suiteClassName && $class->getFileName() == $filename) { $suiteClassName = $loadedClass; break; } } } if (!empty($loadedClasses) && !class_exists($suiteClassName, false)) { $testCaseClass = TestCase::class; foreach ($loadedClasses as $loadedClass) { try { $class = new ReflectionClass($loadedClass); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $classFile = $class->getFileName(); if ($class->isSubclassOf($testCaseClass) && !$class->isAbstract()) { $suiteClassName = $loadedClass; $testCaseClass = $loadedClass; if ($classFile == realpath($suiteClassFile)) { break; } } if ($class->hasMethod('suite')) { try { $method = $class->getMethod('suite'); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { $suiteClassName = $loadedClass; if ($classFile == realpath($suiteClassFile)) { break; } } } } } if (class_exists($suiteClassName, false)) { try { $class = new ReflectionClass($suiteClassName); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($class->getFileName() == realpath($suiteClassFile)) { return $class; } } throw new Exception( sprintf( "Class '%s' could not be found in '%s'.", $suiteClassName, $suiteClassFile ) ); } public function reload(ReflectionClass $aClass): ReflectionClass { return $aClass; } } groupTests, true); } } setFilter($filter); } public function accept(): bool { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } $tmp = \PHPUnit\Util\Test::describe($test); if ($test instanceof WarningTestCase) { $name = $test->getMessage(); } elseif ($tmp[0] !== '') { $name = implode('::', $tmp); } else { $name = $tmp[1]; } $accepted = @preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return (bool) $accepted; } private function setFilter(string $filter): void { if (RegularExpression::safeMatch($filter, '') === false) { if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = sprintf( '%s.*with data set #(\d+)$', $matches[1] ); $this->filterMin = (int) $matches[2]; $this->filterMax = (int) $matches[3]; } else { $filter = sprintf( '%s.*with data set #%s$', $matches[1], $matches[2] ); } } elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = sprintf( '%s.*with data set "%s"$', $matches[1], $matches[2] ); } $filter = sprintf( '/%s/i', str_replace( '/', '\\/', $filter ) ); } $this->filter = $filter; } } isSubclassOf(RecursiveFilterIterator::class)) { throw new InvalidArgumentException( sprintf( 'Class "%s" does not extend RecursiveFilterIterator', $filter->name ) ); } $this->filters[] = [$filter, $args]; } public function factory(Iterator $iterator, TestSuite $suite): FilterIterator { foreach ($this->filters as $filter) { [$class, $args] = $filter; $iterator = $class->newInstance($iterator, $args, $suite); } return $iterator; } } getGroupDetails() as $group => $tests) { if (in_array((string) $group, $groups, true)) { $testHashes = array_map( 'spl_object_hash', $tests ); $this->groupTests = array_merge($this->groupTests, $testHashes); } } } public function accept(): bool { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } return $this->doAccept(spl_object_hash($test)); } abstract protected function doAccept(string $hash); } groupTests, true); } } hooks[] = $hook; } public function startTest(Test $test): void { foreach ($this->hooks as $hook) { if ($hook instanceof BeforeTestHook) { $hook->executeBeforeTest(TestUtil::describeAsString($test)); } } $this->lastTestWasNotSuccessful = false; } public function addError(Test $test, Throwable $t, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterTestErrorHook) { $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function addWarning(Test $test, Warning $e, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterTestWarningHook) { $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterTestFailureHook) { $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterIncompleteTestHook) { $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function addRiskyTest(Test $test, Throwable $t, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterRiskyTestHook) { $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function addSkippedTest(Test $test, Throwable $t, float $time): void { foreach ($this->hooks as $hook) { if ($hook instanceof AfterSkippedTestHook) { $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); } } $this->lastTestWasNotSuccessful = true; } public function endTest(Test $test, float $time): void { if (!$this->lastTestWasNotSuccessful) { foreach ($this->hooks as $hook) { if ($hook instanceof AfterSuccessfulTestHook) { $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); } } } foreach ($this->hooks as $hook) { if ($hook instanceof AfterTestHook) { $hook->executeAfterTest(TestUtil::describeAsString($test), $time); } } } public function startTestSuite(TestSuite $suite): void { } public function endTestSuite(TestSuite $suite): void { } } cache = $cache; } public function flush(): void { $this->cache->persist(); } public function executeAfterSuccessfulTest(string $test, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); } public function executeAfterIncompleteTest(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_INCOMPLETE); } public function executeAfterRiskyTest(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_RISKY); } public function executeAfterSkippedTest(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_SKIPPED); } public function executeAfterTestError(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_ERROR); } public function executeAfterTestFailure(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_FAILURE); } public function executeAfterTestWarning(string $test, string $message, float $time): void { $testName = $this->getTestName($test); $this->cache->setTime($testName, round($time, 3)); $this->cache->setState($testName, BaseTestRunner::STATUS_WARNING); } public function executeAfterLastTest(): void { $this->flush(); } private function getTestName(string $test): string { $matches = []; if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $test, $matches)) { $test = $matches['name'] . ($matches['dataname'] ?? ''); } return $test; } } listeners[] = $listener; } public function removeListener(TestListener $listener): void { foreach ($this->listeners as $key => $_listener) { if ($listener === $_listener) { unset($this->listeners[$key]); } } } public function flushListeners(): void { foreach ($this->listeners as $listener) { if ($listener instanceof Printer) { $listener->flush(); } } } public function addError(Test $test, Throwable $t, float $time): void { if ($t instanceof RiskyTestError) { $this->risky[] = new TestFailure($test, $t); $notifyMethod = 'addRiskyTest'; if ($test instanceof TestCase) { $test->markAsRisky(); } if ($this->stopOnRisky || $this->stopOnDefect) { $this->stop(); } } elseif ($t instanceof IncompleteTest) { $this->notImplemented[] = new TestFailure($test, $t); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($t instanceof SkippedTest) { $this->skipped[] = new TestFailure($test, $t); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->errors[] = new TestFailure($test, $t); $notifyMethod = 'addError'; if ($this->stopOnError || $this->stopOnFailure) { $this->stop(); } } if ($t instanceof Error) { $t = new ExceptionWrapper($t); } foreach ($this->listeners as $listener) { $listener->{$notifyMethod}($test, $t, $time); } $this->lastTestFailed = true; $this->time += $time; } public function addWarning(Test $test, Warning $e, float $time): void { if ($this->stopOnWarning || $this->stopOnDefect) { $this->stop(); } $this->warnings[] = new TestFailure($test, $e); foreach ($this->listeners as $listener) { $listener->addWarning($test, $e, $time); } $this->time += $time; } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { if ($e instanceof RiskyTestError || $e instanceof OutputError) { $this->risky[] = new TestFailure($test, $e); $notifyMethod = 'addRiskyTest'; if ($test instanceof TestCase) { $test->markAsRisky(); } if ($this->stopOnRisky || $this->stopOnDefect) { $this->stop(); } } elseif ($e instanceof IncompleteTest) { $this->notImplemented[] = new TestFailure($test, $e); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($e instanceof SkippedTest) { $this->skipped[] = new TestFailure($test, $e); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->failures[] = new TestFailure($test, $e); $notifyMethod = 'addFailure'; if ($this->stopOnFailure || $this->stopOnDefect) { $this->stop(); } } foreach ($this->listeners as $listener) { $listener->{$notifyMethod}($test, $e, $time); } $this->lastTestFailed = true; $this->time += $time; } public function startTestSuite(TestSuite $suite): void { if ($this->topTestSuite === null) { $this->topTestSuite = $suite; } foreach ($this->listeners as $listener) { $listener->startTestSuite($suite); } } public function endTestSuite(TestSuite $suite): void { foreach ($this->listeners as $listener) { $listener->endTestSuite($suite); } } public function startTest(Test $test): void { $this->lastTestFailed = false; $this->runTests += count($test); foreach ($this->listeners as $listener) { $listener->startTest($test); } } public function endTest(Test $test, float $time): void { foreach ($this->listeners as $listener) { $listener->endTest($test, $time); } if (!$this->lastTestFailed && $test instanceof TestCase) { $class = get_class($test); $key = $class . '::' . $test->getName(); $this->passed[$key] = [ 'result' => $test->getResult(), 'size' => TestUtil::getSize( $class, $test->getName(false) ), ]; $this->time += $time; } } public function allHarmless(): bool { return $this->riskyCount() == 0; } public function riskyCount(): int { return count($this->risky); } public function allCompletelyImplemented(): bool { return $this->notImplementedCount() == 0; } public function notImplementedCount(): int { return count($this->notImplemented); } public function risky(): array { return $this->risky; } public function notImplemented(): array { return $this->notImplemented; } public function noneSkipped(): bool { return $this->skippedCount() == 0; } public function skippedCount(): int { return count($this->skipped); } public function skipped(): array { return $this->skipped; } public function errorCount(): int { return count($this->errors); } public function errors(): array { return $this->errors; } public function failureCount(): int { return count($this->failures); } public function failures(): array { return $this->failures; } public function warningCount(): int { return count($this->warnings); } public function warnings(): array { return $this->warnings; } public function passed(): array { return $this->passed; } public function topTestSuite(): TestSuite { return $this->topTestSuite; } public function getCollectCodeCoverageInformation(): bool { return $this->codeCoverage !== null; } public function run(Test $test): void { Assert::resetCount(); $size = TestUtil::UNKNOWN; if ($test instanceof TestCase) { $test->setRegisterMockObjectsFromTestArgumentsRecursively( $this->registerMockObjectsFromTestArgumentsRecursively ); $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); $size = $test->getSize(); } $error = false; $failure = false; $warning = false; $incomplete = false; $risky = false; $skipped = false; $this->startTest($test); if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { $errorHandler = new ErrorHandler( $this->convertDeprecationsToExceptions, $this->convertErrorsToExceptions, $this->convertNoticesToExceptions, $this->convertWarningsToExceptions ); $errorHandler->register(); } $collectCodeCoverage = $this->codeCoverage !== null && !$test instanceof WarningTestCase && $isAnyCoverageRequired; if ($collectCodeCoverage) { $this->codeCoverage->start($test); } $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && !$test instanceof WarningTestCase && $size === TestUtil::SMALL && function_exists('xdebug_start_function_monitor'); if ($monitorFunctions) { xdebug_start_function_monitor(ResourceOperations::getFunctions()); } Timer::start(); try { if (!$test instanceof WarningTestCase && $this->shouldTimeLimitBeEnforced($size)) { switch ($size) { case TestUtil::SMALL: $_timeout = $this->timeoutForSmallTests; break; case TestUtil::MEDIUM: $_timeout = $this->timeoutForMediumTests; break; case TestUtil::LARGE: $_timeout = $this->timeoutForLargeTests; break; default: $_timeout = $this->defaultTimeLimit; } $invoker = new Invoker; $invoker->invoke([$test, 'runBare'], [], $_timeout); } else { $test->runBare(); } } catch (TimeoutException $e) { $this->addFailure( $test, new RiskyTestError( $e->getMessage() ), $_timeout ); $risky = true; } catch (MockObjectException $e) { $e = new Warning( $e->getMessage() ); $warning = true; } catch (AssertionFailedError $e) { $failure = true; if ($e instanceof RiskyTestError) { $risky = true; } elseif ($e instanceof IncompleteTestError) { $incomplete = true; } elseif ($e instanceof SkippedTestError) { $skipped = true; } } catch (AssertionError $e) { $test->addToAssertionCount(1); $failure = true; $frame = $e->getTrace()[0]; $e = new AssertionFailedError( sprintf( '%s in %s:%s', $e->getMessage(), $frame['file'] ?? $e->getFile(), $frame['line'] ?? $e->getLine() ) ); } catch (Warning $e) { $warning = true; } catch (Exception $e) { $error = true; } catch (Throwable $e) { $e = new ExceptionWrapper($e); $error = true; } $time = Timer::stop(); $test->addToAssertionCount(Assert::getCount()); if ($monitorFunctions) { $blacklist = new Blacklist; $functions = xdebug_get_monitored_functions(); xdebug_stop_function_monitor(); foreach ($functions as $function) { if (!$blacklist->isBlacklisted($function['filename'])) { $this->addFailure( $test, new RiskyTestError( sprintf( '%s() used in %s:%s', $function['function'], $function['filename'], $function['lineno'] ) ), $time ); } } } if ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() == 0) { $risky = true; } if ($collectCodeCoverage) { $append = !$risky && !$incomplete && !$skipped; $linesToBeCovered = []; $linesToBeUsed = []; if ($append && $test instanceof TestCase) { try { $linesToBeCovered = TestUtil::getLinesToBeCovered( get_class($test), $test->getName(false) ); $linesToBeUsed = TestUtil::getLinesToBeUsed( get_class($test), $test->getName(false) ); } catch (InvalidCoversTargetException $cce) { $this->addWarning( $test, new Warning( $cce->getMessage() ), $time ); } } try { $this->codeCoverage->stop( $append, $linesToBeCovered, $linesToBeUsed ); } catch (UnintentionallyCoveredCodeException $cce) { $this->addFailure( $test, new UnintentionallyCoveredCodeError( 'This test executed code that is not listed as code to be covered or used:' . PHP_EOL . $cce->getMessage() ), $time ); } catch (OriginalCoveredCodeNotExecutedException $cce) { $this->addFailure( $test, new CoveredCodeNotExecutedException( 'This test did not execute all the code that is listed as code to be covered:' . PHP_EOL . $cce->getMessage() ), $time ); } catch (OriginalMissingCoversAnnotationException $cce) { if ($linesToBeCovered !== false) { $this->addFailure( $test, new MissingCoversAnnotationException( 'This test does not have a @covers annotation but is expected to have one' ), $time ); } } catch (OriginalCodeCoverageException $cce) { $error = true; $e = $e ?? $cce; } } if (isset($errorHandler)) { $errorHandler->unregister(); unset($errorHandler); } if ($error) { $this->addError($test, $e, $time); } elseif ($failure) { $this->addFailure($test, $e, $time); } elseif ($warning) { $this->addWarning($test, $e, $time); } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && !$test->doesNotPerformAssertions() && $test->getNumAssertions() == 0) { try { $reflected = new ReflectionClass($test); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $name = $test->getName(false); if ($name && $reflected->hasMethod($name)) { try { $reflected = $reflected->getMethod($name); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } $this->addFailure( $test, new RiskyTestError( sprintf( "This test did not perform any assertions\n\n%s:%d", $reflected->getFileName(), $reflected->getStartLine() ) ), $time ); } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && $test->doesNotPerformAssertions() && $test->getNumAssertions() > 0) { $this->addFailure( $test, new RiskyTestError( sprintf( 'This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', $test->getNumAssertions() ) ), $time ); } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { $this->addFailure( $test, new OutputError( sprintf( 'This test printed output: %s', $test->getActualOutput() ) ), $time ); } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof TestCase) { $annotations = $test->getAnnotations(); if (isset($annotations['method']['todo'])) { $this->addFailure( $test, new RiskyTestError( 'Test method is annotated with @todo' ), $time ); } } $this->endTest($test, $time); } public function count(): int { return $this->runTests; } public function shouldStop(): bool { return $this->stop; } public function stop(): void { $this->stop = true; } public function getCodeCoverage(): ?CodeCoverage { return $this->codeCoverage; } public function setCodeCoverage(CodeCoverage $codeCoverage): void { $this->codeCoverage = $codeCoverage; } public function convertDeprecationsToExceptions(bool $flag): void { $this->convertDeprecationsToExceptions = $flag; } public function getConvertDeprecationsToExceptions(): bool { return $this->convertDeprecationsToExceptions; } public function convertErrorsToExceptions(bool $flag): void { $this->convertErrorsToExceptions = $flag; } public function getConvertErrorsToExceptions(): bool { return $this->convertErrorsToExceptions; } public function convertNoticesToExceptions(bool $flag): void { $this->convertNoticesToExceptions = $flag; } public function getConvertNoticesToExceptions(): bool { return $this->convertNoticesToExceptions; } public function convertWarningsToExceptions(bool $flag): void { $this->convertWarningsToExceptions = $flag; } public function getConvertWarningsToExceptions(): bool { return $this->convertWarningsToExceptions; } public function stopOnError(bool $flag): void { $this->stopOnError = $flag; } public function stopOnFailure(bool $flag): void { $this->stopOnFailure = $flag; } public function stopOnWarning(bool $flag): void { $this->stopOnWarning = $flag; } public function beStrictAboutTestsThatDoNotTestAnything(bool $flag): void { $this->beStrictAboutTestsThatDoNotTestAnything = $flag; } public function isStrictAboutTestsThatDoNotTestAnything(): bool { return $this->beStrictAboutTestsThatDoNotTestAnything; } public function beStrictAboutOutputDuringTests(bool $flag): void { $this->beStrictAboutOutputDuringTests = $flag; } public function isStrictAboutOutputDuringTests(): bool { return $this->beStrictAboutOutputDuringTests; } public function beStrictAboutResourceUsageDuringSmallTests(bool $flag): void { $this->beStrictAboutResourceUsageDuringSmallTests = $flag; } public function isStrictAboutResourceUsageDuringSmallTests(): bool { return $this->beStrictAboutResourceUsageDuringSmallTests; } public function enforceTimeLimit(bool $flag): void { $this->enforceTimeLimit = $flag; } public function enforcesTimeLimit(): bool { return $this->enforceTimeLimit; } public function beStrictAboutTodoAnnotatedTests(bool $flag): void { $this->beStrictAboutTodoAnnotatedTests = $flag; } public function isStrictAboutTodoAnnotatedTests(): bool { return $this->beStrictAboutTodoAnnotatedTests; } public function stopOnRisky(bool $flag): void { $this->stopOnRisky = $flag; } public function stopOnIncomplete(bool $flag): void { $this->stopOnIncomplete = $flag; } public function stopOnSkipped(bool $flag): void { $this->stopOnSkipped = $flag; } public function stopOnDefect(bool $flag): void { $this->stopOnDefect = $flag; } public function time(): float { return $this->time; } public function wasSuccessful(): bool { return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); } public function wasSuccessfulIgnoringWarnings(): bool { return empty($this->errors) && empty($this->failures); } public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete(): bool { return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); } public function setDefaultTimeLimit(int $timeout): void { $this->defaultTimeLimit = $timeout; } public function setTimeoutForSmallTests(int $timeout): void { $this->timeoutForSmallTests = $timeout; } public function setTimeoutForMediumTests(int $timeout): void { $this->timeoutForMediumTests = $timeout; } public function setTimeoutForLargeTests(int $timeout): void { $this->timeoutForLargeTests = $timeout; } public function getTimeoutForLargeTests(): int { return $this->timeoutForLargeTests; } public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void { $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } private function shouldTimeLimitBeEnforced(int $size): bool { if (!$this->enforceTimeLimit) { return false; } if (!(($this->defaultTimeLimit || $size !== TestUtil::UNKNOWN))) { return false; } if (!extension_loaded('pcntl')) { return false; } if (!class_exists(Invoker::class)) { return false; } if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { return false; } return true; } } message = $message; } public function getMessage(): string { return $this->message; } public function toString(): string { return $this->getName(); } protected function runTest(): void { $this->markTestIncomplete($this->message); } } getMessage(), (int) $t->getCode()); $this->setOriginalException($t); } public function __toString(): string { $string = TestFailure::exceptionToString($this); if ($trace = Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } if ($this->previous) { $string .= "\nCaused by\n" . $this->previous; } return $string; } public function getClassName(): string { return $this->className; } public function getPreviousWrapped(): ?self { return $this->previous; } public function setClassName(string $className): void { $this->className = $className; } public function setOriginalException(Throwable $t): void { $this->originalException($t); $this->className = get_class($t); $this->file = $t->getFile(); $this->line = $t->getLine(); $this->serializableTrace = $t->getTrace(); foreach (array_keys($this->serializableTrace) as $key) { unset($this->serializableTrace[$key]['args']); } if ($t->getPrevious()) { $this->previous = new self($t->getPrevious()); } } public function getOriginalException(): ?Throwable { return $this->originalException(); } private function originalException(Throwable $exceptionToStore = null): ?Throwable { static $originalExceptions; $instanceId = spl_object_hash($this); if ($exceptionToStore) { $originalExceptions[$instanceId] = $exceptionToStore; } return $originalExceptions[$instanceId] ?? null; } } file = $file; $this->line = $line; } } attributeName = $attributeName; } public function evaluate($other, string $description = '', bool $returnResult = false) { return parent::evaluate( Assert::readAttribute( $other, $this->attributeName ), $description, $returnResult ); } public function toString(): string { return 'attribute "' . $this->attributeName . '" ' . $this->innerConstraint()->toString(); } protected function failureDescription($other): string { return $this->toString(); } } string = $string; $this->ignoreCase = $ignoreCase; } public function toString(): string { if ($this->ignoreCase) { $string = mb_strtolower($this->string); } else { $string = $this->string; } return sprintf( 'contains "%s"', $string ); } protected function matches($other): bool { if ('' === $this->string) { return true; } if ($this->ignoreCase) { return mb_stripos($other, $this->string) !== false; } return mb_strpos($other, $this->string) !== false; } } expectedMessage = $expected; } public function toString(): string { if ($this->expectedMessage === '') { return 'exception message is empty'; } return 'exception message contains '; } protected function matches($other): bool { if ($this->expectedMessage === '') { return $other->getMessage() === ''; } return strpos((string) $other->getMessage(), $this->expectedMessage) !== false; } protected function failureDescription($other): string { if ($this->expectedMessage === '') { return sprintf( "exception message is empty but is '%s'", $other->getMessage() ); } return sprintf( "exception message '%s' contains '%s'", $other->getMessage(), $this->expectedMessage ); } } constraint = new IsType($type); } else { $this->constraint = new IsInstanceOf( $type ); } $this->type = $type; } public function evaluate($other, string $description = '', bool $returnResult = false) { $success = true; foreach ($other as $item) { if (!$this->constraint->evaluate($item, '', true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString(): string { return 'contains only values of type "' . $this->type . '"'; } } callback = $callback; } public function toString(): string { return 'is accepted by specified callback'; } protected function matches($other): bool { return ($this->callback)($other); } } className = $className; } public function toString(): string { return sprintf( 'exception of type "%s"', $this->className ); } protected function matches($other): bool { return $other instanceof $this->className; } protected function failureDescription($other): string { if ($other !== null) { $message = ''; if ($other instanceof Throwable) { $message = '. Message was: "' . $other->getMessage() . '" at' . "\n" . Filter::getFilteredStacktrace($other); } return sprintf( 'exception of type "%s" matches expected exception "%s"%s', get_class($other), $this->className, $message ); } return sprintf( 'exception of type "%s" is thrown', $this->className ); } } key = $key; } public function toString(): string { return 'has the key ' . $this->exporter()->export($this->key); } protected function matches($other): bool { if (is_array($other)) { return array_key_exists($this->key, $other); } if ($other instanceof ArrayAccess) { return $other->offsetExists($this->key); } return false; } protected function failureDescription($other): string { return 'an array ' . $this->toString(); } } suffix = $suffix; } public function toString(): string { return 'ends with "' . $this->suffix . '"'; } protected function matches($other): bool { return substr($other, 0 - strlen($this->suffix)) === $this->suffix; } } toString() ); } } value = $value; } public function toString(): string { return 'is greater than ' . $this->exporter()->export($this->value); } protected function matches($other): bool { return $this->value < $other; } } expectedCount = $expected; } public function toString(): string { return sprintf( 'count matches %d', $this->expectedCount ); } protected function matches($other): bool { return $this->expectedCount === $this->getCountOf($other); } protected function getCountOf($other): ?int { if ($other instanceof Countable || is_array($other)) { return count($other); } if ($other instanceof EmptyIterator) { return 0; } if ($other instanceof Traversable) { while ($other instanceof IteratorAggregate) { $other = $other->getIterator(); } $iterator = $other; if ($iterator instanceof Generator) { return $this->getCountOfGenerator($iterator); } if (!$iterator instanceof Iterator) { return iterator_count($iterator); } $key = $iterator->key(); $count = iterator_count($iterator); if ($key !== null) { $iterator->rewind(); while ($iterator->valid() && $key !== $iterator->key()) { $iterator->next(); } } return $count; } return null; } protected function getCountOfGenerator(Generator $generator): int { for ($count = 0; $generator->valid(); $generator->next()) { $count++; } return $count; } protected function failureDescription($other): string { return sprintf( 'actual size %d matches expected size %d', $this->getCountOf($other), $this->expectedCount ); } } constraints = array_values($constraints); return $constraint; } public function setConstraints(array $constraints): void { $this->constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { throw new \PHPUnit\Framework\Exception( 'All parameters to ' . __CLASS__ . ' must be a constraint object.' ); } $this->constraints[] = $constraint; } } public function evaluate($other, string $description = '', bool $returnResult = false) { $success = true; foreach ($this->constraints as $constraint) { if (!$constraint->evaluate($other, $description, true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString(): string { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' and '; } $text .= $constraint->toString(); } return $text; } public function count(): int { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } value = $value; } public function evaluate($other, string $description = '', bool $returnResult = false) { if (is_float($this->value) && is_float($other) && !is_infinite($this->value) && !is_infinite($other) && !is_nan($this->value) && !is_nan($other)) { $success = abs($this->value - $other) < PHP_FLOAT_EPSILON; } else { $success = $this->value === $other; } if ($returnResult) { return $success; } if (!$success) { $f = null; if (is_string($this->value) && is_string($other)) { $f = new ComparisonFailure( $this->value, $other, sprintf("'%s'", $this->value), sprintf("'%s'", $other) ); } if (is_array($this->value) && is_array($other)) { $f = new ComparisonFailure( $this->value, $other, $this->exporter()->export($this->value), $this->exporter()->export($other) ); } $this->fail($other, $description, $f); } } public function toString(): string { if (is_object($this->value)) { return 'is identical to an object of class "' . get_class($this->value) . '"'; } return 'is identical to ' . $this->exporter()->export($this->value); } protected function failureDescription($other): string { if (is_object($this->value) && is_object($other)) { return 'two variables reference the same object'; } if (is_string($this->value) && is_string($other)) { return 'two strings are identical'; } if (is_array($this->value) && is_array($other)) { return 'two arrays are identical'; } return parent::failureDescription($other); } } value = $value; } public function toString(): string { if (is_string($this->value) && strpos($this->value, "\n") !== false) { return 'contains "' . $this->value . '"'; } return 'contains ' . $this->exporter()->export($this->value); } protected function matches($other): bool { if ($other instanceof SplObjectStorage) { return $other->contains($this->value); } foreach ($other as $element) { if ($this->value == $element) { return true; } } return false; } protected function failureDescription($other): string { return sprintf( '%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString() ); } } constraints = array_values($constraints); return $constraint; } public function setConstraints(array $constraints): void { $this->constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { $constraint = new IsEqual( $constraint ); } $this->constraints[] = $constraint; } } public function evaluate($other, string $description = '', bool $returnResult = false) { $success = true; $lastResult = null; foreach ($this->constraints as $constraint) { $result = $constraint->evaluate($other, $description, true); if ($result === $lastResult) { $success = false; break; } $lastResult = $result; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString(): string { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' xor '; } $text .= $constraint->toString(); } return $text; } public function count(): int { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } exporter()->shortenedExport($other), $error ); } } expectedCode = $expected; } public function toString(): string { return 'exception code is '; } protected function matches($other): bool { return (string) $other->getCode() === (string) $this->expectedCode; } protected function failureDescription($other): string { return sprintf( '%s is equal to expected exception code %s', $this->exporter()->export($other->getCode()), $this->exporter()->export($this->expectedCode) ); } } className = $className; } public function toString(): string { return sprintf( 'is instance of %s "%s"', $this->getType(), $this->className ); } protected function matches($other): bool { return $other instanceof $this->className; } protected function failureDescription($other): string { return sprintf( '%s is an instance of %s "%s"', $this->exporter()->shortenedExport($other), $this->getType(), $this->className ); } private function getType(): string { try { $reflection = new ReflectionClass($this->className); if ($reflection->isInterface()) { return 'interface'; } } catch (ReflectionException $e) { } return 'class'; } } attributeName() ); } protected function matches($other): bool { try { $class = new ReflectionClass($other); if ($class->hasProperty($this->attributeName())) { return $class->getProperty($this->attributeName())->isStatic(); } } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } return false; } } value = $value; } public function toString(): string { if (is_string($this->value) && strpos($this->value, "\n") !== false) { return 'contains "' . $this->value . '"'; } return 'contains ' . $this->exporter()->export($this->value); } protected function matches($other): bool { if ($other instanceof SplObjectStorage) { return $other->contains($this->value); } foreach ($other as $element) { if ($this->value === $element) { return true; } } return false; } protected function failureDescription($other): string { return sprintf( '%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString() ); } } true, 'boolean' => true, 'bool' => true, 'double' => true, 'float' => true, 'integer' => true, 'int' => true, 'null' => true, 'numeric' => true, 'object' => true, 'real' => true, 'resource' => true, 'string' => true, 'scalar' => true, 'callable' => true, 'iterable' => true, ]; private $type; public function __construct(string $type) { if (!isset(self::KNOWN_TYPES[$type])) { throw new \PHPUnit\Framework\Exception( sprintf( 'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' . 'is not a valid type.', $type ) ); } $this->type = $type; } public function toString(): string { return sprintf( 'is of type "%s"', $this->type ); } protected function matches($other): bool { switch ($this->type) { case 'numeric': return is_numeric($other); case 'integer': case 'int': return is_int($other); case 'double': case 'float': case 'real': return is_float($other); case 'string': return is_string($other); case 'boolean': case 'bool': return is_bool($other); case 'null': return null === $other; case 'array': return is_array($other); case 'object': return is_object($other); case 'resource': if (is_resource($other)) { return true; } try { $resource = @get_resource_type($other); if (is_string($resource)) { return true; } } catch (TypeError $e) { } return false; case 'scalar': return is_scalar($other); case 'callable': return is_callable($other); case 'iterable': return is_iterable($other); } } } 0) { $nonInput = $matches[2]; $negatedString = str_replace( $nonInput, str_replace( $positives, $negatives, $nonInput ), $string ); } else { $negatedString = str_replace( $positives, $negatives, $string ); } return $negatedString; } public function __construct($constraint) { if (!($constraint instanceof Constraint)) { $constraint = new IsEqual($constraint); } $this->constraint = $constraint; } public function evaluate($other, string $description = '', bool $returnResult = false) { $success = !$this->constraint->evaluate($other, $description, true); if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString(): string { switch (get_class($this->constraint)) { case LogicalAnd::class: case self::class: case LogicalOr::class: return 'not( ' . $this->constraint->toString() . ' )'; default: return self::negate( $this->constraint->toString() ); } } public function count(): int { return count($this->constraint); } protected function failureDescription($other): string { switch (get_class($this->constraint)) { case LogicalAnd::class: case self::class: case LogicalOr::class: return 'not( ' . $this->constraint->failureDescription($other) . ' )'; default: return self::negate( $this->constraint->failureDescription($other) ); } } } pattern = $pattern; } public function toString(): string { return sprintf( 'matches PCRE pattern "%s"', $this->pattern ); } protected function matches($other): bool { return preg_match($this->pattern, $other) > 0; } } expectedMessageRegExp = $expected; } public function toString(): string { return 'exception message matches '; } protected function matches($other): bool { $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); if ($match === false) { throw new \PHPUnit\Framework\Exception( "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'" ); } return $match === 1; } protected function failureDescription($other): string { return sprintf( "exception message '%s' matches '%s'", $other->getMessage(), $this->expectedMessageRegExp ); } } getCountOf($expected)); } } hasProperty($this->attributeName()); } } value = $value; } public function toString(): string { return sprintf( 'matches JSON string "%s"', $this->value ); } protected function matches($other): bool { [$error, $recodedOther] = Json::canonicalize($other); if ($error) { return false; } [$error, $recodedValue] = Json::canonicalize($this->value); if ($error) { return false; } return $recodedOther == $recodedValue; } protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void { if ($comparisonFailure === null) { [$error, $recodedOther] = Json::canonicalize($other); if ($error) { parent::fail($other, $description); } [$error, $recodedValue] = Json::canonicalize($this->value); if ($error) { parent::fail($other, $description); } $comparisonFailure = new ComparisonFailure( json_decode($this->value), json_decode($other), Json::prettify($recodedValue), Json::prettify($recodedOther), false, 'Failed asserting that two json values are equal.' ); } parent::fail($other, $description, $comparisonFailure); } } attributeName = $attributeName; } public function toString(): string { return sprintf( 'has attribute "%s"', $this->attributeName ); } protected function matches($other): bool { try { return (new ReflectionClass($other))->hasProperty($this->attributeName); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } protected function failureDescription($other): string { return sprintf( '%sclass "%s" %s', is_object($other) ? 'object of ' : '', is_object($other) ? get_class($other) : $other, $this->toString() ); } protected function attributeName(): string { return $this->attributeName; } } checkForObjectIdentity = $checkForObjectIdentity; $this->checkForNonObjectIdentity = $checkForNonObjectIdentity; $this->value = $value; } public function toString(): string { if (is_string($this->value) && strpos($this->value, "\n") !== false) { return 'contains "' . $this->value . '"'; } return 'contains ' . $this->exporter()->export($this->value); } protected function matches($other): bool { if ($other instanceof SplObjectStorage) { return $other->contains($this->value); } if (is_object($this->value)) { foreach ($other as $element) { if ($this->checkForObjectIdentity && $element === $this->value) { return true; } if (!$this->checkForObjectIdentity && $element == $this->value) { return true; } } } else { foreach ($other as $element) { if ($this->checkForNonObjectIdentity && $element === $this->value) { return true; } if (!$this->checkForNonObjectIdentity && $element == $this->value) { return true; } } } return false; } protected function failureDescription($other): string { return sprintf( '%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString() ); } } matches($other)) { $success = true; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function count(): int { return 1; } protected function exporter(): Exporter { if ($this->exporter === null) { $this->exporter = new Exporter; } return $this->exporter; } protected function matches($other): bool { return false; } protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void { $failureDescription = sprintf( 'Failed asserting that %s.', $this->failureDescription($other) ); $additionalFailureDescription = $this->additionalFailureDescription($other); if ($additionalFailureDescription) { $failureDescription .= "\n" . $additionalFailureDescription; } if (!empty($description)) { $failureDescription = $description . "\n" . $failureDescription; } throw new ExpectationFailedException( $failureDescription, $comparisonFailure ); } protected function additionalFailureDescription($other): string { return ''; } protected function failureDescription($other): string { return $this->exporter()->export($other) . ' ' . $this->toString(); } } innerConstraint = $innerConstraint; } public function evaluate($other, string $description = '', bool $returnResult = false) { try { return $this->innerConstraint->evaluate( $other, $description, $returnResult ); } catch (ExpectationFailedException $e) { $this->fail($other, $description, $e->getComparisonFailure()); } } public function count(): int { return count($this->innerConstraint); } protected function innerConstraint(): Constraint { return $this->innerConstraint; } } value = $value; } public function toString(): string { return 'is less than ' . $this->exporter()->export($this->value); } protected function matches($other): bool { return $this->value > $other; } } prefix = $prefix; } public function toString(): string { return 'starts with "' . $this->prefix . '"'; } protected function matches($other): bool { return strpos((string) $other, $this->prefix) === 0; } } value = $value; $this->delta = $delta; $this->canonicalize = $canonicalize; $this->ignoreCase = $ignoreCase; } public function evaluate($other, string $description = '', bool $returnResult = false) { if ($this->value === $other) { return true; } $comparatorFactory = ComparatorFactory::getInstance(); try { $comparator = $comparatorFactory->getComparatorFor( $this->value, $other ); $comparator->assertEquals( $this->value, $other, $this->delta, $this->canonicalize, $this->ignoreCase ); } catch (ComparisonFailure $f) { if ($returnResult) { return false; } throw new ExpectationFailedException( trim($description . "\n" . $f->getMessage()), $f ); } return true; } public function toString(): string { $delta = ''; if (is_string($this->value)) { if (strpos($this->value, "\n") !== false) { return 'is equal to '; } return sprintf( "is equal to '%s'", $this->value ); } if ($this->delta != 0) { $delta = sprintf( ' with delta <%F>', $this->delta ); } return sprintf( 'is equal to %s%s', $this->exporter()->export($this->value), $delta ); } } strict = $strict; $this->subset = $subset; } public function evaluate($other, string $description = '', bool $returnResult = false) { $other = $this->toArray($other); $this->subset = $this->toArray($this->subset); $patched = array_replace_recursive($other, $this->subset); if ($this->strict) { $result = $other === $patched; } else { $result = $other == $patched; } if ($returnResult) { return $result; } if (!$result) { $f = new ComparisonFailure( $patched, $other, var_export($patched, true), var_export($other, true) ); $this->fail($other, $description, $f); } } public function toString(): string { return 'has the subset ' . $this->exporter()->export($this->subset); } protected function failureDescription($other): string { return 'an array ' . $this->toString(); } private function toArray(iterable $other): array { if (is_array($other)) { return $other; } if ($other instanceof ArrayObject) { return $other->getArrayCopy(); } if ($other instanceof Traversable) { return iterator_to_array($other); } return (array) $other; } } createPatternFromFormat( $this->convertNewlines($string) ) ); $this->string = $string; } protected function matches($other): bool { return parent::matches( $this->convertNewlines($other) ); } protected function failureDescription($other): string { return 'string matches format description'; } protected function additionalFailureDescription($other): string { $from = explode("\n", $this->string); $to = explode("\n", $this->convertNewlines($other)); foreach ($from as $index => $line) { if (isset($to[$index]) && $line !== $to[$index]) { $line = $this->createPatternFromFormat($line); if (preg_match($line, $to[$index]) > 0) { $from[$index] = $to[$index]; } } } $this->string = implode("\n", $from); $other = implode("\n", $to); return (new Differ("--- Expected\n+++ Actual\n"))->diff($this->string, $other); } private function createPatternFromFormat(string $string): string { $string = strtr( preg_quote($string, '/'), [ '%%' => '%', '%e' => '\\' . DIRECTORY_SEPARATOR, '%s' => '[^\r\n]+', '%S' => '[^\r\n]*', '%a' => '.+', '%A' => '.*', '%w' => '\s*', '%i' => '[+-]?\d+', '%d' => '\d+', '%x' => '[0-9a-fA-F]+', '%f' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', '%c' => '.', ] ); return '/^' . $string . '$/s'; } private function convertNewlines($text): string { return preg_replace('/\r\n/', "\n", $text); } } constraints = array_values($constraints); return $constraint; } public function setConstraints(array $constraints): void { $this->constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { $constraint = new IsEqual( $constraint ); } $this->constraints[] = $constraint; } } public function evaluate($other, string $description = '', bool $returnResult = false) { $success = false; foreach ($this->constraints as $constraint) { if ($constraint->evaluate($other, $description, true)) { $success = true; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString(): string { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' or '; } $text .= $constraint->toString(); } return $text; } public function count(): int { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } getName(); if (!$theClass->isInstantiable()) { return new WarningTestCase( sprintf('Cannot instantiate class "%s".', $className) ); } $backupSettings = TestUtil::getBackupSettings( $className, $methodName ); $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings( $className, $methodName ); $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings( $className, $methodName ); $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings( $className, $methodName ); $constructor = $theClass->getConstructor(); if ($constructor === null) { throw new Exception('No valid test provided.'); } $parameters = $constructor->getParameters(); if (count($parameters) < 2) { $test = $this->buildTestWithoutData($className); } else { try { $data = TestUtil::getProvidedData( $className, $methodName ); } catch (IncompleteTestError $e) { $message = sprintf( "Test for %s::%s marked incomplete by data provider\n%s", $className, $methodName, $this->throwableToString($e) ); $data = new IncompleteTestCase($className, $methodName, $message); } catch (SkippedTestError $e) { $message = sprintf( "Test for %s::%s skipped by data provider\n%s", $className, $methodName, $this->throwableToString($e) ); $data = new SkippedTestCase($className, $methodName, $message); } catch (Throwable $t) { $message = sprintf( "The data provider specified for %s::%s is invalid.\n%s", $className, $methodName, $this->throwableToString($t) ); $data = new WarningTestCase($message); } if (isset($data)) { $test = $this->buildDataProviderTestSuite( $methodName, $className, $data, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings ); } else { $test = $this->buildTestWithoutData($className); } } if ($test instanceof TestCase) { $test->setName($methodName); $this->configureTestCase( $test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings ); } return $test; } private function buildTestWithoutData(string $className) { return new $className; } private function buildDataProviderTestSuite( string $methodName, string $className, $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings ): DataProviderTestSuite { $dataProviderTestSuite = new DataProviderTestSuite( $className . '::' . $methodName ); $groups = TestUtil::getGroups($className, $methodName); if ($data instanceof WarningTestCase || $data instanceof SkippedTestCase || $data instanceof IncompleteTestCase) { $dataProviderTestSuite->addTest($data, $groups); } else { foreach ($data as $_dataName => $_data) { $_test = new $className($methodName, $_data, $_dataName); assert($_test instanceof TestCase); $this->configureTestCase( $_test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings ); $dataProviderTestSuite->addTest($_test, $groups); } } return $dataProviderTestSuite; } private function configureTestCase( TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings ): void { if ($runTestInSeparateProcess) { $test->setRunTestInSeparateProcess(true); if ($preserveGlobalState !== null) { $test->setPreserveGlobalState($preserveGlobalState); } } if ($runClassInSeparateProcess) { $test->setRunClassInSeparateProcess(true); if ($preserveGlobalState !== null) { $test->setPreserveGlobalState($preserveGlobalState); } } if ($backupSettings['backupGlobals'] !== null) { $test->setBackupGlobals($backupSettings['backupGlobals']); } if ($backupSettings['backupStaticAttributes'] !== null) { $test->setBackupStaticAttributes( $backupSettings['backupStaticAttributes'] ); } } private function throwableToString(Throwable $t): string { $message = $t->getMessage(); if (empty(trim($message))) { $message = ''; } if ($t instanceof InvalidDataSetException) { return sprintf( "%s\n%s", $message, Filter::getFilteredStacktrace($t) ); } return sprintf( "%s: %s\n%s", get_class($t), $message, Filter::getFilteredStacktrace($t) ); } } classCode = $classCode; $this->mockName = $mockName; $this->configurableMethods = $configurableMethods; } public function generate(): string { if (!class_exists($this->mockName, false)) { eval($this->classCode); call_user_func( [ $this->mockName, '__phpunit_initConfigurableMethods', ], ...$this->configurableMethods ); } return $this->mockName; } public function getClassCode(): string { return $this->classCode; } } methods[strtolower($method->getName())] = $method; } } public function asArray(): array { return array_values($this->methods); } public function hasMethod(string $methodName): bool { return array_key_exists(strtolower($methodName), $this->methods); } } $parameters) { if (!is_iterable($parameters)) { throw new InvalidParameterGroupException( sprintf( 'Parameter group #%d must be an array or Traversable, got %s', $index, gettype($parameters) ) ); } foreach ($parameters as $parameter) { if (!$parameter instanceof Constraint) { $parameter = new IsEqual($parameter); } $this->parameterGroups[$index][] = $parameter; } } } public function toString(): string { return 'with consecutive parameters'; } public function apply(BaseInvocation $invocation): void { $this->invocations[] = $invocation; $callIndex = count($this->invocations) - 1; $this->verifyInvocation($invocation, $callIndex); } public function verify(): void { foreach ($this->invocations as $callIndex => $invocation) { $this->verifyInvocation($invocation, $callIndex); } } private function verifyInvocation(BaseInvocation $invocation, $callIndex): void { if (!isset($this->parameterGroups[$callIndex])) { return; } if ($invocation === null) { throw new ExpectationFailedException( 'Mocked method does not exist.' ); } $parameters = $this->parameterGroups[$callIndex]; if (count($invocation->getParameters()) < count($parameters)) { throw new ExpectationFailedException( sprintf( 'Parameter count for invocation %s is too low.', $invocation->toString() ) ); } foreach ($parameters as $i => $parameter) { $parameter->evaluate( $invocation->getParameters()[$i], sprintf( 'Parameter %s for invocation #%d %s does not match expected ' . 'value.', $i, $callIndex, $invocation->toString() ) ); } } } getInvocationCount(); if ($count < 1) { throw new ExpectationFailedException( 'Expected invocation at least once but it never occurred.' ); } } public function matches(BaseInvocation $invocation): bool { return true; } protected function invokedDo(BaseInvocation $invocation): void { } } expectedCount = $expectedCount; } public function isNever(): bool { return $this->expectedCount === 0; } public function toString(): string { return 'invoked ' . $this->expectedCount . ' time(s)'; } public function matches(BaseInvocation $invocation): bool { return true; } public function verify(): void { $count = $this->getInvocationCount(); if ($count !== $this->expectedCount) { throw new ExpectationFailedException( sprintf( 'Method was expected to be called %d times, ' . 'actually called %d times.', $this->expectedCount, $count ) ); } } protected function invokedDo(BaseInvocation $invocation): void { $count = $this->getInvocationCount(); if ($count > $this->expectedCount) { $message = $invocation->toString() . ' '; switch ($this->expectedCount) { case 0: $message .= 'was not expected to be called.'; break; case 1: $message .= 'was not expected to be called more than once.'; break; default: $message .= sprintf( 'was not expected to be called more than %d times.', $this->expectedCount ); } throw new ExpectationFailedException($message); } } } parameters[] = $parameter; } } public function toString(): string { $text = 'with parameter'; foreach ($this->parameters as $index => $parameter) { if ($index > 0) { $text .= ' and'; } $text .= ' ' . $index . ' ' . $parameter->toString(); } return $text; } public function apply(BaseInvocation $invocation): void { $this->invocation = $invocation; $this->parameterVerificationResult = null; try { $this->parameterVerificationResult = $this->doVerify(); } catch (ExpectationFailedException $e) { $this->parameterVerificationResult = $e; throw $this->parameterVerificationResult; } } public function verify(): void { $this->doVerify(); } private function doVerify(): bool { if (isset($this->parameterVerificationResult)) { return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); } if ($this->invocation === null) { throw new ExpectationFailedException('Mocked method does not exist.'); } if (count($this->invocation->getParameters()) < count($this->parameters)) { $message = 'Parameter count for invocation %s is too low.'; if (count($this->parameters) === 1 && get_class($this->parameters[0]) === IsAnything::class) { $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; } throw new ExpectationFailedException( sprintf($message, $this->invocation->toString()) ); } foreach ($this->parameters as $i => $parameter) { $parameter->evaluate( $this->invocation->getParameters()[$i], sprintf( 'Parameter %s for invocation %s does not match expected ' . 'value.', $i, $this->invocation->toString() ) ); } return true; } private function guardAgainstDuplicateEvaluationOfParameterConstraints(): bool { if ($this->parameterVerificationResult instanceof ExpectationFailedException) { throw $this->parameterVerificationResult; } return (bool) $this->parameterVerificationResult; } } requiredInvocations = $requiredInvocations; } public function toString(): string { return 'invoked at least ' . $this->requiredInvocations . ' times'; } public function verify(): void { $count = $this->getInvocationCount(); if ($count < $this->requiredInvocations) { throw new ExpectationFailedException( 'Expected invocation at least ' . $this->requiredInvocations . ' times but it occurred ' . $count . ' time(s).' ); } } public function matches(BaseInvocation $invocation): bool { return true; } protected function invokedDo(BaseInvocation $invocation): void { } } invocations); } public function hasBeenInvoked(): bool { return count($this->invocations) > 0; } final public function invoked(BaseInvocation $invocation) { $this->invocations[] = $invocation; return $this->invokedDo($invocation); } abstract public function matches(BaseInvocation $invocation): bool; abstract protected function invokedDo(BaseInvocation $invocation); } sequenceIndex = $sequenceIndex; } public function toString(): string { return 'invoked at sequence index ' . $this->sequenceIndex; } public function matches(BaseInvocation $invocation): bool { $this->currentIndex++; return $this->currentIndex == $this->sequenceIndex; } public function verify(): void { if ($this->currentIndex < $this->sequenceIndex) { throw new ExpectationFailedException( sprintf( 'The expected invocation at index %s was never reached.', $this->sequenceIndex ) ); } } protected function invokedDo(BaseInvocation $invocation): void { } } allowedInvocations = $allowedInvocations; } public function toString(): string { return 'invoked at most ' . $this->allowedInvocations . ' times'; } public function verify(): void { $count = $this->getInvocationCount(); if ($count > $this->allowedInvocations) { throw new ExpectationFailedException( 'Expected invocation at most ' . $this->allowedInvocations . ' times but it occurred ' . $count . ' time(s).' ); } } public function matches(BaseInvocation $invocation): bool { return true; } protected function invokedDo(BaseInvocation $invocation): void { } } constraint = $constraint; } public function toString(): string { return 'method name ' . $this->constraint->toString(); } public function matches(BaseInvocation $invocation): bool { return $this->matchesName($invocation->getMethodName()); } public function matchesName(string $methodName): bool { return $this->constraint->evaluate($methodName, '', true); } } {arguments_count}) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; } } $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( '{class_name}', '{method_name}', $__phpunit_arguments, '{return_declaration}', $this, {clone_arguments}, true ) ); call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); } {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} {{deprecation} $__phpunit_arguments = [{arguments_call}]; $__phpunit_count = func_num_args(); if ($__phpunit_count > {arguments_count}) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; } } $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( '{class_name}', '{method_name}', $__phpunit_arguments, '{return_declaration}', $this, {clone_arguments} ) ); return $__phpunit_result; } {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} {{deprecation} $__phpunit_arguments = [{arguments_call}]; $__phpunit_count = func_num_args(); if ($__phpunit_count > {arguments_count}) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; } } $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( '{class_name}', '{method_name}', $__phpunit_arguments, '{return_declaration}', $this, {clone_arguments} ) ); } declare(strict_types=1); {prologue}class {class_name} { use {trait_name}; } {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} { throw new \PHPUnit\Framework\MockObject\BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object'); } @trigger_error({deprecation}, E_USER_DEPRECATED); declare(strict_types=1); {namespace}class {class_name} extends \SoapClient { public function __construct($wsdl, array $options) { parent::__construct('{wsdl}', $options); } {methods}} public function {method_name}({arguments}) { } {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} { $__phpunit_arguments = [{arguments_call}]; $__phpunit_count = func_num_args(); if ($__phpunit_count > {arguments_count}) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; } } $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( '{class_name}', '{method_name}', $__phpunit_arguments, '{return_declaration}', $this, {clone_arguments}, true ) ); return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); } declare(strict_types=1); {prologue}{class_declaration} { use \PHPUnit\Framework\MockObject\Api;{method}{clone} {mocked_methods}}{epilogue} isPrivate()) { $modifier = 'private'; } elseif ($method->isProtected()) { $modifier = 'protected'; } else { $modifier = 'public'; } if ($method->isStatic()) { $modifier .= ' static'; } if ($method->returnsReference()) { $reference = '&'; } else { $reference = ''; } $docComment = $method->getDocComment(); if (is_string($docComment) && preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation)) { $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); } else { $deprecation = null; } return new self( $method->getDeclaringClass()->getName(), $method->getName(), $cloneArguments, $modifier, self::getMethodParametersForDeclaration($method), self::getMethodParametersForCall($method), self::deriveReturnType($method), $reference, $callOriginalMethod, $method->isStatic(), $deprecation ); } public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments): self { return new self( $fullClassName, $methodName, $cloneArguments, 'public', '', '', new UnknownType, '', false, false, null ); } public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) { $this->className = $className; $this->methodName = $methodName; $this->cloneArguments = $cloneArguments; $this->modifier = $modifier; $this->argumentsForDeclaration = $argumentsForDeclaration; $this->argumentsForCall = $argumentsForCall; $this->returnType = $returnType; $this->reference = $reference; $this->callOriginalMethod = $callOriginalMethod; $this->static = $static; $this->deprecation = $deprecation; } public function getName(): string { return $this->methodName; } public function generateCode(): string { if ($this->static) { $templateFile = 'mocked_static_method.tpl'; } elseif ($this->returnType instanceof VoidType) { $templateFile = sprintf( '%s_method_void.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked' ); } else { $templateFile = sprintf( '%s_method.tpl', $this->callOriginalMethod ? 'proxied' : 'mocked' ); } $deprecation = $this->deprecation; if (null !== $this->deprecation) { $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; $deprecationTemplate = $this->getTemplate('deprecation.tpl'); $deprecationTemplate->setVar([ 'deprecation' => var_export($deprecation, true), ]); $deprecation = $deprecationTemplate->render(); } $returnTypeDeclaration = str_replace( '?mixed', 'mixed', $this->returnType->getReturnTypeDeclaration() ); $template = $this->getTemplate($templateFile); $template->setVar( [ 'arguments_decl' => $this->argumentsForDeclaration, 'arguments_call' => $this->argumentsForCall, 'return_declaration' => $returnTypeDeclaration, 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, 'class_name' => $this->className, 'method_name' => $this->methodName, 'modifier' => $this->modifier, 'reference' => $this->reference, 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', 'deprecation' => $deprecation, ] ); return $template->render(); } public function getReturnType(): Type { return $this->returnType; } private function getTemplate(string $template): Text_Template { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; if (!isset(self::$templates[$filename])) { self::$templates[$filename] = new Text_Template($filename); } return self::$templates[$filename]; } private static function getMethodParametersForDeclaration(ReflectionMethod $method): string { $parameters = []; foreach ($method->getParameters() as $i => $parameter) { $name = '$' . $parameter->getName(); if ($name === '$' || $name === '$...') { $name = '$arg' . $i; } $nullable = ''; $default = ''; $reference = ''; $typeDeclaration = ''; $type = null; $typeName = null; if ($parameter->hasType()) { $type = $parameter->getType(); if ($type instanceof ReflectionNamedType) { $typeName = $type->getName(); } } if ($parameter->isVariadic()) { $name = '...' . $name; } elseif ($parameter->isDefaultValueAvailable()) { $default = ' = ' . var_export($parameter->getDefaultValue(), true); } elseif ($parameter->isOptional()) { $default = ' = null'; } if ($type !== null) { if ($typeName !== 'mixed' && $parameter->allowsNull()) { $nullable = '?'; } if ($typeName === 'self') { $typeDeclaration = $method->getDeclaringClass()->getName() . ' '; } elseif ($typeName !== null) { $typeDeclaration = $typeName . ' '; } } if ($parameter->isPassedByReference()) { $reference = '&'; } $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; } return implode(', ', $parameters); } private static function getMethodParametersForCall(ReflectionMethod $method): string { $parameters = []; foreach ($method->getParameters() as $i => $parameter) { $name = '$' . $parameter->getName(); if ($name === '$' || $name === '$...') { $name = '$arg' . $i; } if ($parameter->isVariadic()) { continue; } if ($parameter->isPassedByReference()) { $parameters[] = '&' . $name; } else { $parameters[] = $name; } } return implode(', ', $parameters); } private static function deriveReturnType(ReflectionMethod $method): Type { $returnType = self::reflectionMethodGetReturnType($method); if ($returnType === null) { return new UnknownType; } if ($returnType instanceof ReflectionNamedType && $returnType->getName() === 'self') { return ObjectType::fromName($method->getDeclaringClass()->getName(), $returnType->allowsNull()); } if ($returnType instanceof ReflectionNamedType && $returnType->getName() === 'parent') { $parentClass = $method->getDeclaringClass()->getParentClass(); if ($parentClass === false) { throw new RuntimeException( sprintf( 'Cannot mock %s::%s because "parent" return type declaration is used but %s does not have a parent class', $method->getDeclaringClass()->getName(), $method->getName(), $method->getDeclaringClass()->getName() ) ); } return ObjectType::fromName($parentClass->getName(), $returnType->allowsNull()); } return Type::fromName($returnType->getName(), $returnType->allowsNull()); } private static function reflectionMethodGetReturnType(ReflectionMethod $method): ?ReflectionType { if ($method->hasReturnType()) { return $method->getReturnType(); } if (!method_exists($method, 'getTentativeReturnType')) { return null; } return $method->getTentativeReturnType(); } } invocationHandler = $handler; $this->matcher = $matcher; $this->configurableMethods = $configurableMethods; } public function id($id): self { $this->invocationHandler->registerMatcher($id, $this->matcher); return $this; } public function will(Stub $stub): Identity { $this->matcher->setStub($stub); return $this; } public function willReturn($value, ...$nextValues): self { if (count($nextValues) === 0) { $this->ensureTypeOfReturnValues([$value]); $stub = $value instanceof Stub ? $value : new ReturnStub($value); } else { $values = array_merge([$value], $nextValues); $this->ensureTypeOfReturnValues($values); $stub = new ConsecutiveCalls($values); } return $this->will($stub); } public function willReturnReference(&$reference): self { $stub = new ReturnReference($reference); return $this->will($stub); } public function willReturnMap(array $valueMap): self { $stub = new ReturnValueMap($valueMap); return $this->will($stub); } public function willReturnArgument($argumentIndex): self { $stub = new ReturnArgument($argumentIndex); return $this->will($stub); } public function willReturnCallback($callback): self { $stub = new ReturnCallback($callback); return $this->will($stub); } public function willReturnSelf(): self { $stub = new ReturnSelf; return $this->will($stub); } public function willReturnOnConsecutiveCalls(...$values): self { $stub = new ConsecutiveCalls($values); return $this->will($stub); } public function willThrowException(Throwable $exception): self { $stub = new Exception($exception); return $this->will($stub); } public function after($id): self { $this->matcher->setAfterMatchBuilderId($id); return $this; } public function with(...$arguments): self { $this->canDefineParameters(); $this->matcher->setParametersRule(new Rule\Parameters($arguments)); return $this; } public function withConsecutive(...$arguments): self { $this->canDefineParameters(); $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); return $this; } public function withAnyParameters(): self { $this->canDefineParameters(); $this->matcher->setParametersRule(new Rule\AnyParameters); return $this; } public function method($constraint): self { if ($this->matcher->hasMethodNameRule()) { throw new RuntimeException( 'Rule for method name is already defined, cannot redefine' ); } $configurableMethodNames = array_map( static function (ConfigurableMethod $configurable) { return strtolower($configurable->getName()); }, $this->configurableMethods ); if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, true)) { throw new RuntimeException( sprintf( 'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', $constraint ) ); } $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); return $this; } private function canDefineParameters(): void { if (!$this->matcher->hasMethodNameRule()) { throw new RuntimeException( 'Rule for method name is not defined, cannot define rule for parameters ' . 'without one' ); } if ($this->matcher->hasParametersRule()) { throw new RuntimeException( 'Rule for parameters is already defined, cannot redefine' ); } } private function getConfiguredMethod(): ?ConfigurableMethod { $configuredMethod = null; foreach ($this->configurableMethods as $configurableMethod) { if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { if ($configuredMethod !== null) { return null; } $configuredMethod = $configurableMethod; } } return $configuredMethod; } private function ensureTypeOfReturnValues(array $values): void { $configuredMethod = $this->getConfiguredMethod(); if ($configuredMethod === null) { return; } foreach ($values as $value) { if (!$configuredMethod->mayReturn($value)) { throw new IncompatibleReturnValueException( sprintf( 'Method %s may not return value of type %s, its return declaration is "%s"', $configuredMethod->getName(), is_object($value) ? get_class($value) : gettype($value), $configuredMethod->getReturnTypeDeclaration() ) ); } } } } __phpunit_originalObject = $originalObject; } public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void { $this->__phpunit_returnValueGeneration = $returnValueGeneration; } public function __phpunit_getInvocationHandler(): InvocationHandler { if ($this->__phpunit_invocationMocker === null) { $this->__phpunit_invocationMocker = new InvocationHandler( static::$__phpunit_configurableMethods, $this->__phpunit_returnValueGeneration ); } return $this->__phpunit_invocationMocker; } public function __phpunit_hasMatchers(): bool { return $this->__phpunit_getInvocationHandler()->hasMatchers(); } public function __phpunit_verify(bool $unsetInvocationMocker = true): void { $this->__phpunit_getInvocationHandler()->verify(); if ($unsetInvocationMocker) { $this->__phpunit_invocationMocker = null; } } public function expects(InvocationOrder $matcher): InvocationMockerBuilder { return $this->__phpunit_getInvocationHandler()->expects($matcher); } } expects(new AnyInvokedCount); return call_user_func_array( [$expects, 'method'], func_get_args() ); } } __phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); } } EOT; private const MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' namespace PHPUnit\Framework\MockObject; trait MockedCloneMethodWithoutReturnType { public function __clone() { $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); } } EOT; private const UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' namespace PHPUnit\Framework\MockObject; trait UnmockedCloneMethodWithVoidReturnType { public function __clone(): void { $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); parent::__clone(); } } EOT; private const UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' namespace PHPUnit\Framework\MockObject; trait UnmockedCloneMethodWithoutReturnType { public function __clone() { $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); parent::__clone(); } } EOT; private const BLACKLISTED_METHOD_NAMES = [ '__CLASS__' => true, '__DIR__' => true, '__FILE__' => true, '__FUNCTION__' => true, '__LINE__' => true, '__METHOD__' => true, '__NAMESPACE__' => true, '__TRAIT__' => true, '__clone' => true, '__halt_compiler' => true, ]; private static $cache = []; private static $templates = []; public function getMock($type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject { if (!is_array($type) && !is_string($type)) { throw InvalidArgumentException::create(1, 'array or string'); } if (!is_array($methods) && null !== $methods) { throw InvalidArgumentException::create(2, 'array'); } if ($type === 'Traversable' || $type === '\\Traversable') { $type = 'Iterator'; } if (is_array($type)) { $type = array_unique( array_map( static function ($type) { if ($type === 'Traversable' || $type === '\\Traversable' || $type === '\\Iterator') { return 'Iterator'; } return $type; }, $type ) ); } if (!$allowMockingUnknownTypes) { if (is_array($type)) { foreach ($type as $_type) { if (!class_exists($_type, $callAutoload) && !interface_exists($_type, $callAutoload)) { throw new RuntimeException( sprintf( 'Cannot stub or mock class or interface "%s" which does not exist', $_type ) ); } } } elseif (!class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { throw new RuntimeException( sprintf( 'Cannot stub or mock class or interface "%s" which does not exist', $type ) ); } } if (null !== $methods) { foreach ($methods as $method) { if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { throw new RuntimeException( sprintf( 'Cannot stub or mock method with invalid name "%s"', $method ) ); } } if ($methods !== array_unique($methods)) { throw new RuntimeException( sprintf( 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', implode(', ', $methods), implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))) ) ); } } if ($mockClassName !== '' && class_exists($mockClassName, false)) { try { $reflector = new ReflectionClass($mockClassName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$reflector->implementsInterface(MockObject::class)) { throw new RuntimeException( sprintf( 'Class "%s" already exists.', $mockClassName ) ); } } if (!$callOriginalConstructor && $callOriginalMethods) { throw new RuntimeException( 'Proxying to original methods requires invoking the original constructor' ); } $mock = $this->generate( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); return $this->getObject( $mock, $type, $callOriginalConstructor, $callAutoload, $arguments, $callOriginalMethods, $proxyTarget, $returnValueGeneration ); } public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject { if (class_exists($originalClassName, $callAutoload) || interface_exists($originalClassName, $callAutoload)) { try { $reflector = new ReflectionClass($originalClassName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $methods = $mockedMethods; foreach ($reflector->getMethods() as $method) { if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], true)) { $methods[] = $method->getName(); } } if (empty($methods)) { $methods = null; } return $this->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); } throw new RuntimeException( sprintf('Class "%s" does not exist.', $originalClassName) ); } public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject { if (!trait_exists($traitName, $callAutoload)) { throw new RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, '', 'Trait_' ); $classTemplate = $this->getTemplate('trait_class.tpl'); $classTemplate->setVar( [ 'prologue' => 'abstract ', 'class_name' => $className['className'], 'trait_name' => $traitName, ] ); $mockTrait = new MockTrait($classTemplate->render(), $className['className']); $mockTrait->generate(); return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); } public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = true, bool $callOriginalConstructor = false, array $arguments = []): object { if (!trait_exists($traitName, $callAutoload)) { throw new RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, $traitClassName, 'Trait_' ); $classTemplate = $this->getTemplate('trait_class.tpl'); $classTemplate->setVar( [ 'prologue' => '', 'class_name' => $className['className'], 'trait_name' => $traitName, ] ); return $this->getObject( new MockTrait( $classTemplate->render(), $className['className'] ), '', $callOriginalConstructor, $callAutoload, $arguments ); } public function generate($type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass { if (is_array($type)) { sort($type); } if ($mockClassName !== '') { return $this->generateMock( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); } $key = md5( is_array($type) ? implode('_', $type) : $type . serialize($methods) . serialize($callOriginalClone) . serialize($cloneArguments) . serialize($callOriginalMethods) ); if (!isset(self::$cache[$key])) { self::$cache[$key] = $this->generateMock( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); } return self::$cache[$key]; } public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []): string { if (!extension_loaded('soap')) { throw new RuntimeException( 'The SOAP extension is required to generate a mock object from WSDL.' ); } $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); try { $client = new SoapClient($wsdlFile, $options); $_methods = array_unique($client->__getFunctions()); unset($client); } catch (SoapFault $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } sort($_methods); $methodTemplate = $this->getTemplate('wsdl_method.tpl'); $methodsBuffer = ''; foreach ($_methods as $method) { preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(/', $method, $matches, PREG_OFFSET_CAPTURE); $lastFunction = array_pop($matches[0]); $nameStart = $lastFunction[1]; $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; $name = str_replace('(', '', $lastFunction[0]); if (empty($methods) || in_array($name, $methods, true)) { $args = explode( ',', str_replace(')', '', substr($method, $nameEnd + 1)) ); foreach (range(0, count($args) - 1) as $i) { $parameterStart = strpos($args[$i], '$'); if (!$parameterStart) { continue; } $args[$i] = substr($args[$i], $parameterStart); } $methodTemplate->setVar( [ 'method_name' => $name, 'arguments' => implode(', ', $args), ] ); $methodsBuffer .= $methodTemplate->render(); } } $optionsBuffer = '['; foreach ($options as $key => $value) { $optionsBuffer .= $key . ' => ' . $value; } $optionsBuffer .= ']'; $classTemplate = $this->getTemplate('wsdl_class.tpl'); $namespace = ''; if (strpos($className, '\\') !== false) { $parts = explode('\\', $className); $className = array_pop($parts); $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; } $classTemplate->setVar( [ 'namespace' => $namespace, 'class_name' => $className, 'wsdl' => $wsdlFile, 'options' => $optionsBuffer, 'methods' => $methodsBuffer, ] ); return $classTemplate->render(); } public function getClassMethods(string $className): array { try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $methods = []; foreach ($class->getMethods() as $method) { if ($method->isPublic() || $method->isAbstract()) { $methods[] = $method->getName(); } } return $methods; } public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments): array { try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $methods = []; foreach ($class->getMethods() as $method) { if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { $methods[] = MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); } } return $methods; } public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments): array { try { $class = new ReflectionClass($interfaceName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $methods = []; foreach ($class->getMethods() as $method) { $methods[] = MockMethod::fromReflection($method, false, $cloneArguments); } return $methods; } private function userDefinedInterfaceMethods(string $interfaceName): array { try { $interface = new ReflectionClass($interfaceName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $methods = []; foreach ($interface->getMethods() as $method) { if (!$method->isUserDefined()) { continue; } $methods[] = $method; } return $methods; } private function getObject(MockType $mockClass, $type = '', bool $callOriginalConstructor = false, bool $callAutoload = false, array $arguments = [], bool $callOriginalMethods = false, object $proxyTarget = null, bool $returnValueGeneration = true) { $className = $mockClass->generate(); if ($callOriginalConstructor) { if (count($arguments) === 0) { $object = new $className; } else { try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $object = $class->newInstanceArgs($arguments); } } else { try { $object = (new Instantiator)->instantiate($className); } catch (InstantiatorException $exception) { throw new RuntimeException($exception->getMessage()); } } if ($callOriginalMethods) { if (!is_object($proxyTarget)) { if (count($arguments) === 0) { $proxyTarget = new $type; } else { try { $class = new ReflectionClass($type); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } $proxyTarget = $class->newInstanceArgs($arguments); } } $object->__phpunit_setOriginalObject($proxyTarget); } if ($object instanceof MockObject) { $object->__phpunit_setReturnValueGeneration($returnValueGeneration); } return $object; } private function generateMock($type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass { $classTemplate = $this->getTemplate('mocked_class.tpl'); $additionalInterfaces = []; $mockedCloneMethod = false; $unmockedCloneMethod = false; $isClass = false; $isInterface = false; $class = null; $mockMethods = new MockMethodSet; if (is_array($type)) { $interfaceMethods = []; foreach ($type as $_type) { if (!interface_exists($_type, $callAutoload)) { throw new RuntimeException( sprintf( 'Interface "%s" does not exist.', $_type ) ); } $additionalInterfaces[] = $_type; try { $typeClass = new ReflectionClass($_type); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } foreach ($this->getClassMethods($_type) as $method) { if (in_array($method, $interfaceMethods, true)) { throw new RuntimeException( sprintf( 'Duplicate method "%s" not allowed.', $method ) ); } try { $methodReflection = $typeClass->getMethod($method); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if ($this->canMockMethod($methodReflection)) { $mockMethods->addMethods( MockMethod::fromReflection($methodReflection, $callOriginalMethods, $cloneArguments) ); $interfaceMethods[] = $method; } } } unset($interfaceMethods); } $mockClassName = $this->generateClassName( $type, $mockClassName, 'Mock_' ); if (class_exists($mockClassName['fullClassName'], $callAutoload)) { $isClass = true; } elseif (interface_exists($mockClassName['fullClassName'], $callAutoload)) { $isInterface = true; } if (!$isClass && !$isInterface) { $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; if (!empty($mockClassName['namespaceName'])) { $prologue = 'namespace ' . $mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; $epilogue = "\n\n}"; } $mockedCloneMethod = true; } else { try { $class = new ReflectionClass($mockClassName['fullClassName']); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if ($class->isFinal()) { throw new RuntimeException( sprintf( 'Class "%s" is declared "final" and cannot be mocked.', $mockClassName['fullClassName'] ) ); } if ($isInterface && $class->implementsInterface(Throwable::class)) { $actualClassName = Exception::class; $additionalInterfaces[] = $class->getName(); $isInterface = false; try { $class = new ReflectionClass($actualClassName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } foreach ($this->userDefinedInterfaceMethods($mockClassName['fullClassName']) as $method) { $methodName = $method->getName(); if ($class->hasMethod($methodName)) { try { $classMethod = $class->getMethod($methodName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$this->canMockMethod($classMethod)) { continue; } } $mockMethods->addMethods( MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments) ); } $mockClassName = $this->generateClassName( $actualClassName, $mockClassName['className'], 'Mock_' ); } if ($isInterface && $class->implementsInterface(Traversable::class) && !$class->implementsInterface(Iterator::class) && !$class->implementsInterface(IteratorAggregate::class)) { $additionalInterfaces[] = Iterator::class; $mockMethods->addMethods( ...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments) ); } if ($class->hasMethod('__clone')) { try { $cloneMethod = $class->getMethod('__clone'); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$cloneMethod->isFinal()) { if ($callOriginalClone && !$isInterface) { $unmockedCloneMethod = true; } else { $mockedCloneMethod = true; } } } else { $mockedCloneMethod = true; } } if ($isClass && $explicitMethods === []) { $mockMethods->addMethods( ...$this->mockClassMethods($mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments) ); } if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { $mockMethods->addMethods( ...$this->mockInterfaceMethods($mockClassName['fullClassName'], $cloneArguments) ); } if (is_array($explicitMethods)) { foreach ($explicitMethods as $methodName) { if ($class !== null && $class->hasMethod($methodName)) { try { $method = $class->getMethod($methodName); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } if ($this->canMockMethod($method)) { $mockMethods->addMethods( MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments) ); } } else { $mockMethods->addMethods( MockMethod::fromName( $mockClassName['fullClassName'], $methodName, $cloneArguments ) ); } } } $mockedMethods = ''; $configurable = []; foreach ($mockMethods->asArray() as $mockMethod) { $mockedMethods .= $mockMethod->generateCode(); $configurable[] = new ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); } $method = ''; if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { $method = PHP_EOL . ' use \PHPUnit\Framework\MockObject\Method;'; } $cloneTrait = ''; if ($mockedCloneMethod) { $cloneTrait = $this->mockedCloneMethod(); } if ($unmockedCloneMethod) { $cloneTrait = $this->unmockedCloneMethod(); } $classTemplate->setVar( [ 'prologue' => $prologue ?? '', 'epilogue' => $epilogue ?? '', 'class_declaration' => $this->generateMockClassDeclaration( $mockClassName, $isInterface, $additionalInterfaces ), 'clone' => $cloneTrait, 'mock_class_name' => $mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $method, ] ); return new MockClass( $classTemplate->render(), $mockClassName['className'], $configurable ); } private function generateClassName($type, string $className, string $prefix): array { if (is_array($type)) { $type = implode('_', $type); } if ($type[0] === '\\') { $type = substr($type, 1); } $classNameParts = explode('\\', $type); if (count($classNameParts) > 1) { $type = array_pop($classNameParts); $namespaceName = implode('\\', $classNameParts); $fullClassName = $namespaceName . '\\' . $type; } else { $namespaceName = ''; $fullClassName = $type; } if ($className === '') { do { $className = $prefix . $type . '_' . substr(md5((string) mt_rand()), 0, 8); } while (class_exists($className, false)); } return [ 'className' => $className, 'originalClassName' => $type, 'fullClassName' => $fullClassName, 'namespaceName' => $namespaceName, ]; } private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []): string { $buffer = 'class '; $additionalInterfaces[] = MockObject::class; $interfaces = implode(', ', $additionalInterfaces); if ($isInterface) { $buffer .= sprintf( '%s implements %s', $mockClassName['className'], $interfaces ); if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { $buffer .= ', '; if (!empty($mockClassName['namespaceName'])) { $buffer .= $mockClassName['namespaceName'] . '\\'; } $buffer .= $mockClassName['originalClassName']; } } else { $buffer .= sprintf( '%s extends %s%s implements %s', $mockClassName['className'], !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', $mockClassName['originalClassName'], $interfaces ); } return $buffer; } private function canMockMethod(ReflectionMethod $method): bool { return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())); } private function isMethodNameBlacklisted(string $name): bool { return isset(self::BLACKLISTED_METHOD_NAMES[$name]); } private function getTemplate(string $template): Text_Template { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; if (!isset(self::$templates[$filename])) { self::$templates[$filename] = new Text_Template($filename); } return self::$templates[$filename]; } private function isConstructor(ReflectionMethod $method): bool { $methodName = strtolower($method->getName()); if ($methodName === '__construct') { return true; } if (PHP_MAJOR_VERSION >= 8) { return false; } $className = strtolower($method->getDeclaringClass()->getName()); return $methodName === $className; } private function mockedCloneMethod(): string { if (PHP_MAJOR_VERSION >= 8) { if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType')) { eval(self::MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); } return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType;'; } if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType')) { eval(self::MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); } return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType;'; } private function unmockedCloneMethod(): string { if (PHP_MAJOR_VERSION >= 8) { if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType')) { eval(self::UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); } return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType;'; } if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType')) { eval(self::UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); } return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType;'; } } testCase = $testCase; $this->type = $type; $this->generator = new Generator; } public function getMock(): MockObject { $object = $this->generator->getMock( $this->type, !$this->emptyMethodsArray ? $this->methods : null, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->cloneArguments, $this->callOriginalMethods, $this->proxyTarget, $this->allowMockingUnknownTypes, $this->returnValueGeneration ); $this->testCase->registerMockObject($object); return $object; } public function getMockForAbstractClass(): MockObject { $object = $this->generator->getMockForAbstractClass( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); $this->testCase->registerMockObject($object); return $object; } public function getMockForTrait(): MockObject { $object = $this->generator->getMockForTrait( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); $this->testCase->registerMockObject($object); return $object; } public function setMethods(?array $methods = null): self { if ($methods === null) { $this->methods = $methods; } else { $this->methods = array_merge($this->methods ?? [], $methods); } return $this; } public function onlyMethods(array $methods): self { if (empty($methods)) { $this->emptyMethodsArray = true; return $this; } try { $reflector = new ReflectionClass($this->type); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } foreach ($methods as $method) { if (!$reflector->hasMethod($method)) { throw new RuntimeException( sprintf( 'Trying to set mock method "%s" with onlyMethods, but it does not exist in class "%s". Use addMethods() for methods that don\'t exist in the class.', $method, $this->type ) ); } } $this->methods = array_merge($this->methods ?? [], $methods); return $this; } public function addMethods(array $methods): self { if (empty($methods)) { $this->emptyMethodsArray = true; return $this; } try { $reflector = new ReflectionClass($this->type); } catch (ReflectionException $e) { throw new RuntimeException( $e->getMessage(), (int) $e->getCode(), $e ); } foreach ($methods as $method) { if ($reflector->hasMethod($method)) { throw new RuntimeException( sprintf( 'Trying to set mock method "%s" with addMethods(), but it exists in class "%s". Use onlyMethods() for methods that exist in the class.', $method, $this->type ) ); } } $this->methods = array_merge($this->methods ?? [], $methods); return $this; } public function setMethodsExcept(array $methods = []): self { return $this->setMethods( array_diff( $this->generator->getClassMethods($this->type), $methods ) ); } public function setConstructorArgs(array $args): self { $this->constructorArgs = $args; return $this; } public function setMockClassName(string $name): self { $this->mockClassName = $name; return $this; } public function disableOriginalConstructor(): self { $this->originalConstructor = false; return $this; } public function enableOriginalConstructor(): self { $this->originalConstructor = true; return $this; } public function disableOriginalClone(): self { $this->originalClone = false; return $this; } public function enableOriginalClone(): self { $this->originalClone = true; return $this; } public function disableAutoload(): self { $this->autoload = false; return $this; } public function enableAutoload(): self { $this->autoload = true; return $this; } public function disableArgumentCloning(): self { $this->cloneArguments = false; return $this; } public function enableArgumentCloning(): self { $this->cloneArguments = true; return $this; } public function enableProxyingToOriginalMethods(): self { $this->callOriginalMethods = true; return $this; } public function disableProxyingToOriginalMethods(): self { $this->callOriginalMethods = false; $this->proxyTarget = null; return $this; } public function setProxyTarget(object $object): self { $this->proxyTarget = $object; return $this; } public function allowMockingUnknownTypes(): self { $this->allowMockingUnknownTypes = true; return $this; } public function disallowMockingUnknownTypes(): self { $this->allowMockingUnknownTypes = false; return $this; } public function enableAutoReturnValueGeneration(): self { $this->returnValueGeneration = true; return $this; } public function disableAutoReturnValueGeneration(): self { $this->returnValueGeneration = false; return $this; } } className = $className; $this->methodName = $methodName; $this->parameters = $parameters; $this->object = $object; $this->proxiedCall = $proxiedCall; $returnType = ltrim($returnType, ': '); if (strtolower($methodName) === '__tostring') { $returnType = 'string'; } if (strpos($returnType, '?') === 0) { $returnType = substr($returnType, 1); $this->isReturnTypeNullable = true; } $this->returnType = $returnType; if (!$cloneObjects) { return; } foreach ($this->parameters as $key => $value) { if (is_object($value)) { $this->parameters[$key] = $this->cloneObject($value); } } } public function getClassName(): string { return $this->className; } public function getMethodName(): string { return $this->methodName; } public function getParameters(): array { return $this->parameters; } public function generateReturnValue() { if ($this->isReturnTypeNullable || $this->proxiedCall) { return; } switch (strtolower($this->returnType)) { case '': case 'void': return; case 'string': return ''; case 'float': return 0.0; case 'int': return 0; case 'bool': return false; case 'array': return []; case 'object': return new stdClass; case 'callable': case 'closure': return static function (): void { }; case 'traversable': case 'generator': case 'iterable': $generator = static function () { yield from []; }; return $generator(); default: $generator = new Generator; return $generator->getMock($this->returnType, [], [], '', false); } } public function toString(): string { $exporter = new Exporter; return sprintf( '%s::%s(%s)%s', $this->className, $this->methodName, implode( ', ', array_map( [$exporter, 'shortenedExport'], $this->parameters ) ), $this->returnType ? sprintf(': %s', $this->returnType) : '' ); } public function getObject(): object { return $this->object; } private function cloneObject(object $original): object { if (Type::isCloneable($original)) { return clone $original; } return $original; } } methodName = $methodName; } public function toString(): string { return sprintf( 'is "%s"', $this->methodName ); } protected function matches($other): bool { if (!is_string($other)) { return false; } return strtolower($this->methodName) === strtolower($other); } } callback = $callback; } public function invoke(Invocation $invocation) { return call_user_func_array($this->callback, $invocation->getParameters()); } public function toString(): string { if (is_array($this->callback)) { if (is_object($this->callback[0])) { $class = get_class($this->callback[0]); $type = '->'; } else { $class = $this->callback[0]; $type = '::'; } return sprintf( 'return result of user defined callback %s%s%s() with the ' . 'passed arguments', $class, $type, $this->callback[1] ); } return 'return result of user defined callback ' . $this->callback . ' with the passed arguments'; } } value = $value; } public function invoke(Invocation $invocation) { return $this->value; } public function toString(): string { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } exception = $exception; } public function invoke(Invocation $invocation): void { throw $this->exception; } public function toString(): string { $exporter = new Exporter; return sprintf( 'raise user-specified exception %s', $exporter->export($this->exception) ); } } stack = $stack; } public function invoke(Invocation $invocation) { $this->value = array_shift($this->stack); if ($this->value instanceof Stub) { $this->value = $this->value->invoke($invocation); } return $this->value; } public function toString(): string { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } argumentIndex = $argumentIndex; } public function invoke(Invocation $invocation) { if (isset($invocation->getParameters()[$this->argumentIndex])) { return $invocation->getParameters()[$this->argumentIndex]; } } public function toString(): string { return sprintf('return argument #%d', $this->argumentIndex); } } reference = &$reference; } public function invoke(Invocation $invocation) { return $this->reference; } public function toString(): string { $exporter = new Exporter; return sprintf( 'return user-specified reference %s', $exporter->export($this->reference) ); } } getObject(); } public function toString(): string { return 'return the current object'; } } valueMap = $valueMap; } public function invoke(Invocation $invocation) { $parameterCount = count($invocation->getParameters()); foreach ($this->valueMap as $map) { if (!is_array($map) || $parameterCount !== (count($map) - 1)) { continue; } $return = array_pop($map); if ($invocation->getParameters() === $map) { return $return; } } } public function toString(): string { return 'return value from a map'; } } configurableMethods = $configurableMethods; $this->returnValueGeneration = $returnValueGeneration; } public function hasMatchers(): bool { foreach ($this->matchers as $matcher) { if ($matcher->hasMatchers()) { return true; } } return false; } public function lookupMatcher(string $id): ?Matcher { if (isset($this->matcherMap[$id])) { return $this->matcherMap[$id]; } return null; } public function registerMatcher(string $id, Matcher $matcher): void { if (isset($this->matcherMap[$id])) { throw new RuntimeException( 'Matcher with id <' . $id . '> is already registered.' ); } $this->matcherMap[$id] = $matcher; } public function expects(InvocationOrder $rule): InvocationMocker { $matcher = new Matcher($rule); $this->addMatcher($matcher); return new InvocationMocker( $this, $matcher, ...$this->configurableMethods ); } public function invoke(Invocation $invocation) { $exception = null; $hasReturnValue = false; $returnValue = null; foreach ($this->matchers as $match) { try { if ($match->matches($invocation)) { $value = $match->invoked($invocation); if (!$hasReturnValue) { $returnValue = $value; $hasReturnValue = true; } } } catch (Exception $e) { $exception = $e; } } if ($exception !== null) { throw $exception; } if ($hasReturnValue) { return $returnValue; } if (!$this->returnValueGeneration) { $exception = new ExpectationFailedException( sprintf( 'Return value inference disabled and no expectation set up for %s::%s()', $invocation->getClassName(), $invocation->getMethodName() ) ); if (strtolower($invocation->getMethodName()) === '__tostring') { $this->deferredError = $exception; return ''; } throw $exception; } return $invocation->generateReturnValue(); } public function matches(Invocation $invocation): bool { foreach ($this->matchers as $matcher) { if (!$matcher->matches($invocation)) { return false; } } return true; } public function verify(): void { foreach ($this->matchers as $matcher) { $matcher->verify(); } if ($this->deferredError) { throw $this->deferredError; } } private function addMatcher(Matcher $matcher): void { $this->matchers[] = $matcher; } } name = $name; $this->returnType = $returnType; } public function getName(): string { return $this->name; } public function mayReturn($value): bool { if ($value === null && $this->returnType->allowsNull()) { return true; } return $this->returnType->isAssignable(Type::fromValue($value, false)); } public function getReturnTypeDeclaration(): string { return $this->returnType->getReturnTypeDeclaration(); } } classCode = $classCode; $this->mockName = $mockName; } public function generate(): string { if (!class_exists($this->mockName, false)) { eval($this->classCode); } return $this->mockName; } public function getClassCode(): string { return $this->classCode; } } invocationRule = $rule; } public function hasMatchers(): bool { return !$this->invocationRule instanceof AnyInvokedCount; } public function hasMethodNameRule(): bool { return $this->methodNameRule !== null; } public function getMethodNameRule(): MethodName { return $this->methodNameRule; } public function setMethodNameRule(MethodName $rule): void { $this->methodNameRule = $rule; } public function hasParametersRule(): bool { return $this->parametersRule !== null; } public function setParametersRule(ParametersRule $rule): void { $this->parametersRule = $rule; } public function setStub(Stub $stub): void { $this->stub = $stub; } public function setAfterMatchBuilderId(string $id): void { $this->afterMatchBuilderId = $id; } public function invoked(Invocation $invocation) { if ($this->methodNameRule === null) { throw new RuntimeException('No method rule is set'); } if ($this->afterMatchBuilderId !== null) { $matcher = $invocation->getObject() ->__phpunit_getInvocationHandler() ->lookupMatcher($this->afterMatchBuilderId); if (!$matcher) { throw new RuntimeException( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } assert($matcher instanceof self); if ($matcher->invocationRule->hasBeenInvoked()) { $this->afterMatchBuilderIsInvoked = true; } } $this->invocationRule->invoked($invocation); try { if ($this->parametersRule !== null) { $this->parametersRule->apply($invocation); } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } if ($this->stub) { return $this->stub->invoke($invocation); } return $invocation->generateReturnValue(); } public function matches(Invocation $invocation): bool { if ($this->afterMatchBuilderId !== null) { $matcher = $invocation->getObject() ->__phpunit_getInvocationHandler() ->lookupMatcher($this->afterMatchBuilderId); if (!$matcher) { throw new RuntimeException( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } assert($matcher instanceof self); if (!$matcher->invocationRule->hasBeenInvoked()) { return false; } } if ($this->methodNameRule === null) { throw new RuntimeException('No method rule is set'); } if (!$this->invocationRule->matches($invocation)) { return false; } try { if (!$this->methodNameRule->matches($invocation)) { return false; } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } return true; } public function verify(): void { if ($this->methodNameRule === null) { throw new RuntimeException('No method rule is set'); } try { $this->invocationRule->verify(); if ($this->parametersRule === null) { $this->parametersRule = new AnyParameters; } $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { $this->parametersRule->verify(); } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s.\n%s", $this->methodNameRule->toString(), $this->invocationRule->toString(), TestFailure::exceptionToString($e) ) ); } } public function toString(): string { $list = []; if ($this->invocationRule !== null) { $list[] = $this->invocationRule->toString(); } if ($this->methodNameRule !== null) { $list[] = 'where ' . $this->methodNameRule->toString(); } if ($this->parametersRule !== null) { $list[] = 'and ' . $this->parametersRule->toString(); } if ($this->afterMatchBuilderId !== null) { $list[] = 'after ' . $this->afterMatchBuilderId; } if ($this->stub !== null) { $list[] = 'will ' . $this->stub->toString(); } return implode(' ', $list); } } setName($name); } $this->data = $data; $this->dataName = $dataName; } public static function setUpBeforeClass(): void { } public static function tearDownAfterClass(): void { } protected function setUp(): void { } protected function assertPreConditions(): void { } protected function assertPostConditions(): void { } protected function tearDown(): void { } public function toString(): string { try { $class = new ReflectionClass($this); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $buffer = sprintf( '%s::%s', $class->name, $this->getName(false) ); return $buffer . $this->getDataSetAsString(); } public function count(): int { return 1; } public function getActualOutputForAssertion(): string { $this->outputRetrievedForAssertion = true; return $this->getActualOutput(); } public function expectOutputRegex(string $expectedRegex): void { $this->outputExpectedRegex = $expectedRegex; } public function expectOutputString(string $expectedString): void { $this->outputExpectedString = $expectedString; } public function expectException(string $exception): void { $this->expectedException = $exception; } public function expectExceptionCode($code): void { $this->expectedExceptionCode = $code; } public function expectExceptionMessage(string $message): void { $this->expectedExceptionMessage = $message; } public function expectExceptionMessageMatches(string $regularExpression): void { $this->expectedExceptionMessageRegExp = $regularExpression; } public function expectExceptionMessageRegExp(string $regularExpression): void { $this->deprecatedExpectExceptionMessageRegExpUsed = true; $this->expectExceptionMessageMatches($regularExpression); } public function expectExceptionObject(\Exception $exception): void { $this->expectException(get_class($exception)); $this->expectExceptionMessage($exception->getMessage()); $this->expectExceptionCode($exception->getCode()); } public function expectNotToPerformAssertions(): void { $this->doesNotPerformAssertions = true; } public function expectDeprecation(): void { $this->expectException(Deprecated::class); } public function expectDeprecationMessage(string $message): void { $this->expectExceptionMessage($message); } public function expectDeprecationMessageMatches(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } public function expectNotice(): void { $this->expectException(Notice::class); } public function expectNoticeMessage(string $message): void { $this->expectExceptionMessage($message); } public function expectNoticeMessageMatches(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } public function expectWarning(): void { $this->expectException(WarningError::class); } public function expectWarningMessage(string $message): void { $this->expectExceptionMessage($message); } public function expectWarningMessageMatches(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } public function expectError(): void { $this->expectException(Error::class); } public function expectErrorMessage(string $message): void { $this->expectExceptionMessage($message); } public function expectErrorMessageMatches(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } public function getStatus(): int { return $this->status; } public function markAsRisky(): void { $this->status = BaseTestRunner::STATUS_RISKY; } public function getStatusMessage(): string { return $this->statusMessage; } public function hasFailed(): bool { $status = $this->getStatus(); return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; } public function run(TestResult $result = null): TestResult { if ($result === null) { $result = $this->createResult(); } if (!$this instanceof WarningTestCase) { $this->setTestResultObject($result); } if (!$this instanceof WarningTestCase && !$this instanceof SkippedTestCase && !$this->handleDependencies()) { return $result; } if ($this->runInSeparateProcess()) { $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; try { $class = new ReflectionClass($this); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($runEntireClass) { $template = new Text_Template( __DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl' ); } else { $template = new Text_Template( __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl' ); } if ($this->preserveGlobalState) { $constants = GlobalState::getConstantsAsString(); $globals = GlobalState::getGlobalsAsString(); $includedFiles = GlobalState::getIncludedFilesAsString(); $iniSettings = GlobalState::getIniSettingsAsString(); } else { $constants = ''; if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; } else { $globals = ''; } $includedFiles = ''; $iniSettings = ''; } $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; if (defined('PHPUNIT_COMPOSER_INSTALL')) { $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); } else { $composerAutoload = '\'\''; } if (defined('__PHPUNIT_PHAR__')) { $phar = var_export(__PHPUNIT_PHAR__, true); } else { $phar = '\'\''; } if ($result->getCodeCoverage()) { $codeCoverageFilter = $result->getCodeCoverage()->filter(); } else { $codeCoverageFilter = null; } $data = var_export(serialize($this->data), true); $dataName = var_export($this->dataName, true); $dependencyInput = var_export(serialize($this->dependencyInput), true); $includePath = var_export(get_include_path(), true); $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true); $data = "'." . $data . ".'"; $dataName = "'.(" . $dataName . ").'"; $dependencyInput = "'." . $dependencyInput . ".'"; $includePath = "'." . $includePath . ".'"; $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; $var = [ 'composerAutoload' => $composerAutoload, 'phar' => $phar, 'filename' => $class->getFileName(), 'className' => $class->getName(), 'collectCodeCoverageInformation' => $coverage, 'data' => $data, 'dataName' => $dataName, 'dependencyInput' => $dependencyInput, 'constants' => $constants, 'globals' => $globals, 'include_path' => $includePath, 'included_files' => $includedFiles, 'iniSettings' => $iniSettings, 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, 'enforcesTimeLimit' => $enforcesTimeLimit, 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, 'codeCoverageFilter' => $codeCoverageFilter, 'configurationFilePath' => $configurationFilePath, 'name' => $this->getName(false), ]; if (!$runEntireClass) { $var['methodName'] = $this->name; } $template->setVar($var); $php = AbstractPhpProcess::factory(); $php->runTestJob($template->render(), $this, $result); } else { $result->run($this); } $this->result = null; return $result; } public function getMockBuilder($className): MockBuilder { if (!is_string($className)) { $this->addWarning('Passing an array of interface names to getMockBuilder() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); } $this->recordDoubledType($className); return new MockBuilder($this, $className); } public function registerComparator(Comparator $comparator): void { ComparatorFactory::getInstance()->register($comparator); $this->customComparators[] = $comparator; } public function setUseErrorHandler(bool $useErrorHandler): void { } public function doubledTypes(): array { return array_unique($this->doubledTypes); } public function getGroups(): array { return $this->groups; } public function setGroups(array $groups): void { $this->groups = $groups; } public function getAnnotations(): array { return TestUtil::parseTestMethodAnnotations( static::class, $this->name ); } public function getName(bool $withDataSet = true): string { if ($withDataSet) { return $this->name . $this->getDataSetAsString(false); } return $this->name; } public function getSize(): int { return TestUtil::getSize( static::class, $this->getName(false) ); } public function hasSize(): bool { return $this->getSize() !== TestUtil::UNKNOWN; } public function isSmall(): bool { return $this->getSize() === TestUtil::SMALL; } public function isMedium(): bool { return $this->getSize() === TestUtil::MEDIUM; } public function isLarge(): bool { return $this->getSize() === TestUtil::LARGE; } public function getActualOutput(): string { if (!$this->outputBufferingActive) { return $this->output; } return (string) ob_get_contents(); } public function hasOutput(): bool { if ($this->output === '') { return false; } if ($this->hasExpectationOnOutput()) { return false; } return true; } public function doesNotPerformAssertions(): bool { return $this->doesNotPerformAssertions; } public function hasExpectationOnOutput(): bool { return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; } public function getExpectedException(): ?string { return $this->expectedException; } public function getExpectedExceptionCode() { return $this->expectedExceptionCode; } public function getExpectedExceptionMessage(): ?string { return $this->expectedExceptionMessage; } public function getExpectedExceptionMessageRegExp(): ?string { return $this->expectedExceptionMessageRegExp; } public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void { $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } public function runBare(): void { $this->numAssertions = 0; $this->snapshotGlobalState(); $this->startOutputBuffering(); clearstatcache(); $currentWorkingDirectory = getcwd(); $hookMethods = TestUtil::getHookMethods(static::class); $hasMetRequirements = false; try { $this->checkRequirements(); $hasMetRequirements = true; if ($this->inIsolation) { foreach ($hookMethods['beforeClass'] as $method) { $this->{$method}(); } } $this->setExpectedExceptionFromAnnotation(); $this->setDoesNotPerformAssertionsFromAnnotation(); foreach ($hookMethods['before'] as $method) { $this->{$method}(); } $this->assertPreConditions(); $this->testResult = $this->runTest(); $this->verifyMockObjects(); $this->assertPostConditions(); if (!empty($this->warnings)) { throw new Warning( implode( "\n", array_unique($this->warnings) ) ); } $this->status = BaseTestRunner::STATUS_PASSED; } catch (IncompleteTest $e) { $this->status = BaseTestRunner::STATUS_INCOMPLETE; $this->statusMessage = $e->getMessage(); } catch (SkippedTest $e) { $this->status = BaseTestRunner::STATUS_SKIPPED; $this->statusMessage = $e->getMessage(); } catch (Warning $e) { $this->status = BaseTestRunner::STATUS_WARNING; $this->statusMessage = $e->getMessage(); } catch (AssertionFailedError $e) { $this->status = BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (PredictionException $e) { $this->status = BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (Throwable $_e) { $e = $_e; $this->status = BaseTestRunner::STATUS_ERROR; $this->statusMessage = $_e->getMessage(); } $this->mockObjects = []; $this->prophet = null; try { if ($hasMetRequirements) { foreach ($hookMethods['after'] as $method) { $this->{$method}(); } if ($this->inIsolation) { foreach ($hookMethods['afterClass'] as $method) { $this->{$method}(); } } } } catch (Throwable $_e) { $e = $e ?? $_e; } try { $this->stopOutputBuffering(); } catch (RiskyTestError $_e) { $e = $e ?? $_e; } if (isset($_e)) { $this->status = BaseTestRunner::STATUS_ERROR; $this->statusMessage = $_e->getMessage(); } clearstatcache(); if ($currentWorkingDirectory !== getcwd()) { chdir($currentWorkingDirectory); } $this->restoreGlobalState(); $this->unregisterCustomComparators(); $this->cleanupIniSettings(); $this->cleanupLocaleSettings(); libxml_clear_errors(); if (!isset($e)) { try { if ($this->outputExpectedRegex !== null) { $this->assertRegExp($this->outputExpectedRegex, $this->output); } elseif ($this->outputExpectedString !== null) { $this->assertEquals($this->outputExpectedString, $this->output); } } catch (Throwable $_e) { $e = $_e; } } if (isset($e)) { if ($e instanceof PredictionException) { $e = new AssertionFailedError($e->getMessage()); } $this->onNotSuccessfulTest($e); } } public function setName(string $name): void { $this->name = $name; } public function setDependencies(array $dependencies): void { $this->dependencies = $dependencies; } public function getDependencies(): array { return $this->dependencies; } public function hasDependencies(): bool { return count($this->dependencies) > 0; } public function setDependencyInput(array $dependencyInput): void { $this->dependencyInput = $dependencyInput; } public function getDependencyInput(): array { return $this->dependencyInput; } public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState): void { $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; } public function setBackupGlobals(?bool $backupGlobals): void { if ($this->backupGlobals === null && $backupGlobals !== null) { $this->backupGlobals = $backupGlobals; } } public function setBackupStaticAttributes(?bool $backupStaticAttributes): void { if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { $this->backupStaticAttributes = $backupStaticAttributes; } } public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void { if ($this->runTestInSeparateProcess === null) { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } } public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void { if ($this->runClassInSeparateProcess === null) { $this->runClassInSeparateProcess = $runClassInSeparateProcess; } } public function setPreserveGlobalState(bool $preserveGlobalState): void { $this->preserveGlobalState = $preserveGlobalState; } public function setInIsolation(bool $inIsolation): void { $this->inIsolation = $inIsolation; } public function isInIsolation(): bool { return $this->inIsolation; } public function getResult() { return $this->testResult; } public function setResult($result): void { $this->testResult = $result; } public function setOutputCallback(callable $callback): void { $this->outputCallback = $callback; } public function getTestResultObject(): ?TestResult { return $this->result; } public function setTestResultObject(TestResult $result): void { $this->result = $result; } public function registerMockObject(MockObject $mockObject): void { $this->mockObjects[] = $mockObject; } public function addToAssertionCount(int $count): void { $this->numAssertions += $count; } public function getNumAssertions(): int { return $this->numAssertions; } public function usesDataProvider(): bool { return !empty($this->data); } public function dataDescription(): string { return is_string($this->dataName) ? $this->dataName : ''; } public function dataName() { return $this->dataName; } public function getDataSetAsString(bool $includeData = true): string { $buffer = ''; if (!empty($this->data)) { if (is_int($this->dataName)) { $buffer .= sprintf(' with data set #%d', $this->dataName); } else { $buffer .= sprintf(' with data set "%s"', $this->dataName); } if ($includeData) { $exporter = new Exporter; $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); } } return $buffer; } public function getProvidedData(): array { return $this->data; } public function addWarning(string $warning): void { $this->warnings[] = $warning; } protected function runTest() { if (trim($this->name) === '') { throw new Exception( 'PHPUnit\Framework\TestCase::$name must be a non-blank string.' ); } $testArguments = array_merge($this->data, $this->dependencyInput); $this->registerMockObjectsFromTestArguments($testArguments); try { $testResult = $this->{$this->name}(...array_values($testArguments)); } catch (Throwable $exception) { if (!$this->checkExceptionExpectations($exception)) { throw $exception; } if ($this->expectedException !== null) { if ($this->expectedException === Error::class) { $this->assertThat( $exception, LogicalOr::fromConstraints( new ExceptionConstraint(Error::class), new ExceptionConstraint(\Error::class) ) ); } else { $this->assertThat( $exception, new ExceptionConstraint( $this->expectedException ) ); } } if ($this->expectedExceptionMessage !== null) { $this->assertThat( $exception, new ExceptionMessage( $this->expectedExceptionMessage ) ); } if ($this->expectedExceptionMessageRegExp !== null) { $this->assertThat( $exception, new ExceptionMessageRegularExpression( $this->expectedExceptionMessageRegExp ) ); } if ($this->expectedExceptionCode !== null) { $this->assertThat( $exception, new ExceptionCode( $this->expectedExceptionCode ) ); } if ($this->deprecatedExpectExceptionMessageRegExpUsed) { $this->addWarning('expectExceptionMessageRegExp() is deprecated in PHPUnit 8 and will be removed in PHPUnit 9. Use expectExceptionMessageMatches() instead.'); } return; } if ($this->expectedException !== null) { $this->assertThat( null, new ExceptionConstraint( $this->expectedException ) ); } elseif ($this->expectedExceptionMessage !== null) { $this->numAssertions++; throw new AssertionFailedError( sprintf( 'Failed asserting that exception with message "%s" is thrown', $this->expectedExceptionMessage ) ); } elseif ($this->expectedExceptionMessageRegExp !== null) { $this->numAssertions++; throw new AssertionFailedError( sprintf( 'Failed asserting that exception with message matching "%s" is thrown', $this->expectedExceptionMessageRegExp ) ); } elseif ($this->expectedExceptionCode !== null) { $this->numAssertions++; throw new AssertionFailedError( sprintf( 'Failed asserting that exception with code "%s" is thrown', $this->expectedExceptionCode ) ); } return $testResult; } protected function iniSet(string $varName, string $newValue): void { $currentValue = ini_set($varName, $newValue); if ($currentValue !== false) { $this->iniSettings[$varName] = $currentValue; } else { throw new Exception( sprintf( 'INI setting "%s" could not be set to "%s".', $varName, $newValue ) ); } } protected function setLocale(...$args): void { if (count($args) < 2) { throw new Exception; } [$category, $locale] = $args; if (defined('LC_MESSAGES')) { $categories[] = LC_MESSAGES; } if (!in_array($category, self::LOCALE_CATEGORIES, true)) { throw new Exception; } if (!is_array($locale) && !is_string($locale)) { throw new Exception; } $this->locale[$category] = setlocale($category, 0); $result = setlocale(...$args); if ($result === false) { throw new Exception( 'The locale functionality is not implemented on your platform, ' . 'the specified locale does not exist or the category name is ' . 'invalid.' ); } } protected function createStub(string $originalClassName): Stub { return $this->createMock($originalClassName); } protected function createMock($originalClassName): MockObject { if (!is_string($originalClassName)) { $this->addWarning('Passing an array of interface names to createMock() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); } return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->getMock(); } protected function createConfiguredMock($originalClassName, array $configuration): MockObject { $o = $this->createMock($originalClassName); foreach ($configuration as $method => $return) { $o->method($method)->willReturn($return); } return $o; } protected function createPartialMock($originalClassName, array $methods): MockObject { if (!is_string($originalClassName)) { $this->addWarning('Passing an array of interface names to createPartialMock() for creating a test double that implements multiple interfaces is deprecated and will no longer be supported in PHPUnit 9.'); } $class_names = is_array($originalClassName) ? $originalClassName : [$originalClassName]; foreach ($class_names as $class_name) { $reflection = new ReflectionClass($class_name); $mockedMethodsThatDontExist = array_filter( $methods, static function (string $method) use ($reflection) { return !$reflection->hasMethod($method); } ); if ($mockedMethodsThatDontExist) { $this->addWarning( sprintf( 'createPartialMock called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', implode(', ', $mockedMethodsThatDontExist), $class_name ) ); } } return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->setMethods(empty($methods) ? null : $methods) ->getMock(); } protected function createTestProxy(string $originalClassName, array $constructorArguments = []): MockObject { return $this->getMockBuilder($originalClassName) ->setConstructorArgs($constructorArguments) ->enableProxyingToOriginalMethods() ->getMock(); } protected function getMockClass($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = false, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false): string { $this->recordDoubledType($originalClassName); $mock = $this->getMockObjectGenerator()->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); return get_class($mock); } protected function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false): MockObject { $this->recordDoubledType($originalClassName); $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( $originalClassName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->registerMockObject($mockObject); return $mockObject; } protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClassName = '', array $methods = [], $callOriginalConstructor = true, array $options = []): MockObject { $this->recordDoubledType('SoapClient'); if ($originalClassName === '') { $fileName = pathinfo(basename(parse_url($wsdlFile, PHP_URL_PATH)), PATHINFO_FILENAME); $originalClassName = preg_replace('/\W/', '', $fileName); } if (!class_exists($originalClassName)) { eval( $this->getMockObjectGenerator()->generateClassFromWsdl( $wsdlFile, $originalClassName, $methods, $options ) ); } $mockObject = $this->getMockObjectGenerator()->getMock( $originalClassName, $methods, ['', $options], $mockClassName, $callOriginalConstructor, false, false ); $this->registerMockObject($mockObject); return $mockObject; } protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false): MockObject { $this->recordDoubledType($traitName); $mockObject = $this->getMockObjectGenerator()->getMockForTrait( $traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->registerMockObject($mockObject); return $mockObject; } protected function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) { $this->recordDoubledType($traitName); return $this->getMockObjectGenerator()->getObjectForTrait( $traitName, $traitClassName, $callAutoload, $callOriginalConstructor, $arguments ); } protected function prophesize($classOrInterface = null): ObjectProphecy { if (is_string($classOrInterface)) { $this->recordDoubledType($classOrInterface); } return $this->getProphet()->prophesize($classOrInterface); } protected function createResult(): TestResult { return new TestResult; } protected function onNotSuccessfulTest(Throwable $t): void { throw $t; } private function setExpectedExceptionFromAnnotation(): void { if ($this->name === null) { return; } try { $expectedException = TestUtil::getExpectedException( static::class, $this->name ); if ($expectedException !== false) { $this->addWarning('The @expectedException, @expectedExceptionCode, @expectedExceptionMessage, and @expectedExceptionMessageRegExp annotations are deprecated. They will be removed in PHPUnit 9. Refactor your test to use expectException(), expectExceptionCode(), expectExceptionMessage(), or expectExceptionMessageMatches() instead.'); $this->expectException($expectedException['class']); if ($expectedException['code'] !== null) { $this->expectExceptionCode($expectedException['code']); } if ($expectedException['message'] !== '') { $this->expectExceptionMessage($expectedException['message']); } elseif ($expectedException['message_regex'] !== '') { $this->expectExceptionMessageMatches($expectedException['message_regex']); } } } catch (UtilException $e) { } } private function checkRequirements(): void { if (!$this->name || !method_exists($this, $this->name)) { return; } $missingRequirements = TestUtil::getMissingRequirements( static::class, $this->name ); if (!empty($missingRequirements)) { $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); } } private function verifyMockObjects(): void { foreach ($this->mockObjects as $mockObject) { if ($mockObject->__phpunit_hasMatchers()) { $this->numAssertions++; } $mockObject->__phpunit_verify( $this->shouldInvocationMockerBeReset($mockObject) ); } if ($this->prophet !== null) { try { $this->prophet->checkPredictions(); } finally { foreach ($this->prophet->getProphecies() as $objectProphecy) { foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { foreach ($methodProphecies as $methodProphecy) { assert($methodProphecy instanceof MethodProphecy); $this->numAssertions += count($methodProphecy->getCheckedPredictions()); } } } } } } private function handleDependencies(): bool { if (!empty($this->dependencies) && !$this->inIsolation) { $passed = $this->result->passed(); $passedKeys = array_keys($passed); foreach ($passedKeys as $key => $value) { $pos = strpos($value, ' with data set'); if ($pos !== false) { $passedKeys[$key] = substr($value, 0, $pos); } } $passedKeys = array_flip(array_unique($passedKeys)); foreach ($this->dependencies as $dependency) { $deepClone = false; $shallowClone = false; if (empty($dependency)) { $this->markSkippedForNotSpecifyingDependency(); return false; } if (strpos($dependency, 'clone ') === 0) { $deepClone = true; $dependency = substr($dependency, strlen('clone ')); } elseif (strpos($dependency, '!clone ') === 0) { $deepClone = false; $dependency = substr($dependency, strlen('!clone ')); } if (strpos($dependency, 'shallowClone ') === 0) { $shallowClone = true; $dependency = substr($dependency, strlen('shallowClone ')); } elseif (strpos($dependency, '!shallowClone ') === 0) { $shallowClone = false; $dependency = substr($dependency, strlen('!shallowClone ')); } if (strpos($dependency, '::') === false) { $dependency = static::class . '::' . $dependency; } if (!isset($passedKeys[$dependency])) { if (!$this->isCallableTestMethod($dependency)) { $this->warnAboutDependencyThatDoesNotExist($dependency); } else { $this->markSkippedForMissingDependency($dependency); } return false; } if (isset($passed[$dependency])) { if ($passed[$dependency]['size'] !== TestUtil::UNKNOWN && $this->getSize() !== TestUtil::UNKNOWN && $passed[$dependency]['size'] > $this->getSize()) { $this->result->addError( $this, new SkippedTestError( 'This test depends on a test that is larger than itself.' ), 0 ); return false; } if ($deepClone) { $deepCopy = new DeepCopy; $deepCopy->skipUncloneable(false); $this->dependencyInput[$dependency] = $deepCopy->copy($passed[$dependency]['result']); } elseif ($shallowClone) { $this->dependencyInput[$dependency] = clone $passed[$dependency]['result']; } else { $this->dependencyInput[$dependency] = $passed[$dependency]['result']; } } else { $this->dependencyInput[$dependency] = null; } } } return true; } private function markSkippedForNotSpecifyingDependency(): void { $this->status = BaseTestRunner::STATUS_SKIPPED; $this->result->startTest($this); $this->result->addError( $this, new SkippedTestError( 'This method has an invalid @depends annotation.' ), 0 ); $this->result->endTest($this, 0); } private function markSkippedForMissingDependency(string $dependency): void { $this->status = BaseTestRunner::STATUS_SKIPPED; $this->result->startTest($this); $this->result->addError( $this, new SkippedTestError( sprintf( 'This test depends on "%s" to pass.', $dependency ) ), 0 ); $this->result->endTest($this, 0); } private function warnAboutDependencyThatDoesNotExist(string $dependency): void { $this->status = BaseTestRunner::STATUS_WARNING; $this->result->startTest($this); $this->result->addWarning( $this, new Warning( sprintf( 'This test depends on "%s" which does not exist.', $dependency ) ), 0 ); $this->result->endTest($this, 0); } private function getMockObjectGenerator(): MockGenerator { if ($this->mockObjectGenerator === null) { $this->mockObjectGenerator = new MockGenerator; } return $this->mockObjectGenerator; } private function startOutputBuffering(): void { ob_start(); $this->outputBufferingActive = true; $this->outputBufferingLevel = ob_get_level(); } private function stopOutputBuffering(): void { if (ob_get_level() !== $this->outputBufferingLevel) { while (ob_get_level() >= $this->outputBufferingLevel) { ob_end_clean(); } throw new RiskyTestError( 'Test code or tested code did not (only) close its own output buffers' ); } $this->output = ob_get_contents(); if ($this->outputCallback !== false) { $this->output = (string) call_user_func($this->outputCallback, $this->output); } ob_end_clean(); $this->outputBufferingActive = false; $this->outputBufferingLevel = ob_get_level(); } private function snapshotGlobalState(): void { if ($this->runTestInSeparateProcess || $this->inIsolation || (!$this->backupGlobals && !$this->backupStaticAttributes)) { return; } $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); } private function restoreGlobalState(): void { if (!$this->snapshot instanceof Snapshot) { return; } if ($this->beStrictAboutChangesToGlobalState) { try { $this->compareGlobalStateSnapshots( $this->snapshot, $this->createGlobalStateSnapshot($this->backupGlobals === true) ); } catch (RiskyTestError $rte) { } } $restorer = new Restorer; if ($this->backupGlobals) { $restorer->restoreGlobalVariables($this->snapshot); } if ($this->backupStaticAttributes) { $restorer->restoreStaticAttributes($this->snapshot); } $this->snapshot = null; if (isset($rte)) { throw $rte; } } private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot { $blacklist = new Blacklist; foreach ($this->backupGlobalsBlacklist as $globalVariable) { $blacklist->addGlobalVariable($globalVariable); } if (!defined('PHPUNIT_TESTSUITE')) { $blacklist->addClassNamePrefix('PHPUnit'); $blacklist->addClassNamePrefix('SebastianBergmann\CodeCoverage'); $blacklist->addClassNamePrefix('SebastianBergmann\FileIterator'); $blacklist->addClassNamePrefix('SebastianBergmann\Invoker'); $blacklist->addClassNamePrefix('SebastianBergmann\Timer'); $blacklist->addClassNamePrefix('PHP_Token'); $blacklist->addClassNamePrefix('Text_Template'); $blacklist->addClassNamePrefix('Doctrine\Instantiator'); $blacklist->addClassNamePrefix('Prophecy'); $blacklist->addStaticAttribute(ComparatorFactory::class, 'instance'); foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { foreach ($attributes as $attribute) { $blacklist->addStaticAttribute($class, $attribute); } } } return new Snapshot( $blacklist, $backupGlobals, (bool) $this->backupStaticAttributes, false, false, false, false, false, false, false ); } private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void { $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; if ($backupGlobals) { $this->compareGlobalStateSnapshotPart( $before->globalVariables(), $after->globalVariables(), "--- Global variables before the test\n+++ Global variables after the test\n" ); $this->compareGlobalStateSnapshotPart( $before->superGlobalVariables(), $after->superGlobalVariables(), "--- Super-global variables before the test\n+++ Super-global variables after the test\n" ); } if ($this->backupStaticAttributes) { $this->compareGlobalStateSnapshotPart( $before->staticAttributes(), $after->staticAttributes(), "--- Static attributes before the test\n+++ Static attributes after the test\n" ); } } private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void { if ($before != $after) { $differ = new Differ($header); $exporter = new Exporter; $diff = $differ->diff( $exporter->export($before), $exporter->export($after) ); throw new RiskyTestError( $diff ); } } private function getProphet(): Prophet { if ($this->prophet === null) { $this->prophet = new Prophet; } return $this->prophet; } private function shouldInvocationMockerBeReset(MockObject $mock): bool { $enumerator = new Enumerator; foreach ($enumerator->enumerate($this->dependencyInput) as $object) { if ($mock === $object) { return false; } } if (!is_array($this->testResult) && !is_object($this->testResult)) { return true; } return !in_array($mock, $enumerator->enumerate($this->testResult), true); } private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []): void { if ($this->registerMockObjectsFromTestArgumentsRecursively) { foreach ((new Enumerator)->enumerate($testArguments) as $object) { if ($object instanceof MockObject) { $this->registerMockObject($object); } } } else { foreach ($testArguments as $testArgument) { if ($testArgument instanceof MockObject) { if (Type::isCloneable($testArgument)) { $testArgument = clone $testArgument; } $this->registerMockObject($testArgument); } elseif (is_array($testArgument) && !in_array($testArgument, $visited, true)) { $visited[] = $testArgument; $this->registerMockObjectsFromTestArguments( $testArgument, $visited ); } } } } private function setDoesNotPerformAssertionsFromAnnotation(): void { $annotations = $this->getAnnotations(); if (isset($annotations['method']['doesNotPerformAssertions'])) { $this->doesNotPerformAssertions = true; } } private function unregisterCustomComparators(): void { $factory = ComparatorFactory::getInstance(); foreach ($this->customComparators as $comparator) { $factory->unregister($comparator); } $this->customComparators = []; } private function cleanupIniSettings(): void { foreach ($this->iniSettings as $varName => $oldValue) { ini_set($varName, $oldValue); } $this->iniSettings = []; } private function cleanupLocaleSettings(): void { foreach ($this->locale as $category => $locale) { setlocale($category, $locale); } $this->locale = []; } private function checkExceptionExpectations(Throwable $throwable): bool { $result = false; if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { $result = true; } if ($throwable instanceof Exception) { $result = false; } if (is_string($this->expectedException)) { try { $reflector = new ReflectionClass($this->expectedException); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($this->expectedException === 'PHPUnit\Framework\Exception' || $this->expectedException === '\PHPUnit\Framework\Exception' || $reflector->isSubclassOf(Exception::class)) { $result = true; } } return $result; } private function runInSeparateProcess(): bool { return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && !$this->inIsolation && !$this instanceof PhptTestCase; } private function recordDoubledType($originalClassName): void { if (is_string($originalClassName)) { $this->doubledTypes[] = $originalClassName; } if (is_array($originalClassName)) { foreach ($originalClassName as $_originalClassName) { if (is_string($_originalClassName)) { $this->doubledTypes[] = $_originalClassName; } } } } private function isCallableTestMethod(string $dependency): bool { [$className, $methodName] = explode('::', $dependency); if (!class_exists($className)) { return false; } try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { return false; } if (!$class->isSubclassOf(__CLASS__)) { return false; } if (!$class->hasMethod($methodName)) { return false; } try { $method = $class->getMethod($methodName); } catch (ReflectionException $e) { return false; } return TestUtil::isTestMethod($method); } } declaredClassesPointer = count(get_declared_classes()); if (!$theClass instanceof ReflectionClass) { if (class_exists($theClass, true)) { if ($name === '') { $name = $theClass; } try { $theClass = new ReflectionClass($theClass); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } else { $this->setName($theClass); return; } } if (!$theClass->isSubclassOf(TestCase::class)) { $this->setName((string) $theClass); return; } if ($name !== '') { $this->setName($name); } else { $this->setName($theClass->getName()); } $constructor = $theClass->getConstructor(); if ($constructor !== null && !$constructor->isPublic()) { $this->addTest( new WarningTestCase( sprintf( 'Class "%s" has no public constructor.', $theClass->getName() ) ) ); return; } foreach ((new Reflection)->publicMethodsInTestClass($theClass) as $method) { if (!TestUtil::isTestMethod($method)) { continue; } $this->addTestMethod($theClass, $method); } if (empty($this->tests)) { $this->addTest( new WarningTestCase( sprintf( 'No tests found in class "%s".', $theClass->getName() ) ) ); } $this->testCase = true; } public function toString(): string { return $this->getName(); } public function addTest(Test $test, $groups = []): void { try { $class = new ReflectionClass($test); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$class->isAbstract()) { $this->tests[] = $test; $this->numTests = -1; if ($test instanceof self && empty($groups)) { $groups = $test->getGroups(); } if (empty($groups)) { $groups = ['default']; } foreach ($groups as $group) { if (!isset($this->groups[$group])) { $this->groups[$group] = [$test]; } else { $this->groups[$group][] = $test; } } if ($test instanceof TestCase) { $test->setGroups($groups); } } } public function addTestSuite($testClass): void { if (!(is_object($testClass) || (is_string($testClass) && class_exists($testClass)))) { throw InvalidArgumentException::create( 1, 'class name or object' ); } if (!is_object($testClass)) { try { $testClass = new ReflectionClass($testClass); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } if ($testClass instanceof self) { $this->addTest($testClass); } elseif ($testClass instanceof ReflectionClass) { $suiteMethod = false; if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { try { $method = $testClass->getMethod( BaseTestRunner::SUITE_METHODNAME ); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($method->isStatic()) { $this->addTest( $method->invoke(null, $testClass->getName()) ); $suiteMethod = true; } } if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) { $this->addTest(new self($testClass)); } } else { throw new Exception; } } public function addTestFile(string $filename): void { if (file_exists($filename) && substr($filename, -5) === '.phpt') { $this->addTest( new PhptTestCase($filename) ); return; } $filename = FileLoader::checkAndLoad($filename); $newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer); if (!empty($newClasses)) { $this->foundClasses = array_merge($newClasses, $this->foundClasses); $this->declaredClassesPointer = count(get_declared_classes()); } $shortName = basename($filename, '.php'); $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; foreach ($this->foundClasses as $i => $className) { if (preg_match($shortNameRegEx, $className)) { try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($class->getFileName() == $filename) { $newClasses = [$className]; unset($this->foundClasses[$i]); break; } } } foreach ($newClasses as $className) { try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (dirname($class->getFileName()) === __DIR__) { continue; } if (!$class->isAbstract()) { if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { try { $method = $class->getMethod( BaseTestRunner::SUITE_METHODNAME ); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($method->isStatic()) { $this->addTest($method->invoke(null, $className)); } } elseif ($class->implementsInterface(Test::class)) { $this->addTestSuite($class); } } } $this->numTests = -1; } public function addTestFiles(iterable $fileNames): void { foreach ($fileNames as $filename) { $this->addTestFile((string) $filename); } } public function count(bool $preferCache = false): int { if ($preferCache && $this->cachedNumTests !== null) { return $this->cachedNumTests; } $numTests = 0; foreach ($this as $test) { $numTests += count($test); } $this->cachedNumTests = $numTests; return $numTests; } public function getName(): string { return $this->name; } public function getGroups(): array { return array_keys($this->groups); } public function getGroupDetails(): array { return $this->groups; } public function setGroupDetails(array $groups): void { $this->groups = $groups; } public function run(TestResult $result = null): TestResult { if ($result === null) { $result = $this->createResult(); } if (count($this) === 0) { return $result; } $className = $this->name; $hookMethods = TestUtil::getHookMethods($className); $result->startTestSuite($this); try { foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { if ($this->testCase && class_exists($this->name, false) && method_exists($this->name, $beforeClassMethod)) { if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); } call_user_func([$this->name, $beforeClassMethod]); } } } catch (SkippedTestSuiteError $error) { foreach ($this->tests() as $test) { $result->startTest($test); $result->addFailure($test, $error, 0); $result->endTest($test, 0); } $result->endTestSuite($this); return $result; } catch (Throwable $t) { $errorAdded = false; foreach ($this->tests() as $test) { if ($result->shouldStop()) { break; } $result->startTest($test); if (!$errorAdded) { $result->addError($test, $t, 0); $errorAdded = true; } else { $result->addFailure( $test, new SkippedTestError('Test skipped because of an error in hook method'), 0 ); } $result->endTest($test, 0); } $result->endTestSuite($this); return $result; } foreach ($this as $test) { if ($result->shouldStop()) { break; } if ($test instanceof TestCase || $test instanceof self) { $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); $test->setBackupGlobals($this->backupGlobals); $test->setBackupStaticAttributes($this->backupStaticAttributes); $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); } $test->run($result); } try { foreach ($hookMethods['afterClass'] as $afterClassMethod) { if ($this->testCase && class_exists($this->name, false) && method_exists($this->name, $afterClassMethod)) { call_user_func([$this->name, $afterClassMethod]); } } } catch (Throwable $t) { $message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage(); $error = new SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); $placeholderTest = clone $test; $placeholderTest->setName($afterClassMethod); $result->startTest($placeholderTest); $result->addFailure($placeholderTest, $error, 0); $result->endTest($placeholderTest, 0); } $result->endTestSuite($this); return $result; } public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } public function setName(string $name): void { $this->name = $name; } public function testAt(int $index) { return $this->tests[$index] ?? false; } public function tests(): array { return $this->tests; } public function setTests(array $tests): void { $this->tests = $tests; } public function markTestSuiteSkipped($message = ''): void { throw new SkippedTestSuiteError($message); } public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void { if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; } } public function setBackupGlobals($backupGlobals): void { if (null === $this->backupGlobals && is_bool($backupGlobals)) { $this->backupGlobals = $backupGlobals; } } public function setBackupStaticAttributes($backupStaticAttributes): void { if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { $this->backupStaticAttributes = $backupStaticAttributes; } } public function getIterator(): Iterator { $iterator = new TestSuiteIterator($this); if ($this->iteratorFilter !== null) { $iterator = $this->iteratorFilter->factory($iterator, $this); } return $iterator; } public function injectFilter(Factory $filter): void { $this->iteratorFilter = $filter; foreach ($this as $test) { if ($test instanceof self) { $test->injectFilter($filter); } } } protected function createResult(): TestResult { return new TestResult; } protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void { if (!TestUtil::isTestMethod($method)) { return; } $methodName = $method->getName(); $test = (new TestBuilder)->build($class, $methodName); if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) { $test->setDependencies( TestUtil::getDependencies($class->getName(), $methodName) ); } $this->addTest( $test, TestUtil::getGroups($class->getName(), $methodName) ); } } message = $message; parent::__construct('Warning'); } public function getMessage(): string { return $this->message; } public function toString(): string { return 'Warning'; } protected function runTest(): void { throw new Warning($this->message); } } toString(); if ($e instanceof ExpectationFailedException && $e->getComparisonFailure()) { $buffer .= $e->getComparisonFailure()->getDiff(); } if ($e instanceof PHPTAssertionFailedError) { $buffer .= $e->getDiff(); } if (!empty($buffer)) { $buffer = trim($buffer) . "\n"; } return $buffer; } if ($e instanceof Error) { return $e->getMessage() . "\n"; } if ($e instanceof ExceptionWrapper) { return $e->getClassName() . ': ' . $e->getMessage() . "\n"; } return get_class($e) . ': ' . $e->getMessage() . "\n"; } public function __construct(Test $failedTest, $t) { if ($failedTest instanceof SelfDescribing) { $this->testName = $failedTest->toString(); } else { $this->testName = get_class($failedTest); } if (!$failedTest instanceof TestCase || !$failedTest->isInIsolation()) { $this->failedTest = $failedTest; } $this->thrownException = $t; } public function toString(): string { return sprintf( '%s: %s', $this->testName, $this->thrownException->getMessage() ); } public function getExceptionAsString(): string { return self::exceptionToString($this->thrownException); } public function getTestName(): string { return $this->testName; } public function failedTest(): ?Test { return $this->failedTest; } public function thrownException(): Throwable { return $this->thrownException; } public function exceptionMessage(): string { return $this->thrownException()->getMessage(); } public function isFailure(): bool { return $this->thrownException() instanceof AssertionFailedError; } } message = $message; } public function getMessage(): string { return $this->message; } public function toString(): string { return $this->getName(); } protected function runTest(): void { $this->markTestSkipped($this->message); } } dependencies = $dependencies; foreach ($this->tests as $test) { if (!$test instanceof TestCase) { continue; } $test->setDependencies($dependencies); } } public function getDependencies(): array { return $this->dependencies; } public function hasDependencies(): bool { return count($this->dependencies) > 0; } public function getSize(): int { [$className, $methodName] = explode('::', $this->getName()); return TestUtil::getSize($className, $methodName); } } diff = $diff; } public function getDiff(): string { return $this->diff; } } serializableTrace = $this->getTrace(); foreach (array_keys($this->serializableTrace) as $key) { unset($this->serializableTrace[$key]['args']); } } public function __toString(): string { $string = TestFailure::exceptionToString($this); if ($trace = Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } return $string; } public function __sleep(): array { return array_keys(get_object_vars($this)); } public function getSerializableTrace(): array { return $this->serializableTrace; } } getMessage(); } } getMessage(); } } syntheticFile = $file; $this->syntheticLine = $line; $this->syntheticTrace = $trace; } public function getSyntheticFile(): string { return $this->syntheticFile; } public function getSyntheticLine(): int { return $this->syntheticLine; } public function getSyntheticTrace(): array { return $this->syntheticTrace; } } comparisonFailure = $comparisonFailure; parent::__construct($message, 0, $previous); } public function getComparisonFailure(): ?ComparisonFailure { return $this->comparisonFailure; } } tagName, $actualElement->tagName, $message ); if ($checkAttributes) { static::assertSame( $expectedElement->attributes->length, $actualElement->attributes->length, sprintf( '%s%sNumber of attributes on node "%s" does not match', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->attributes->length; $i++) { $expectedAttribute = $expectedElement->attributes->item($i); $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); assert($expectedAttribute instanceof DOMAttr); if (!$actualAttribute) { static::fail( sprintf( '%s%sCould not find attribute "%s" on node "%s"', $message, !empty($message) ? "\n" : '', $expectedAttribute->name, $expectedElement->tagName ) ); } } } Xml::removeCharacterDataNodes($expectedElement); Xml::removeCharacterDataNodes($actualElement); static::assertSame( $expectedElement->childNodes->length, $actualElement->childNodes->length, sprintf( '%s%sNumber of child nodes of "%s" differs', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { static::assertEqualXMLStructure( $expectedElement->childNodes->item($i), $actualElement->childNodes->item($i), $checkAttributes, $message ); } } public static function assertThat($value, Constraint $constraint, string $message = ''): void { self::$count += count($constraint); $constraint->evaluate($value, $message); } public static function assertJson(string $actualJson, string $message = ''): void { static::assertThat($actualJson, static::isJson(), $message); } public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); static::assertThat($actualJson, new JsonMatches($expectedJson), $message); } public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void { static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); static::assertThat( $actualJson, new LogicalNot( new JsonMatches($expectedJson) ), $message ); } public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { static::assertFileExists($expectedFile, $message); $expectedJson = file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); static::assertThat($actualJson, new JsonMatches($expectedJson), $message); } public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { static::assertFileExists($expectedFile, $message); $expectedJson = file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); static::assertThat( $actualJson, new LogicalNot( new JsonMatches($expectedJson) ), $message ); } public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { static::assertFileExists($expectedFile, $message); static::assertFileExists($actualFile, $message); $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraintExpected = new JsonMatches( $expectedJson ); $constraintActual = new JsonMatches($actualJson); static::assertThat($expectedJson, $constraintActual, $message); static::assertThat($actualJson, $constraintExpected, $message); } public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { static::assertFileExists($expectedFile, $message); static::assertFileExists($actualFile, $message); $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraintExpected = new JsonMatches( $expectedJson ); $constraintActual = new JsonMatches($actualJson); static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); } public static function logicalAnd(): LogicalAnd { $constraints = func_get_args(); $constraint = new LogicalAnd; $constraint->setConstraints($constraints); return $constraint; } public static function logicalOr(): LogicalOr { $constraints = func_get_args(); $constraint = new LogicalOr; $constraint->setConstraints($constraints); return $constraint; } public static function logicalNot(Constraint $constraint): LogicalNot { return new LogicalNot($constraint); } public static function logicalXor(): LogicalXor { $constraints = func_get_args(); $constraint = new LogicalXor; $constraint->setConstraints($constraints); return $constraint; } public static function anything(): IsAnything { return new IsAnything; } public static function isTrue(): IsTrue { return new IsTrue; } public static function callback(callable $callback): Callback { return new Callback($callback); } public static function isFalse(): IsFalse { return new IsFalse; } public static function isJson(): IsJson { return new IsJson; } public static function isNull(): IsNull { return new IsNull; } public static function isFinite(): IsFinite { return new IsFinite; } public static function isInfinite(): IsInfinite { return new IsInfinite; } public static function isNan(): IsNan { return new IsNan; } public static function attribute(Constraint $constraint, string $attributeName): Attribute { self::createWarning('attribute() is deprecated and will be removed in PHPUnit 9.'); return new Attribute($constraint, $attributeName); } public static function contains($value, bool $checkForObjectIdentity = true, bool $checkForNonObjectIdentity = false): TraversableContains { return new TraversableContains($value, $checkForObjectIdentity, $checkForNonObjectIdentity); } public static function containsEqual($value): TraversableContainsEqual { return new TraversableContainsEqual($value); } public static function containsIdentical($value): TraversableContainsIdentical { return new TraversableContainsIdentical($value); } public static function containsOnly(string $type): TraversableContainsOnly { return new TraversableContainsOnly($type); } public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly { return new TraversableContainsOnly($className, false); } public static function arrayHasKey($key): ArrayHasKey { return new ArrayHasKey($key); } public static function equalTo($value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): IsEqual { return new IsEqual($value, $delta, $maxDepth, $canonicalize, $ignoreCase); } public static function attributeEqualTo(string $attributeName, $value, float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): Attribute { self::createWarning('attributeEqualTo() is deprecated and will be removed in PHPUnit 9.'); return static::attribute( static::equalTo( $value, $delta, $maxDepth, $canonicalize, $ignoreCase ), $attributeName ); } public static function isEmpty(): IsEmpty { return new IsEmpty; } public static function isWritable(): IsWritable { return new IsWritable; } public static function isReadable(): IsReadable { return new IsReadable; } public static function directoryExists(): DirectoryExists { return new DirectoryExists; } public static function fileExists(): FileExists { return new FileExists; } public static function greaterThan($value): GreaterThan { return new GreaterThan($value); } public static function greaterThanOrEqual($value): LogicalOr { return static::logicalOr( new IsEqual($value), new GreaterThan($value) ); } public static function classHasAttribute(string $attributeName): ClassHasAttribute { return new ClassHasAttribute($attributeName); } public static function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute { return new ClassHasStaticAttribute($attributeName); } public static function objectHasAttribute($attributeName): ObjectHasAttribute { return new ObjectHasAttribute($attributeName); } public static function identicalTo($value): IsIdentical { return new IsIdentical($value); } public static function isInstanceOf(string $className): IsInstanceOf { return new IsInstanceOf($className); } public static function isType(string $type): IsType { return new IsType($type); } public static function lessThan($value): LessThan { return new LessThan($value); } public static function lessThanOrEqual($value): LogicalOr { return static::logicalOr( new IsEqual($value), new LessThan($value) ); } public static function matchesRegularExpression(string $pattern): RegularExpression { return new RegularExpression($pattern); } public static function matches(string $string): StringMatchesFormatDescription { return new StringMatchesFormatDescription($string); } public static function stringStartsWith($prefix): StringStartsWith { return new StringStartsWith($prefix); } public static function stringContains(string $string, bool $case = true): StringContains { return new StringContains($string, $case); } public static function stringEndsWith(string $suffix): StringEndsWith { return new StringEndsWith($suffix); } public static function countOf(int $count): Count { return new Count($count); } public static function fail(string $message = ''): void { self::$count++; throw new AssertionFailedError($message); } public static function readAttribute($classOrObject, string $attributeName) { self::createWarning('readAttribute() is deprecated and will be removed in PHPUnit 9.'); if (!self::isValidClassAttributeName($attributeName)) { throw InvalidArgumentException::create(2, 'valid attribute name'); } if (is_string($classOrObject)) { if (!class_exists($classOrObject)) { throw InvalidArgumentException::create( 1, 'class name' ); } return static::getStaticAttribute( $classOrObject, $attributeName ); } if (is_object($classOrObject)) { return static::getObjectAttribute( $classOrObject, $attributeName ); } throw InvalidArgumentException::create( 1, 'class name or object' ); } public static function getStaticAttribute(string $className, string $attributeName) { self::createWarning('getStaticAttribute() is deprecated and will be removed in PHPUnit 9.'); if (!class_exists($className)) { throw InvalidArgumentException::create(1, 'class name'); } if (!self::isValidClassAttributeName($attributeName)) { throw InvalidArgumentException::create(2, 'valid attribute name'); } try { $class = new ReflectionClass($className); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } while ($class) { $attributes = $class->getStaticProperties(); if (array_key_exists($attributeName, $attributes)) { return $attributes[$attributeName]; } $class = $class->getParentClass(); } throw new Exception( sprintf( 'Attribute "%s" not found in class.', $attributeName ) ); } public static function getObjectAttribute($object, string $attributeName) { self::createWarning('getObjectAttribute() is deprecated and will be removed in PHPUnit 9.'); if (!is_object($object)) { throw InvalidArgumentException::create(1, 'object'); } if (!self::isValidClassAttributeName($attributeName)) { throw InvalidArgumentException::create(2, 'valid attribute name'); } $reflector = new ReflectionObject($object); do { try { $attribute = $reflector->getProperty($attributeName); if (!$attribute || $attribute->isPublic()) { return $object->{$attributeName}; } $attribute->setAccessible(true); $value = $attribute->getValue($object); $attribute->setAccessible(false); return $value; } catch (ReflectionException $e) { } } while ($reflector = $reflector->getParentClass()); throw new Exception( sprintf( 'Attribute "%s" not found in object.', $attributeName ) ); } public static function markTestIncomplete(string $message = ''): void { throw new IncompleteTestError($message); } public static function markTestSkipped(string $message = ''): void { if ($hint = self::detectLocationHint($message)) { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); array_unshift($trace, $hint); throw new SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); } throw new SkippedTestError($message); } public static function getCount(): int { return self::$count; } public static function resetCount(): void { self::$count = 0; } private static function detectLocationHint(string $message): ?array { $hint = null; $lines = preg_split('/\r\n|\r|\n/', $message); while (strpos($lines[0], '__OFFSET') !== false) { $offset = explode('=', array_shift($lines)); if ($offset[0] === '__OFFSET_FILE') { $hint['file'] = $offset[1]; } if ($offset[0] === '__OFFSET_LINE') { $hint['line'] = $offset[1]; } } if ($hint) { $hint['message'] = implode(PHP_EOL, $lines); } return $hint; } private static function isValidObjectAttributeName(string $attributeName): bool { return (bool) preg_match('/[^\x00-\x1f\x7f-\x9f]+/', $attributeName); } private static function isValidClassAttributeName(string $attributeName): bool { return (bool) preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName); } private static function createWarning(string $warning): void { foreach (debug_backtrace() as $step) { if (isset($step['object']) && $step['object'] instanceof TestCase) { assert($step['object'] instanceof TestCase); $step['object']->addWarning($warning); break; } } } private static function assertInternalTypeReplacement(string $type, bool $not): string { switch ($type) { case 'numeric': return 'assertIs' . ($not ? 'Not' : '') . 'Numeric'; case 'integer': case 'int': return 'assertIs' . ($not ? 'Not' : '') . 'Int'; case 'double': case 'float': case 'real': return 'assertIs' . ($not ? 'Not' : '') . 'Float'; case 'string': return 'assertIs' . ($not ? 'Not' : '') . 'String'; case 'boolean': case 'bool': return 'assertIs' . ($not ? 'Not' : '') . 'Bool'; case 'null': return 'assert' . ($not ? 'Not' : '') . 'Null'; case 'array': return 'assertIs' . ($not ? 'Not' : '') . 'Array'; case 'object': return 'assertIs' . ($not ? 'Not' : '') . 'Object'; case 'resource': return 'assertIs' . ($not ? 'Not' : '') . 'Resource'; case 'scalar': return 'assertIs' . ($not ? 'Not' : '') . 'Scalar'; case 'callable': return 'assertIs' . ($not ? 'Not' : '') . 'Callable'; case 'iterable': return 'assertIs' . ($not ? 'Not' : '') . 'Iterable'; } throw new Exception( sprintf( '"%s" is not a type supported by assertInternalType() / assertNotInternalType()', $type ) ); } } tests = $testSuite->tests(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < count($this->tests); } public function key(): int { return $this->position; } public function current(): Test { return $this->tests[$this->position]; } public function next(): void { $this->position++; } public function getChildren(): self { if (!$this->hasChildren()) { throw new NoChildTestSuiteException( 'The current item is not a TestSuite instance and therefore does not have any children.' ); } $current = $this->current(); assert($current instanceof TestSuite); return new self($current); } public function hasChildren(): bool { return $this->valid() && $this->current() instanceof TestSuite; } } getIterator()) as $test) { if ($test instanceof TestCase) { $name = sprintf( '%s::%s', get_class($test), str_replace(' with data set ', '', $test->getName()) ); } elseif ($test instanceof PhptTestCase) { $name = $test->getName(); } else { continue; } $buffer .= sprintf( ' - %s' . PHP_EOL, $name ); } return $buffer; } } {tests_directory} {src_directory} EOT; public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory): string { return str_replace( [ '{phpunit_version}', '{bootstrap_script}', '{tests_directory}', '{src_directory}', ], [ $phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory, ], self::TEMPLATE ); } } 1, Instantiator::class => 1, DeepCopy::class => 1, Manifest::class => 1, PharIoVersion::class => 1, Project::class => 1, DocBlock::class => 1, Type::class => 1, Prophet::class => 1, TestCase::class => 2, CodeCoverage::class => 1, FileIteratorFacade::class => 1, Invoker::class => 1, Text_Template::class => 1, Timer::class => 1, PHP_Token::class => 1, Wizard::class => 1, Comparator::class => 1, Diff::class => 1, Runtime::class => 1, Exporter::class => 1, Snapshot::class => 1, Enumerator::class => 1, Context::class => 1, ResourceOperations::class => 1, TypeName::class => 1, Version::class => 1, Tokenizer::class => 1, Assert::class => 1, ]; private static $directories; public function getBlacklistedDirectories(): array { $this->initialize(); return self::$directories; } public function isBlacklisted(string $file): bool { if (defined('PHPUNIT_TESTSUITE')) { return false; } $this->initialize(); foreach (self::$directories as $directory) { if (strpos($file, $directory) === 0) { return true; } } return false; } private function initialize(): void { if (self::$directories === null) { self::$directories = []; foreach (self::$blacklistedClassNames as $className => $parent) { if (!class_exists($className)) { continue; } try { $directory = (new ReflectionClass($className))->getFileName(); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } for ($i = 0; $i < $parent; $i++) { $directory = dirname($directory); } self::$directories[] = $directory; } if (DIRECTORY_SEPARATOR === '\\') { self::$directories[] = sys_get_temp_dir() . '\\PHP'; } } } } $stdout_handle, ]; } protected function useTemporaryFile(): bool { return true; } } stdin || $this->useTemporaryFile()) { if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || file_put_contents($this->tempFile, $job) === false) { throw new Exception( 'Unable to write temporary file' ); } $job = $this->stdin; } return $this->runProcess($job, $settings); } protected function getHandles(): array { return []; } protected function runProcess(string $job, array $settings): array { $handles = $this->getHandles(); $env = null; if ($this->env) { $env = $_SERVER ?? []; unset($env['argv'], $env['argc']); $env = array_merge($env, $this->env); foreach ($env as $envKey => $envVar) { if (is_array($envVar)) { unset($env[$envKey]); } } } $pipeSpec = [ 0 => $handles[0] ?? ['pipe', 'r'], 1 => $handles[1] ?? ['pipe', 'w'], 2 => $handles[2] ?? ['pipe', 'w'], ]; $process = proc_open( $this->getCommand($settings, $this->tempFile), $pipeSpec, $pipes, null, $env ); if (!is_resource($process)) { throw new Exception( 'Unable to spawn worker process' ); } if ($job) { $this->process($pipes[0], $job); } fclose($pipes[0]); $stderr = $stdout = ''; if ($this->timeout) { unset($pipes[0]); while (true) { $r = $pipes; $w = null; $e = null; $n = @stream_select($r, $w, $e, $this->timeout); if ($n === false) { break; } if ($n === 0) { proc_terminate($process, 9); throw new Exception( sprintf( 'Job execution aborted after %d seconds', $this->timeout ) ); } if ($n > 0) { foreach ($r as $pipe) { $pipeOffset = 0; foreach ($pipes as $i => $origPipe) { if ($pipe === $origPipe) { $pipeOffset = $i; break; } } if (!$pipeOffset) { break; } $line = fread($pipe, 8192); if ($line === '' || $line === false) { fclose($pipes[$pipeOffset]); unset($pipes[$pipeOffset]); } elseif ($pipeOffset === 1) { $stdout .= $line; } else { $stderr .= $line; } } if (empty($pipes)) { break; } } } } else { if (isset($pipes[1])) { $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); } if (isset($pipes[2])) { $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); } } if (isset($handles[1])) { rewind($handles[1]); $stdout = stream_get_contents($handles[1]); fclose($handles[1]); } if (isset($handles[2])) { rewind($handles[2]); $stderr = stream_get_contents($handles[2]); fclose($handles[2]); } proc_close($process); $this->cleanup(); return ['stdout' => $stdout, 'stderr' => $stderr]; } protected function process($pipe, string $job): void { fwrite($pipe, $job); } protected function cleanup(): void { if ($this->tempFile) { unlink($this->tempFile); } } protected function useTemporaryFile(): bool { return false; } } start(__FILE__); } register_shutdown_function(function() use ($coverage) { $output = null; if ($coverage) { $output = $coverage->stop(); } file_put_contents('{coverageFile}', serialize($output)); }); ob_end_clean(); require '{job}'; setCodeCoverage( new CodeCoverage( null, unserialize('{codeCoverageFilter}') ) ); } $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); $result->enforceTimeLimit({enforcesTimeLimit}); $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); \assert($test instanceof TestCase); $test->setDependencyInput(unserialize('{dependencyInput}')); $test->setInIsolation(true); ob_end_clean(); $test->run($result); $output = ''; if (!$test->hasExpectationOnOutput()) { $output = $test->getActualOutput(); } ini_set('xdebug.scream', '0'); @rewind(STDOUT); if ($stdout = @stream_get_contents(STDOUT)) { $output = $stdout . $output; $streamMetaData = stream_get_meta_data(STDOUT); if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { @ftruncate(STDOUT, 0); @rewind(STDOUT); } } print serialize( [ 'testResult' => $test->getResult(), 'numAssertions' => $test->getNumAssertions(), 'result' => $result, 'output' => $output ] ); } $configurationFilePath = '{configurationFilePath}'; if ('' !== $configurationFilePath) { $configuration = PHPUnit\Util\Configuration::getInstance($configurationFilePath); $configuration->handlePHPConfiguration(); unset($configuration); } function __phpunit_error_handler($errno, $errstr, $errfile, $errline) { return true; } set_error_handler('__phpunit_error_handler'); {constants} {included_files} {globals} restore_error_handler(); if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); } __phpunit_run_isolated_test(); setCodeCoverage( new CodeCoverage( null, unserialize('{codeCoverageFilter}') ) ); } $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); $result->enforceTimeLimit({enforcesTimeLimit}); $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); $test->setDependencyInput(unserialize('{dependencyInput}')); $test->setInIsolation(TRUE); ob_end_clean(); $test->run($result); $output = ''; if (!$test->hasExpectationOnOutput()) { $output = $test->getActualOutput(); } ini_set('xdebug.scream', '0'); @rewind(STDOUT); if ($stdout = @stream_get_contents(STDOUT)) { $output = $stdout . $output; $streamMetaData = stream_get_meta_data(STDOUT); if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { @ftruncate(STDOUT, 0); @rewind(STDOUT); } } print serialize( [ 'testResult' => $test->getResult(), 'numAssertions' => $test->getNumAssertions(), 'result' => $result, 'output' => $output ] ); } $configurationFilePath = '{configurationFilePath}'; if ('' !== $configurationFilePath) { $configuration = PHPUnit\Util\Configuration::getInstance($configurationFilePath); $configuration->handlePHPConfiguration(); unset($configuration); } function __phpunit_error_handler($errno, $errstr, $errfile, $errline) { return true; } set_error_handler('__phpunit_error_handler'); {constants} {included_files} {globals} restore_error_handler(); if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); } __phpunit_run_isolated_test(); runtime = new Runtime; } public function setUseStderrRedirection(bool $stderrRedirection): void { $this->stderrRedirection = $stderrRedirection; } public function useStderrRedirection(): bool { return $this->stderrRedirection; } public function setStdin(string $stdin): void { $this->stdin = $stdin; } public function getStdin(): string { return $this->stdin; } public function setArgs(string $args): void { $this->args = $args; } public function getArgs(): string { return $this->args; } public function setEnv(array $env): void { $this->env = $env; } public function getEnv(): array { return $this->env; } public function setTimeout(int $timeout): void { $this->timeout = $timeout; } public function getTimeout(): int { return $this->timeout; } public function runTestJob(string $job, Test $test, TestResult $result): void { $result->startTest($test); $_result = $this->runJob($job); $this->processChildResult( $test, $result, $_result['stdout'], $_result['stderr'] ); } public function getCommand(array $settings, string $file = null): string { $command = $this->runtime->getBinary(); if ($this->runtime->hasPCOV()) { $settings = array_merge( $settings, $this->runtime->getCurrentSettings( array_keys(ini_get_all('pcov')) ) ); } elseif ($this->runtime->hasXdebug()) { $settings = array_merge( $settings, $this->runtime->getCurrentSettings( array_keys(ini_get_all('xdebug')) ) ); } $command .= $this->settingsToParameters($settings); if (PHP_SAPI === 'phpdbg') { $command .= ' -qrr'; if (!$file) { $command .= 's='; } } if ($file) { $command .= ' ' . escapeshellarg($file); } if ($this->args) { if (!$file) { $command .= ' --'; } $command .= ' ' . $this->args; } if ($this->stderrRedirection) { $command .= ' 2>&1'; } return $command; } abstract public function runJob(string $job, array $settings = []): array; protected function settingsToParameters(array $settings): string { $buffer = ''; foreach ($settings as $setting) { $buffer .= ' -d ' . escapeshellarg($setting); } return $buffer; } private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void { $time = 0; if (!empty($stderr)) { $result->addError( $test, new Exception(trim($stderr)), $time ); } else { set_error_handler( static function ($errno, $errstr, $errfile, $errline): void { throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); } ); try { if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { $stdout = substr($stdout, 19); } $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); restore_error_handler(); if ($childResult === false) { $result->addFailure( $test, new AssertionFailedError('Test was run in child process and ended unexpectedly'), $time ); } } catch (ErrorException $e) { restore_error_handler(); $childResult = false; $result->addError( $test, new Exception(trim($stdout), 0, $e), $time ); } if ($childResult !== false) { if (!empty($childResult['output'])) { $output = $childResult['output']; } $test->setResult($childResult['testResult']); $test->addToAssertionCount($childResult['numAssertions']); $childResult = $childResult['result']; assert($childResult instanceof TestResult); if ($result->getCollectCodeCoverageInformation()) { $result->getCodeCoverage()->merge( $childResult->getCodeCoverage() ); } $time = $childResult->time(); $notImplemented = $childResult->notImplemented(); $risky = $childResult->risky(); $skipped = $childResult->skipped(); $errors = $childResult->errors(); $warnings = $childResult->warnings(); $failures = $childResult->failures(); if (!empty($notImplemented)) { $result->addError( $test, $this->getException($notImplemented[0]), $time ); } elseif (!empty($risky)) { $result->addError( $test, $this->getException($risky[0]), $time ); } elseif (!empty($skipped)) { $result->addError( $test, $this->getException($skipped[0]), $time ); } elseif (!empty($errors)) { $result->addError( $test, $this->getException($errors[0]), $time ); } elseif (!empty($warnings)) { $result->addWarning( $test, $this->getException($warnings[0]), $time ); } elseif (!empty($failures)) { $result->addFailure( $test, $this->getException($failures[0]), $time ); } } } $result->endTest($test, $time); if (!empty($output)) { print $output; } } private function getException(TestFailure $error): Exception { $exception = $error->thrownException(); if ($exception instanceof __PHP_Incomplete_Class) { $exceptionArray = []; foreach ((array) $exception as $key => $value) { $key = substr($key, strrpos($key, "\0") + 1); $exceptionArray[$key] = $value; } $exception = new SyntheticError( sprintf( '%s: %s', $exceptionArray['_PHP_Incomplete_Class_Name'], $exceptionArray['message'] ), $exceptionArray['code'], $exceptionArray['file'], $exceptionArray['line'], $exceptionArray['trace'] ); } return $exception; } } getWhitelistItems($filterData); $files = array_map( static function ($item) { return sprintf( " '%s'", $item ); }, $items ); $files = implode(",\n", $files); return <<filename = $filename; $this->document = Xml::loadFile($filename, false, true, true); $this->xpath = new DOMXPath($this->document); $this->validateConfigurationAgainstSchema(); } private function __clone() { } public function hasValidationErrors(): bool { return count($this->errors) > 0; } public function getValidationErrors(): array { $result = []; foreach ($this->errors as $error) { if (!isset($result[$error->line])) { $result[$error->line] = []; } $result[$error->line][] = trim($error->message); } return $result; } public function getFilename(): string { return $this->filename; } public function getExtensionConfiguration(): array { $result = []; foreach ($this->xpath->query('extensions/extension') as $extension) { $result[] = $this->getElementConfigurationParameters($extension); } return $result; } public function getFilterConfiguration(): array { $addUncoveredFilesFromWhitelist = true; $processUncoveredFilesFromWhitelist = false; $includeDirectory = []; $includeFile = []; $excludeDirectory = []; $excludeFile = []; $tmp = $this->xpath->query('filter/whitelist'); if ($tmp->length === 1) { if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) { $addUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'addUncoveredFilesFromWhitelist' ), true ); } if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) { $processUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'processUncoveredFilesFromWhitelist' ), false ); } $includeDirectory = $this->readFilterDirectories( 'filter/whitelist/directory' ); $includeFile = $this->readFilterFiles( 'filter/whitelist/file' ); $excludeDirectory = $this->readFilterDirectories( 'filter/whitelist/exclude/directory' ); $excludeFile = $this->readFilterFiles( 'filter/whitelist/exclude/file' ); } return [ 'whitelist' => [ 'addUncoveredFilesFromWhitelist' => $addUncoveredFilesFromWhitelist, 'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist, 'include' => [ 'directory' => $includeDirectory, 'file' => $includeFile, ], 'exclude' => [ 'directory' => $excludeDirectory, 'file' => $excludeFile, ], ], ]; } public function getGroupConfiguration(): array { return $this->parseGroupConfiguration('groups'); } public function getTestdoxGroupConfiguration(): array { return $this->parseGroupConfiguration('testdoxGroups'); } public function getListenerConfiguration(): array { $result = []; foreach ($this->xpath->query('listeners/listener') as $listener) { $result[] = $this->getElementConfigurationParameters($listener); } return $result; } public function getLoggingConfiguration(): array { $result = []; foreach ($this->xpath->query('logging/log') as $log) { assert($log instanceof DOMElement); $type = (string) $log->getAttribute('type'); $target = (string) $log->getAttribute('target'); if (!$target) { continue; } $target = $this->toAbsolutePath($target); if ($type === 'coverage-html') { if ($log->hasAttribute('lowUpperBound')) { $result['lowUpperBound'] = $this->getInteger( (string) $log->getAttribute('lowUpperBound'), 50 ); } if ($log->hasAttribute('highLowerBound')) { $result['highLowerBound'] = $this->getInteger( (string) $log->getAttribute('highLowerBound'), 90 ); } } elseif ($type === 'coverage-crap4j') { if ($log->hasAttribute('threshold')) { $result['crap4jThreshold'] = $this->getInteger( (string) $log->getAttribute('threshold'), 30 ); } } elseif ($type === 'coverage-text') { if ($log->hasAttribute('showUncoveredFiles')) { $result['coverageTextShowUncoveredFiles'] = $this->getBoolean( (string) $log->getAttribute('showUncoveredFiles'), false ); } if ($log->hasAttribute('showOnlySummary')) { $result['coverageTextShowOnlySummary'] = $this->getBoolean( (string) $log->getAttribute('showOnlySummary'), false ); } } $result[$type] = $target; } return $result; } public function getPHPConfiguration(): array { $result = [ 'include_path' => [], 'ini' => [], 'const' => [], 'var' => [], 'env' => [], 'post' => [], 'get' => [], 'cookie' => [], 'server' => [], 'files' => [], 'request' => [], ]; foreach ($this->xpath->query('php/includePath') as $includePath) { $path = (string) $includePath->textContent; if ($path) { $result['include_path'][] = $this->toAbsolutePath($path); } } foreach ($this->xpath->query('php/ini') as $ini) { assert($ini instanceof DOMElement); $name = (string) $ini->getAttribute('name'); $value = (string) $ini->getAttribute('value'); $result['ini'][$name]['value'] = $value; } foreach ($this->xpath->query('php/const') as $const) { assert($const instanceof DOMElement); $name = (string) $const->getAttribute('name'); $value = (string) $const->getAttribute('value'); $result['const'][$name]['value'] = $this->getBoolean($value, $value); } foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { foreach ($this->xpath->query('php/' . $array) as $var) { assert($var instanceof DOMElement); $name = (string) $var->getAttribute('name'); $value = (string) $var->getAttribute('value'); $verbatim = false; if ($var->hasAttribute('verbatim')) { $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); $result[$array][$name]['verbatim'] = $verbatim; } if ($var->hasAttribute('force')) { $force = $this->getBoolean($var->getAttribute('force'), false); $result[$array][$name]['force'] = $force; } if (!$verbatim) { $value = $this->getBoolean($value, $value); } $result[$array][$name]['value'] = $value; } } return $result; } public function handlePHPConfiguration(): void { $configuration = $this->getPHPConfiguration(); if (!empty($configuration['include_path'])) { ini_set( 'include_path', implode(PATH_SEPARATOR, $configuration['include_path']) . PATH_SEPARATOR . ini_get('include_path') ); } foreach ($configuration['ini'] as $name => $data) { $value = $data['value']; if (defined($value)) { $value = (string) constant($value); } ini_set($name, $value); } foreach ($configuration['const'] as $name => $data) { $value = $data['value']; if (!defined($name)) { define($name, $value); } } foreach ($configuration['var'] as $name => $data) { $GLOBALS[$name] = $data['value']; } foreach ($configuration['server'] as $name => $data) { $_SERVER[$name] = $data['value']; } foreach (['post', 'get', 'cookie', 'files', 'request'] as $array) { $target = &$GLOBALS['_' . strtoupper($array)]; foreach ($configuration[$array] as $name => $data) { $target[$name] = $data['value']; } } foreach ($configuration['env'] as $name => $data) { $value = $data['value']; $force = $data['force'] ?? false; if ($force || getenv($name) === false) { putenv("{$name}={$value}"); } $value = getenv($name); if (!isset($_ENV[$name])) { $_ENV[$name] = $value; } if ($force) { $_ENV[$name] = $value; } } } public function getPHPUnitConfiguration(): array { $result = []; $root = $this->document->documentElement; if ($root->hasAttribute('cacheTokens')) { $result['cacheTokens'] = $this->getBoolean( (string) $root->getAttribute('cacheTokens'), false ); } if ($root->hasAttribute('columns')) { $columns = (string) $root->getAttribute('columns'); if ($columns === 'max') { $result['columns'] = 'max'; } else { $result['columns'] = $this->getInteger($columns, 80); } } if ($root->hasAttribute('colors')) { if ($this->getBoolean($root->getAttribute('colors'), false)) { $result['colors'] = ResultPrinter::COLOR_AUTO; } else { $result['colors'] = ResultPrinter::COLOR_NEVER; } } if ($root->hasAttribute('stderr')) { $result['stderr'] = $this->getBoolean( (string) $root->getAttribute('stderr'), false ); } if ($root->hasAttribute('backupGlobals')) { $result['backupGlobals'] = $this->getBoolean( (string) $root->getAttribute('backupGlobals'), false ); } if ($root->hasAttribute('backupStaticAttributes')) { $result['backupStaticAttributes'] = $this->getBoolean( (string) $root->getAttribute('backupStaticAttributes'), false ); } if ($root->getAttribute('bootstrap')) { $result['bootstrap'] = $this->toAbsolutePath( (string) $root->getAttribute('bootstrap') ); } if ($root->hasAttribute('convertDeprecationsToExceptions')) { $result['convertDeprecationsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertDeprecationsToExceptions'), false ); } if ($root->hasAttribute('convertErrorsToExceptions')) { $result['convertErrorsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertErrorsToExceptions'), true ); } if ($root->hasAttribute('convertNoticesToExceptions')) { $result['convertNoticesToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertNoticesToExceptions'), true ); } if ($root->hasAttribute('convertWarningsToExceptions')) { $result['convertWarningsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertWarningsToExceptions'), true ); } if ($root->hasAttribute('forceCoversAnnotation')) { $result['forceCoversAnnotation'] = $this->getBoolean( (string) $root->getAttribute('forceCoversAnnotation'), false ); } if ($root->hasAttribute('disableCodeCoverageIgnore')) { $result['disableCodeCoverageIgnore'] = $this->getBoolean( (string) $root->getAttribute('disableCodeCoverageIgnore'), false ); } if ($root->hasAttribute('processIsolation')) { $result['processIsolation'] = $this->getBoolean( (string) $root->getAttribute('processIsolation'), false ); } if ($root->hasAttribute('stopOnDefect')) { $result['stopOnDefect'] = $this->getBoolean( (string) $root->getAttribute('stopOnDefect'), false ); } if ($root->hasAttribute('stopOnError')) { $result['stopOnError'] = $this->getBoolean( (string) $root->getAttribute('stopOnError'), false ); } if ($root->hasAttribute('stopOnFailure')) { $result['stopOnFailure'] = $this->getBoolean( (string) $root->getAttribute('stopOnFailure'), false ); } if ($root->hasAttribute('stopOnWarning')) { $result['stopOnWarning'] = $this->getBoolean( (string) $root->getAttribute('stopOnWarning'), false ); } if ($root->hasAttribute('stopOnIncomplete')) { $result['stopOnIncomplete'] = $this->getBoolean( (string) $root->getAttribute('stopOnIncomplete'), false ); } if ($root->hasAttribute('stopOnRisky')) { $result['stopOnRisky'] = $this->getBoolean( (string) $root->getAttribute('stopOnRisky'), false ); } if ($root->hasAttribute('stopOnSkipped')) { $result['stopOnSkipped'] = $this->getBoolean( (string) $root->getAttribute('stopOnSkipped'), false ); } if ($root->hasAttribute('failOnWarning')) { $result['failOnWarning'] = $this->getBoolean( (string) $root->getAttribute('failOnWarning'), false ); } if ($root->hasAttribute('failOnRisky')) { $result['failOnRisky'] = $this->getBoolean( (string) $root->getAttribute('failOnRisky'), false ); } if ($root->hasAttribute('testSuiteLoaderClass')) { $result['testSuiteLoaderClass'] = (string) $root->getAttribute( 'testSuiteLoaderClass' ); } if ($root->hasAttribute('defaultTestSuite')) { $result['defaultTestSuite'] = (string) $root->getAttribute( 'defaultTestSuite' ); } if ($root->getAttribute('testSuiteLoaderFile')) { $result['testSuiteLoaderFile'] = $this->toAbsolutePath( (string) $root->getAttribute('testSuiteLoaderFile') ); } if ($root->hasAttribute('printerClass')) { $result['printerClass'] = (string) $root->getAttribute( 'printerClass' ); } if ($root->getAttribute('printerFile')) { $result['printerFile'] = $this->toAbsolutePath( (string) $root->getAttribute('printerFile') ); } if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) { $result['beStrictAboutChangesToGlobalState'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutChangesToGlobalState'), false ); } if ($root->hasAttribute('beStrictAboutOutputDuringTests')) { $result['disallowTestOutput'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutOutputDuringTests'), false ); } if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) { $result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'), false ); } if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) { $result['reportUselessTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'), true ); } if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) { $result['disallowTodoAnnotatedTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'), false ); } if ($root->hasAttribute('beStrictAboutCoversAnnotation')) { $result['strictCoverage'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutCoversAnnotation'), false ); } if ($root->hasAttribute('defaultTimeLimit')) { $result['defaultTimeLimit'] = $this->getInteger( (string) $root->getAttribute('defaultTimeLimit'), 1 ); } if ($root->hasAttribute('enforceTimeLimit')) { $result['enforceTimeLimit'] = $this->getBoolean( (string) $root->getAttribute('enforceTimeLimit'), false ); } if ($root->hasAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage')) { $result['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $this->getBoolean( (string) $root->getAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage'), false ); } if ($root->hasAttribute('timeoutForSmallTests')) { $result['timeoutForSmallTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForSmallTests'), 1 ); } if ($root->hasAttribute('timeoutForMediumTests')) { $result['timeoutForMediumTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForMediumTests'), 10 ); } if ($root->hasAttribute('timeoutForLargeTests')) { $result['timeoutForLargeTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForLargeTests'), 60 ); } if ($root->hasAttribute('reverseDefectList')) { $result['reverseDefectList'] = $this->getBoolean( (string) $root->getAttribute('reverseDefectList'), false ); } if ($root->hasAttribute('verbose')) { $result['verbose'] = $this->getBoolean( (string) $root->getAttribute('verbose'), false ); } if ($root->hasAttribute('testdox')) { $testdox = $this->getBoolean( (string) $root->getAttribute('testdox'), false ); if ($testdox) { if (isset($result['printerClass'])) { $result['conflictBetweenPrinterClassAndTestdox'] = true; } else { $result['printerClass'] = CliTestDoxPrinter::class; } } } if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) { $result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean( (string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'), false ); } if ($root->hasAttribute('extensionsDirectory')) { $result['extensionsDirectory'] = $this->toAbsolutePath( (string) $root->getAttribute( 'extensionsDirectory' ) ); } if ($root->hasAttribute('cacheResult')) { $result['cacheResult'] = $this->getBoolean( (string) $root->getAttribute('cacheResult'), true ); } if ($root->hasAttribute('cacheResultFile')) { $result['cacheResultFile'] = $this->toAbsolutePath( (string) $root->getAttribute('cacheResultFile') ); } if ($root->hasAttribute('executionOrder')) { foreach (explode(',', $root->getAttribute('executionOrder')) as $order) { switch ($order) { case 'default': $result['executionOrder'] = TestSuiteSorter::ORDER_DEFAULT; $result['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFAULT; $result['resolveDependencies'] = false; break; case 'defects': $result['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFECTS_FIRST; break; case 'depends': $result['resolveDependencies'] = true; break; case 'duration': $result['executionOrder'] = TestSuiteSorter::ORDER_DURATION; break; case 'no-depends': $result['resolveDependencies'] = false; break; case 'random': $result['executionOrder'] = TestSuiteSorter::ORDER_RANDOMIZED; break; case 'reverse': $result['executionOrder'] = TestSuiteSorter::ORDER_REVERSED; break; case 'size': $result['executionOrder'] = TestSuiteSorter::ORDER_SIZE; break; } } } if ($root->hasAttribute('resolveDependencies')) { $result['resolveDependencies'] = $this->getBoolean( (string) $root->getAttribute('resolveDependencies'), false ); } if ($root->hasAttribute('noInteraction')) { $result['noInteraction'] = $this->getBoolean( (string) $root->getAttribute('noInteraction'), false ); } return $result; } public function getTestSuiteConfiguration(string $testSuiteFilter = ''): TestSuite { $testSuiteNodes = $this->xpath->query('testsuites/testsuite'); if ($testSuiteNodes->length === 0) { $testSuiteNodes = $this->xpath->query('testsuite'); } if ($testSuiteNodes->length === 1) { return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter); } $suite = new TestSuite; foreach ($testSuiteNodes as $testSuiteNode) { $suite->addTestSuite( $this->getTestSuite($testSuiteNode, $testSuiteFilter) ); } return $suite; } public function getTestSuiteNames(): array { $names = []; foreach ($this->xpath->query('*/testsuite') as $node) { $names[] = $node->getAttribute('name'); } return $names; } private function validateConfigurationAgainstSchema(): void { $original = libxml_use_internal_errors(true); $xsdFilename = __DIR__ . '/../../phpunit.xsd'; if (defined('__PHPUNIT_PHAR_ROOT__')) { $xsdFilename = __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd'; } $this->document->schemaValidateSource(file_get_contents($xsdFilename)); $this->errors = libxml_get_errors(); libxml_clear_errors(); libxml_use_internal_errors($original); } private function getConfigurationArguments(DOMNodeList $nodes): array { $arguments = []; if ($nodes->length === 0) { return $arguments; } foreach ($nodes as $node) { if (!$node instanceof DOMElement) { continue; } if ($node->tagName !== 'arguments') { continue; } foreach ($node->childNodes as $argument) { if (!$argument instanceof DOMElement) { continue; } if ($argument->tagName === 'file' || $argument->tagName === 'directory') { $arguments[] = $this->toAbsolutePath((string) $argument->textContent); } else { $arguments[] = Xml::xmlToVariable($argument); } } } return $arguments; } private function getTestSuite(DOMElement $testSuiteNode, string $testSuiteFilter = ''): TestSuite { if ($testSuiteNode->hasAttribute('name')) { $suite = new TestSuite( (string) $testSuiteNode->getAttribute('name') ); } else { $suite = new TestSuite; } $exclude = []; foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) { $excludeFile = (string) $excludeNode->textContent; if ($excludeFile) { $exclude[] = $this->toAbsolutePath($excludeFile); } } $fileIteratorFacade = new FileIteratorFacade; $testSuiteFilter = $testSuiteFilter ? explode(',', $testSuiteFilter) : []; foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) { assert($directoryNode instanceof DOMElement); if (!empty($testSuiteFilter) && !in_array($directoryNode->parentNode->getAttribute('name'), $testSuiteFilter, true)) { continue; } $directory = (string) $directoryNode->textContent; if (empty($directory)) { continue; } if (!$this->satisfiesPhpVersion($directoryNode)) { continue; } $files = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($directory), $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : 'Test.php', $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', $exclude ); $suite->addTestFiles($files); } foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) { assert($fileNode instanceof DOMElement); if (!empty($testSuiteFilter) && !in_array($fileNode->parentNode->getAttribute('name'), $testSuiteFilter, true)) { continue; } $file = (string) $fileNode->textContent; if (empty($file)) { continue; } $file = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($file) ); if (!isset($file[0])) { continue; } $file = $file[0]; if (!$this->satisfiesPhpVersion($fileNode)) { continue; } $suite->addTestFile($file); } return $suite; } private function satisfiesPhpVersion(DOMElement $node): bool { $phpVersion = PHP_VERSION; $phpVersionOperator = '>='; if ($node->hasAttribute('phpVersion')) { $phpVersion = (string) $node->getAttribute('phpVersion'); } if ($node->hasAttribute('phpVersionOperator')) { $phpVersionOperator = (string) $node->getAttribute('phpVersionOperator'); } return version_compare(PHP_VERSION, $phpVersion, (new VersionComparisonOperator($phpVersionOperator))->asString()); } private function getBoolean(string $value, $default) { if (strtolower($value) === 'false') { return false; } if (strtolower($value) === 'true') { return true; } return $default; } private function getInteger(string $value, int $default): int { if (is_numeric($value)) { return (int) $value; } return $default; } private function readFilterDirectories(string $query): array { $directories = []; foreach ($this->xpath->query($query) as $directoryNode) { assert($directoryNode instanceof DOMElement); $directoryPath = (string) $directoryNode->textContent; if (!$directoryPath) { continue; } $directories[] = [ 'path' => $this->toAbsolutePath($directoryPath), 'prefix' => $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', 'suffix' => $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', 'group' => $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT', ]; } return $directories; } private function readFilterFiles(string $query): array { $files = []; foreach ($this->xpath->query($query) as $file) { $filePath = (string) $file->textContent; if ($filePath) { $files[] = $this->toAbsolutePath($filePath); } } return $files; } private function toAbsolutePath(string $path, bool $useIncludePath = false): string { $path = trim($path); if (strpos($path, '/') === 0) { return $path; } if (defined('PHP_WINDOWS_VERSION_BUILD') && ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) { return $path; } if (strpos($path, '://') !== false) { return $path; } $file = dirname($this->filename) . DIRECTORY_SEPARATOR . $path; if ($useIncludePath && !file_exists($file)) { $includePathFile = stream_resolve_include_path($path); if ($includePathFile) { $file = $includePathFile; } } return $file; } private function parseGroupConfiguration(string $root): array { $groups = [ 'include' => [], 'exclude' => [], ]; foreach ($this->xpath->query($root . '/include/group') as $group) { $groups['include'][] = (string) $group->textContent; } foreach ($this->xpath->query($root . '/exclude/group') as $group) { $groups['exclude'][] = (string) $group->textContent; } return $groups; } private function getElementConfigurationParameters(DOMElement $element): array { $class = (string) $element->getAttribute('class'); $file = ''; $arguments = $this->getConfigurationArguments($element->childNodes); if ($element->getAttribute('file')) { $file = $this->toAbsolutePath( (string) $element->getAttribute('file'), true ); } return [ 'class' => $class, 'file' => $file, 'arguments' => $arguments, ]; } } 0) { $json = (array) $json; } else { return; } } ksort($json); foreach ($json as $key => &$value) { self::recursiveSort($value); } } } 1 && $arg[1] === '-' && !$long_options)) { $non_opts[] = $args[$i]; continue; } if (strlen($arg) > 1 && $arg[1] === '-') { self::parseLongOption( substr($arg, 2), $long_options, $opts, $args ); } else { self::parseShortOption( substr($arg, 1), $short_options, $opts, $args ); } } return [$opts, $non_opts]; } private static function parseShortOption(string $arg, string $short_options, array &$opts, array &$args): void { $argLen = strlen($arg); for ($i = 0; $i < $argLen; $i++) { $opt = $arg[$i]; $opt_arg = null; if ($arg[$i] === ':' || ($spec = strstr($short_options, $opt)) === false) { throw new Exception( "unrecognized option -- {$opt}" ); } if (strlen($spec) > 1 && $spec[1] === ':') { if ($i + 1 < $argLen) { $opts[] = [$opt, substr($arg, $i + 1)]; break; } if (!(strlen($spec) > 2 && $spec[2] === ':')) { if (false === $opt_arg = current($args)) { throw new Exception( "option requires an argument -- {$opt}" ); } next($args); } } $opts[] = [$opt, $opt_arg]; } } private static function parseLongOption(string $arg, array $long_options, array &$opts, array &$args): void { $count = count($long_options); $list = explode('=', $arg); $opt = $list[0]; $opt_arg = null; if (count($list) > 1) { $opt_arg = $list[1]; } $opt_len = strlen($opt); foreach ($long_options as $i => $long_opt) { $opt_start = substr($long_opt, 0, $opt_len); if ($opt_start !== $opt) { continue; } $opt_rest = substr($long_opt, $opt_len); if ($opt_rest !== '' && $i + 1 < $count && $opt[0] !== '=' && strpos($long_options[$i + 1], $opt) === 0) { throw new Exception( "option --{$opt} is ambiguous" ); } if (substr($long_opt, -1) === '=') { if (substr($long_opt, -2) !== '==' && !strlen((string) $opt_arg)) { if (false === $opt_arg = current($args)) { throw new Exception( "option --{$opt} requires an argument" ); } next($args); } } elseif ($opt_arg) { throw new Exception( "option --{$opt} doesn't allow an argument" ); } $full_option = '--' . preg_replace('/={1,2}$/', '', $long_opt); $opts[] = [$full_option, $opt_arg]; return; } throw new Exception("unrecognized option --{$opt}"); } } convertDeprecationsToExceptions = $convertDeprecationsToExceptions; $this->convertErrorsToExceptions = $convertErrorsToExceptions; $this->convertNoticesToExceptions = $convertNoticesToExceptions; $this->convertWarningsToExceptions = $convertWarningsToExceptions; } public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool { if (!($errorNumber & error_reporting())) { return false; } switch ($errorNumber) { case E_NOTICE: case E_USER_NOTICE: case E_STRICT: if (!$this->convertNoticesToExceptions) { return false; } throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); case E_WARNING: case E_USER_WARNING: if (!$this->convertWarningsToExceptions) { return false; } throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); case E_DEPRECATED: case E_USER_DEPRECATED: if (!$this->convertDeprecationsToExceptions) { return false; } throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); default: if (!$this->convertErrorsToExceptions) { return false; } throw new Error($errorString, $errorNumber, $errorFile, $errorLine); } } public function register(): void { if ($this->registered) { return; } $oldErrorHandler = set_error_handler($this); if ($oldErrorHandler !== null) { restore_error_handler(); return; } $this->registered = true; } public function unregister(): void { if (!$this->registered) { return; } restore_error_handler(); } } out = $out; return; } if (strpos($out, 'socket://') === 0) { $out = explode(':', str_replace('socket://', '', $out)); if (count($out) !== 2) { throw new Exception; } $this->out = fsockopen($out[0], $out[1]); } else { if (strpos($out, 'php://') === false && !Filesystem::createDirectory(dirname($out))) { throw new Exception(sprintf('Directory "%s" was not created', dirname($out))); } $this->out = fopen($out, 'wt'); } $this->outTarget = $out; } public function flush(): void { if ($this->out && strncmp($this->outTarget, 'php://', 6) !== 0) { assert(is_resource($this->out)); fclose($this->out); } } public function incrementalFlush(): void { if ($this->out) { assert(is_resource($this->out)); fflush($this->out); } else { flush(); } } public function write(string $buffer): void { if ($this->out) { assert(is_resource($this->out)); fwrite($this->out, $buffer); if ($this->autoFlush) { $this->incrementalFlush(); } } else { if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { $buffer = htmlspecialchars($buffer, ENT_SUBSTITUTE); } print $buffer; if ($this->autoFlush) { $this->incrementalFlush(); } } } public function getAutoFlush(): bool { return $this->autoFlush; } public function setAutoFlush(bool $autoFlush): void { $this->autoFlush = $autoFlush; } } printHeader($result); $this->printFooter($result); } public function addError(Test $test, Throwable $t, float $time): void { $this->printEvent( 'testFailed', [ 'name' => $test->getName(), 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time), ] ); } public function addWarning(Test $test, Warning $e, float $time): void { $this->write(self::getMessage($e) . PHP_EOL); } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $parameters = [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), 'duration' => self::toMilliseconds($time), ]; if ($e instanceof ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure instanceof ComparisonFailure) { $expectedString = $comparisonFailure->getExpectedAsString(); if ($expectedString === null || empty($expectedString)) { $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); } $actualString = $comparisonFailure->getActualAsString(); if ($actualString === null || empty($actualString)) { $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); } if ($actualString !== null && $expectedString !== null) { $parameters['type'] = 'comparisonFailure'; $parameters['actual'] = $actualString; $parameters['expected'] = $expectedString; } } } $this->printEvent('testFailed', $parameters); } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { $this->printIgnoredTest($test->getName(), $t, $time); } public function addRiskyTest(Test $test, Throwable $t, float $time): void { $this->addError($test, $t, $time); } public function addSkippedTest(Test $test, Throwable $t, float $time): void { $testName = $test->getName(); if ($this->startedTestName !== $testName) { $this->startTest($test); $this->printIgnoredTest($testName, $t, $time); $this->endTest($test, $time); } else { $this->printIgnoredTest($testName, $t, $time); } } public function printIgnoredTest($testName, Throwable $t, float $time): void { $this->printEvent( 'testIgnored', [ 'name' => $testName, 'message' => self::getMessage($t), 'details' => self::getDetails($t), 'duration' => self::toMilliseconds($time), ] ); } public function startTestSuite(TestSuite $suite): void { if (stripos(ini_get('disable_functions'), 'getmypid') === false) { $this->flowId = getmypid(); } else { $this->flowId = false; } if (!$this->isSummaryTestCountPrinted) { $this->isSummaryTestCountPrinted = true; $this->printEvent( 'testCount', ['count' => count($suite)] ); } $suiteName = $suite->getName(); if (empty($suiteName)) { return; } $parameters = ['name' => $suiteName]; if (class_exists($suiteName, false)) { $fileName = self::getFileName($suiteName); $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; } else { $split = explode('::', $suiteName); if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { $fileName = self::getFileName($split[0]); $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; $parameters['name'] = $split[1]; } } $this->printEvent('testSuiteStarted', $parameters); } public function endTestSuite(TestSuite $suite): void { $suiteName = $suite->getName(); if (empty($suiteName)) { return; } $parameters = ['name' => $suiteName]; if (!class_exists($suiteName, false)) { $split = explode('::', $suiteName); if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { $parameters['name'] = $split[1]; } } $this->printEvent('testSuiteFinished', $parameters); } public function startTest(Test $test): void { $testName = $test->getName(); $this->startedTestName = $testName; $params = ['name' => $testName]; if ($test instanceof TestCase) { $className = get_class($test); $fileName = self::getFileName($className); $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; } $this->printEvent('testStarted', $params); } public function endTest(Test $test, float $time): void { parent::endTest($test, $time); $this->printEvent( 'testFinished', [ 'name' => $test->getName(), 'duration' => self::toMilliseconds($time), ] ); } protected function writeProgress(string $progress): void { } private function printEvent(string $eventName, array $params = []): void { $this->write("\n##teamcity[{$eventName}"); if ($this->flowId) { $params['flowId'] = $this->flowId; } foreach ($params as $key => $value) { $escapedValue = self::escapeValue((string) $value); $this->write(" {$key}='{$escapedValue}'"); } $this->write("]\n"); } private static function getMessage(Throwable $t): string { $message = ''; if ($t instanceof ExceptionWrapper) { if ($t->getClassName() !== '') { $message .= $t->getClassName(); } if ($message !== '' && $t->getMessage() !== '') { $message .= ' : '; } } return $message . $t->getMessage(); } private static function getDetails(Throwable $t): string { $stackTrace = Filter::getFilteredStacktrace($t); $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); while ($previous) { $stackTrace .= "\nCaused by\n" . TestFailure::exceptionToString($previous) . "\n" . Filter::getFilteredStacktrace($previous); $previous = $previous instanceof ExceptionWrapper ? $previous->getPreviousWrapped() : $previous->getPrevious(); } return ' ' . str_replace("\n", "\n ", $stackTrace); } private static function getPrimitiveValueAsString($value): ?string { if ($value === null) { return 'null'; } if (is_bool($value)) { return $value ? 'true' : 'false'; } if (is_scalar($value)) { return print_r($value, true); } return null; } private static function escapeValue(string $text): string { return str_replace( ['|', "'", "\n", "\r", ']', '['], ['||', "|'", '|n', '|r', '|]', '|['], $text ); } private static function getFileName($className): string { try { return (new ReflectionClass($className))->getFileName(); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } private static function toMilliseconds(float $time): int { return (int) round($time * 1000); } } document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('testsuites'); $this->document->appendChild($this->root); parent::__construct($out); $this->reportRiskyTests = $reportRiskyTests; } public function flush(): void { $this->write($this->getXML()); parent::flush(); } public function addError(Test $test, Throwable $t, float $time): void { $this->doAddFault($test, $t, $time, 'error'); $this->testSuiteErrors[$this->testSuiteLevel]++; } public function addWarning(Test $test, Warning $e, float $time): void { $this->doAddFault($test, $e, $time, 'warning'); $this->testSuiteWarnings[$this->testSuiteLevel]++; } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->doAddFault($test, $e, $time, 'failure'); $this->testSuiteFailures[$this->testSuiteLevel]++; } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { $this->doAddSkipped(); } public function addRiskyTest(Test $test, Throwable $t, float $time): void { if (!$this->reportRiskyTests || $this->currentTestCase === null) { return; } $error = $this->document->createElement( 'error', Xml::prepareString( "Risky Test\n" . Filter::getFilteredStacktrace($t) ) ); $error->setAttribute('type', get_class($t)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } public function addSkippedTest(Test $test, Throwable $t, float $time): void { $this->doAddSkipped(); } public function startTestSuite(TestSuite $suite): void { $testSuite = $this->document->createElement('testsuite'); $testSuite->setAttribute('name', $suite->getName()); if (class_exists($suite->getName(), false)) { try { $class = new ReflectionClass($suite->getName()); $testSuite->setAttribute('file', $class->getFileName()); } catch (ReflectionException $e) { } } if ($this->testSuiteLevel > 0) { $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); } else { $this->root->appendChild($testSuite); } $this->testSuiteLevel++; $this->testSuites[$this->testSuiteLevel] = $testSuite; $this->testSuiteTests[$this->testSuiteLevel] = 0; $this->testSuiteAssertions[$this->testSuiteLevel] = 0; $this->testSuiteErrors[$this->testSuiteLevel] = 0; $this->testSuiteWarnings[$this->testSuiteLevel] = 0; $this->testSuiteFailures[$this->testSuiteLevel] = 0; $this->testSuiteSkipped[$this->testSuiteLevel] = 0; $this->testSuiteTimes[$this->testSuiteLevel] = 0; } public function endTestSuite(TestSuite $suite): void { $this->testSuites[$this->testSuiteLevel]->setAttribute( 'tests', (string) $this->testSuiteTests[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'assertions', (string) $this->testSuiteAssertions[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'errors', (string) $this->testSuiteErrors[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'warnings', (string) $this->testSuiteWarnings[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'failures', (string) $this->testSuiteFailures[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'skipped', (string) $this->testSuiteSkipped[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'time', sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) ); if ($this->testSuiteLevel > 1) { $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } $this->testSuiteLevel--; } public function startTest(Test $test): void { $usesDataprovider = false; if (method_exists($test, 'usesDataProvider')) { $usesDataprovider = $test->usesDataProvider(); } $testCase = $this->document->createElement('testcase'); $testCase->setAttribute('name', $test->getName()); try { $class = new ReflectionClass($test); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $methodName = $test->getName(!$usesDataprovider); if ($class->hasMethod($methodName)) { try { $method = $class->getMethod($methodName); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $testCase->setAttribute('class', $class->getName()); $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); $testCase->setAttribute('file', $class->getFileName()); $testCase->setAttribute('line', (string) $method->getStartLine()); } $this->currentTestCase = $testCase; } public function endTest(Test $test, float $time): void { $numAssertions = 0; if (method_exists($test, 'getNumAssertions')) { $numAssertions = $test->getNumAssertions(); } $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', (string) $numAssertions ); $this->currentTestCase->setAttribute( 'time', sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $testOutput = ''; if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; } if (!empty($testOutput)) { $systemOut = $this->document->createElement( 'system-out', Xml::prepareString($testOutput) ); $this->currentTestCase->appendChild($systemOut); } $this->currentTestCase = null; } public function getXML(): string { return $this->document->saveXML(); } private function doAddFault(Test $test, Throwable $t, float $time, $type): void { if ($this->currentTestCase === null) { return; } if ($test instanceof SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= TestFailure::exceptionToString($t) . "\n" . Filter::getFilteredStacktrace($t); $fault = $this->document->createElement( $type, Xml::prepareString($buffer) ); if ($t instanceof ExceptionWrapper) { $fault->setAttribute('type', $t->getClassName()); } else { $fault->setAttribute('type', get_class($t)); } $this->currentTestCase->appendChild($fault); } private function doAddSkipped(): void { if ($this->currentTestCase === null) { return; } $skipped = $this->document->createElement('skipped'); $this->currentTestCase->appendChild($skipped); $this->testSuiteSkipped[$this->testSuiteLevel]++; } } getSyntheticTrace(); $eFile = $t->getSyntheticFile(); $eLine = $t->getSyntheticLine(); } elseif ($t instanceof Exception) { $eTrace = $t->getSerializableTrace(); $eFile = $t->getFile(); $eLine = $t->getLine(); } else { if ($t->getPrevious()) { $t = $t->getPrevious(); } $eTrace = $t->getTrace(); $eFile = $t->getFile(); $eLine = $t->getLine(); } if (!self::frameExists($eTrace, $eFile, $eLine)) { array_unshift( $eTrace, ['file' => $eFile, 'line' => $eLine] ); } $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? __PHPUNIT_PHAR_ROOT__ : false; $blacklist = new Blacklist; foreach ($eTrace as $frame) { if (self::shouldPrintFrame($frame, $prefix, $blacklist)) { $filteredStacktrace .= sprintf( "%s:%s\n", $frame['file'], $frame['line'] ?? '?' ); } } return $filteredStacktrace; } private static function shouldPrintFrame($frame, $prefix, Blacklist $blacklist): bool { if (!isset($frame['file'])) { return false; } $file = $frame['file']; $fileIsNotPrefixed = $prefix === false || strpos($file, $prefix) !== 0; if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); } else { $script = ''; } return is_file($file) && self::fileIsBlacklisted($file, $blacklist) && $fileIsNotPrefixed && $file !== $script; } private static function fileIsBlacklisted($file, Blacklist $blacklist): bool { return (empty($GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST']) || !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'], true)) && !$blacklist->isBlacklisted($file); } private static function frameExists(array $trace, string $file, int $line): bool { foreach ($trace as $frame) { if (isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line) { return true; } } return false; } } importNode($element, true); } public static function load($actual, bool $isHtml = false, string $filename = '', bool $xinclude = false, bool $strict = false): DOMDocument { if ($actual instanceof DOMDocument) { return $actual; } if (!is_string($actual)) { throw new Exception('Could not load XML from ' . gettype($actual)); } if ($actual === '') { throw new Exception('Could not load XML from empty string'); } if ($xinclude) { $cwd = getcwd(); @chdir(dirname($filename)); } $document = new DOMDocument; $document->preserveWhiteSpace = false; $internal = libxml_use_internal_errors(true); $message = ''; $reporting = error_reporting(0); if ($filename !== '') { $document->documentURI = $filename; } if ($isHtml) { $loaded = $document->loadHTML($actual); } else { $loaded = $document->loadXML($actual); } if (!$isHtml && $xinclude) { $document->xinclude(); } foreach (libxml_get_errors() as $error) { $message .= "\n" . $error->message; } libxml_use_internal_errors($internal); error_reporting($reporting); if (isset($cwd)) { @chdir($cwd); } if ($loaded === false || ($strict && $message !== '')) { if ($filename !== '') { throw new Exception( sprintf( 'Could not load "%s".%s', $filename, $message !== '' ? "\n" . $message : '' ) ); } if ($message === '') { $message = 'Could not load XML for unknown reason'; } throw new Exception($message); } return $document; } public static function loadFile(string $filename, bool $isHtml = false, bool $xinclude = false, bool $strict = false): DOMDocument { $reporting = error_reporting(0); $contents = file_get_contents($filename); error_reporting($reporting); if ($contents === false) { throw new Exception( sprintf( 'Could not read "%s".', $filename ) ); } return self::load($contents, $isHtml, $filename, $xinclude, $strict); } public static function removeCharacterDataNodes(DOMNode $node): void { if ($node->hasChildNodes()) { for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { $node->removeChild($child); } } } } public static function prepareString(string $string): string { return preg_replace( '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', htmlspecialchars( self::convertToUtf8($string), ENT_QUOTES ) ); } public static function xmlToVariable(DOMElement $element) { $variable = null; switch ($element->tagName) { case 'array': $variable = []; foreach ($element->childNodes as $entry) { if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { continue; } $item = $entry->childNodes->item(0); if ($item instanceof DOMText) { $item = $entry->childNodes->item(1); } $value = self::xmlToVariable($item); if ($entry->hasAttribute('key')) { $variable[(string) $entry->getAttribute('key')] = $value; } else { $variable[] = $value; } } break; case 'object': $className = $element->getAttribute('class'); if ($element->hasChildNodes()) { $arguments = $element->childNodes->item(0)->childNodes; $constructorArgs = []; foreach ($arguments as $argument) { if ($argument instanceof DOMElement) { $constructorArgs[] = self::xmlToVariable($argument); } } try { assert(class_exists($className)); $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } else { $variable = new $className; } break; case 'boolean': $variable = $element->textContent === 'true'; break; case 'integer': case 'double': case 'string': $variable = $element->textContent; settype($variable, $element->tagName); break; } return $variable; } private static function convertToUtf8(string $string): string { if (!self::isUtf8($string)) { $string = mb_convert_encoding($string, 'UTF-8'); } return $string; } private static function isUtf8(string $string): bool { $length = strlen($string); for ($i = 0; $i < $length; $i++) { if (ord($string[$i]) < 0x80) { $n = 0; } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { $n = 1; } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { $n = 2; } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { return false; } } } return true; } } '·', "\t" => '⇥', ]; private const WHITESPACE_EOL_MAP = [ ' ' => '·', "\t" => '⇥', "\n" => '↵', "\r" => '⟵', ]; private static $ansiCodes = [ 'reset' => '0', 'bold' => '1', 'dim' => '2', 'dim-reset' => '22', 'underlined' => '4', 'fg-default' => '39', 'fg-black' => '30', 'fg-red' => '31', 'fg-green' => '32', 'fg-yellow' => '33', 'fg-blue' => '34', 'fg-magenta' => '35', 'fg-cyan' => '36', 'fg-white' => '37', 'bg-default' => '49', 'bg-black' => '40', 'bg-red' => '41', 'bg-green' => '42', 'bg-yellow' => '43', 'bg-blue' => '44', 'bg-magenta' => '45', 'bg-cyan' => '46', 'bg-white' => '47', ]; public static function colorize(string $color, string $buffer): string { if (trim($buffer) === '') { return $buffer; } $codes = array_map('\trim', explode(',', $color)); $styles = []; foreach ($codes as $code) { if (isset(self::$ansiCodes[$code])) { $styles[] = self::$ansiCodes[$code] ?? ''; } } if (empty($styles)) { return $buffer; } return self::optimizeColor(sprintf("\x1b[%sm", implode(';', $styles)) . $buffer . "\x1b[0m"); } public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = false): string { if ($prevPath === null) { $prevPath = ''; } $path = explode(DIRECTORY_SEPARATOR, $path); $prevPath = explode(DIRECTORY_SEPARATOR, $prevPath); for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { if ($path[$i] == $prevPath[$i]) { $path[$i] = self::dim($path[$i]); } } if ($colorizeFilename) { $last = count($path) - 1; $path[$last] = preg_replace_callback( '/([\-_\.]+|phpt$)/', static function ($matches) { return self::dim($matches[0]); }, $path[$last] ); } return self::optimizeColor(implode(self::dim(DIRECTORY_SEPARATOR), $path)); } public static function dim(string $buffer): string { if (trim($buffer) === '') { return $buffer; } return "\e[2m{$buffer}\e[22m"; } public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = false): string { $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; return preg_replace_callback('/\s+/', static function ($matches) use ($replaceMap) { return self::dim(strtr($matches[0], $replaceMap)); }, $buffer); } private static function optimizeColor(string $buffer): string { $patterns = [ "/\e\\[22m\e\\[2m/" => '', "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/" => "\e[$1;$2m", "/(\e\\[[^m]*m)+(\e\\[0m)/" => '$2', ]; return preg_replace(array_keys($patterns), array_values($patterns), $buffer); } } PHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t \-.|~^]+)[ \t]*\r?$/m'; private const REGEX_REQUIRES_OS = '/@requires\s+(?POS(?:FAMILY)?)\s+(?P.+?)[ \t]*\r?$/m'; private const REGEX_REQUIRES_SETTING = '/@requires\s+(?Psetting)\s+(?P([^ ]+?))\s*(?P[\w\.-]+[\w\.]?)?[ \t]*\r?$/m'; private const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^\s<>=!]+))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; private const REGEX_TEST_WITH = '/@testWith\s+/'; private const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m'; private $docComment; private $isMethod; private $symbolAnnotations; private $parsedRequirements; private $startLine; private $endLine; private $fileName; private $name; private $className; public static function ofClass(ReflectionClass $class): self { $className = $class->getName(); return new self( (string) $class->getDocComment(), false, self::extractAnnotationsFromReflector($class), $class->getStartLine(), $class->getEndLine(), $class->getFileName(), $className, $className ); } public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self { return new self( (string) $method->getDocComment(), true, self::extractAnnotationsFromReflector($method), $method->getStartLine(), $method->getEndLine(), $method->getFileName(), $method->getName(), $classNameInHierarchy ); } private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) { $this->docComment = $docComment; $this->isMethod = $isMethod; $this->symbolAnnotations = $symbolAnnotations; $this->startLine = $startLine; $this->endLine = $endLine; $this->fileName = $fileName; $this->name = $name; $this->className = $className; } public function requirements(): array { if ($this->parsedRequirements !== null) { return $this->parsedRequirements; } $offset = $this->startLine; $requires = []; $recordedSettings = []; $extensionVersions = []; $recordedOffsets = [ '__FILE' => realpath($this->fileName), ]; $lines = preg_replace(['#^/\*{2}#', '#\*/$#'], '', preg_split('/\r\n|\r|\n/', $this->docComment)); $offset -= count($lines); foreach ($lines as $line) { if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { $requires[$matches['name']] = $matches['value']; $recordedOffsets[$matches['name']] = $offset; } if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { $requires[$matches['name']] = [ 'version' => $matches['version'], 'operator' => $matches['operator'], ]; $recordedOffsets[$matches['name']] = $offset; } if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { if (!empty($requires[$matches['name']])) { $offset++; continue; } try { $versionConstraintParser = new VersionConstraintParser; $requires[$matches['name'] . '_constraint'] = [ 'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])), ]; $recordedOffsets[$matches['name'] . '_constraint'] = $offset; } catch (\PharIo\Version\Exception $e) { throw new Warning($e->getMessage(), $e->getCode(), $e); } } if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { $recordedSettings[$matches['setting']] = $matches['value']; $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; } if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { $name = $matches['name'] . 's'; if (!isset($requires[$name])) { $requires[$name] = []; } $requires[$name][] = $matches['value']; $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; if ($name === 'extensions' && !empty($matches['version'])) { $extensionVersions[$matches['value']] = [ 'version' => $matches['version'], 'operator' => $matches['operator'], ]; } } $offset++; } return $this->parsedRequirements = array_merge( $requires, ['__OFFSET' => $recordedOffsets], array_filter([ 'setting' => $recordedSettings, 'extension_versions' => $extensionVersions, ]) ); } public function expectedException() { $docComment = (string) substr($this->docComment, 3, -2); if (1 !== preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) { return false; } $class = $matches[1]; $annotations = $this->symbolAnnotations(); $code = null; $message = ''; $messageRegExp = ''; if (isset($matches[2])) { $message = trim($matches[2]); } elseif (isset($annotations['expectedExceptionMessage'])) { $message = $this->parseAnnotationContent($annotations['expectedExceptionMessage'][0]); } if (isset($annotations['expectedExceptionMessageRegExp'])) { $messageRegExp = $this->parseAnnotationContent($annotations['expectedExceptionMessageRegExp'][0]); } if (isset($matches[3])) { $code = $matches[3]; } elseif (isset($annotations['expectedExceptionCode'])) { $code = $this->parseAnnotationContent($annotations['expectedExceptionCode'][0]); } if (is_numeric($code)) { $code = (int) $code; } elseif (is_string($code) && defined($code)) { $code = (int) constant($code); } return [ 'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp, ]; } public function getProvidedData(): ?array { $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); if ($data === null) { return null; } if ($data === []) { throw new SkippedTestError; } foreach ($data as $key => $value) { if (!is_array($value)) { throw new InvalidDataSetException( sprintf( 'Data set %s is invalid.', is_int($key) ? '#' . $key : '"' . $key . '"' ) ); } } return $data; } public function getInlineAnnotations(): array { $code = file($this->fileName); $lineNumber = $this->startLine; $startLine = $this->startLine - 1; $endLine = $this->endLine - 1; $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); $annotations = []; foreach ($codeLines as $line) { if (preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { $annotations[strtolower($matches['name'])] = [ 'line' => $lineNumber, 'value' => $matches['value'], ]; } $lineNumber++; } return $annotations; } public function symbolAnnotations(): array { return $this->symbolAnnotations; } public function isHookToBeExecutedBeforeClass(): bool { return $this->isMethod && false !== strpos($this->docComment, '@beforeClass'); } public function isHookToBeExecutedAfterClass(): bool { return $this->isMethod && false !== strpos($this->docComment, '@afterClass'); } public function isToBeExecutedBeforeTest(): bool { return 1 === preg_match('/@before\b/', $this->docComment); } public function isToBeExecutedAfterTest(): bool { return 1 === preg_match('/@after\b/', $this->docComment); } private function parseAnnotationContent(string $message): string { if (defined($message) && (strpos($message, '::') !== false && substr_count($message, '::') + 1 === 2)) { return constant($message); } return $message; } private function getDataFromDataProviderAnnotation(string $docComment): ?array { $methodName = null; $className = $this->className; if ($this->isMethod) { $methodName = $this->name; } if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { return null; } $result = []; foreach ($matches[1] as $match) { $dataProviderMethodNameNamespace = explode('\\', $match); $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); $dataProviderMethodName = array_pop($leaf); if (empty($dataProviderMethodNameNamespace)) { $dataProviderMethodNameNamespace = ''; } else { $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; } if (empty($leaf)) { $dataProviderClassName = $className; } else { $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); } try { $dataProviderClass = new ReflectionClass($dataProviderClassName); $dataProviderMethod = $dataProviderClass->getMethod( $dataProviderMethodName ); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($dataProviderMethod->isStatic()) { $object = null; } else { $object = $dataProviderClass->newInstance(); } if ($dataProviderMethod->getNumberOfParameters() === 0) { $data = $dataProviderMethod->invoke($object); } else { $data = $dataProviderMethod->invoke($object, $methodName); } if ($data instanceof Traversable) { $origData = $data; $data = []; foreach ($origData as $key => $value) { if (is_int($key)) { $data[] = $value; } elseif (array_key_exists($key, $data)) { throw new InvalidDataProviderException( sprintf( 'The key "%s" has already been defined in the data provider "%s".', $key, $match ) ); } else { $data[$key] = $value; } } } if (is_array($data)) { $result = array_merge($result, $data); } } return $result; } private function getDataFromTestWithAnnotation(string $docComment): ?array { $docComment = $this->cleanUpMultiLineAnnotation($docComment); if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { return null; } $offset = strlen($matches[0][0]) + $matches[0][1]; $annotationContent = substr($docComment, $offset); $data = []; foreach (explode("\n", $annotationContent) as $candidateRow) { $candidateRow = trim($candidateRow); if ($candidateRow[0] !== '[') { break; } $dataSet = json_decode($candidateRow, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception( 'The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg() ); } $data[] = $dataSet; } if (!$data) { throw new Exception('The data set for the @testWith annotation cannot be parsed.'); } return $data; } private function cleanUpMultiLineAnnotation(string $docComment): string { $docComment = str_replace("\r\n", "\n", $docComment); $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment); $docComment = (string) substr($docComment, 0, -1); return rtrim($docComment, "\n"); } private static function parseDocBlock(string $docBlock): array { $docBlock = (string) substr($docBlock, 3, -2); $annotations = []; if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docBlock, $matches)) { $numMatches = count($matches[0]); for ($i = 0; $i < $numMatches; $i++) { $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; } } return $annotations; } private static function extractAnnotationsFromReflector(Reflector $reflector): array { $annotations = []; if ($reflector instanceof ReflectionClass) { $annotations = array_merge( $annotations, ...array_map( static function (ReflectionClass $trait): array { return self::parseDocBlock((string) $trait->getDocComment()); }, array_values($reflector->getTraits()) ) ); } return array_merge( $annotations, self::parseDocBlock((string) $reflector->getDocComment()) ); } } classDocBlocks)) { return $this->classDocBlocks[$class]; } try { $reflection = new ReflectionClass($class); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } return $this->classDocBlocks[$class] = DocBlock::ofClass($reflection); } public function forMethod(string $classInHierarchy, string $method): DocBlock { if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { return $this->methodDocBlocks[$classInHierarchy][$method]; } try { $reflection = new ReflectionMethod($classInHierarchy, $method); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } return $this->methodDocBlocks[$classInHierarchy][$method] = DocBlock::ofMethod($reflection, $classInHierarchy); } } getName()]; } if ($test instanceof SelfDescribing) { return ['', $test->toString()]; } return ['', get_class($test)]; } public static function describeAsString(\PHPUnit\Framework\Test $test): string { if ($test instanceof SelfDescribing) { return $test->toString(); } return get_class($test); } public static function getLinesToBeCovered(string $className, string $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (!self::shouldCoversAnnotationBeUsed($annotations)) { return false; } return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); } public static function getLinesToBeUsed(string $className, string $methodName): array { return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); } public static function requiresCodeCoverageDataCollection(TestCase $test): bool { $annotations = $test->getAnnotations(); if (isset($annotations['method']['coversNothing'])) { return false; } if (isset($annotations['method']['covers'])) { return true; } if (isset($annotations['class']['coversNothing'])) { return false; } return true; } public static function getRequirements(string $className, string $methodName): array { return self::mergeArraysRecursively( Registry::getInstance()->forClassName($className)->requirements(), Registry::getInstance()->forMethod($className, $methodName)->requirements() ); } public static function getMissingRequirements(string $className, string $methodName): array { $required = self::getRequirements($className, $methodName); $missing = []; $hint = null; if (!empty($required['PHP'])) { $operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) { $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); $hint = 'PHP'; } } elseif (!empty($required['PHP_constraint'])) { $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); if (!$required['PHP_constraint']['constraint']->complies($version)) { $missing[] = sprintf( 'PHP version does not match the required constraint %s.', $required['PHP_constraint']['constraint']->asString() ); $hint = 'PHP_constraint'; } } if (!empty($required['PHPUnit'])) { $phpunitVersion = Version::id(); $operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); $hint = $hint ?? 'PHPUnit'; } } elseif (!empty($required['PHPUnit_constraint'])) { $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { $missing[] = sprintf( 'PHPUnit version does not match the required constraint %s.', $required['PHPUnit_constraint']['constraint']->asString() ); $hint = $hint ?? 'PHPUnit_constraint'; } } if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) { $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); $hint = $hint ?? 'OSFAMILY'; } if (!empty($required['OS'])) { $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); if (!preg_match($requiredOsPattern, PHP_OS)) { $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); $hint = $hint ?? 'OS'; } } if (!empty($required['functions'])) { foreach ($required['functions'] as $function) { $pieces = explode('::', $function); if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { continue; } if (function_exists($function)) { continue; } $missing[] = sprintf('Function %s is required.', $function); $hint = $hint ?? 'function_' . $function; } } if (!empty($required['setting'])) { foreach ($required['setting'] as $setting => $value) { if (ini_get($setting) !== $value) { $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); $hint = $hint ?? '__SETTING_' . $setting; } } } if (!empty($required['extensions'])) { foreach ($required['extensions'] as $extension) { if (isset($required['extension_versions'][$extension])) { continue; } if (!extension_loaded($extension)) { $missing[] = sprintf('Extension %s is required.', $extension); $hint = $hint ?? 'extension_' . $extension; } } } if (!empty($required['extension_versions'])) { foreach ($required['extension_versions'] as $extension => $req) { $actualVersion = phpversion($extension); $operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); if ($actualVersion === false || !version_compare($actualVersion, $req['version'], $operator->asString())) { $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); $hint = $hint ?? 'extension_' . $extension; } } } if ($hint && isset($required['__OFFSET'])) { array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); } return $missing; } public static function getExpectedException(string $className, string $methodName) { return Registry::getInstance()->forMethod($className, $methodName)->expectedException(); } public static function getProvidedData(string $className, string $methodName): ?array { return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); } public static function parseTestMethodAnnotations(string $className, ?string $methodName = ''): array { $registry = Registry::getInstance(); if ($methodName !== null) { try { return [ 'method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), 'class' => $registry->forClassName($className)->symbolAnnotations(), ]; } catch (Exception $methodNotFound) { } } return [ 'method' => null, 'class' => $registry->forClassName($className)->symbolAnnotations(), ]; } public static function getInlineAnnotations(string $className, string $methodName): array { return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); } public static function getBackupSettings(string $className, string $methodName): array { return [ 'backupGlobals' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupGlobals' ), 'backupStaticAttributes' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupStaticAttributes' ), ]; } public static function getDependencies(string $className, string $methodName): array { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $dependencies = $annotations['class']['depends'] ?? []; if (isset($annotations['method']['depends'])) { $dependencies = array_merge( $dependencies, $annotations['method']['depends'] ); } return array_unique($dependencies); } public static function getGroups(string $className, ?string $methodName = ''): array { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $groups = []; if (isset($annotations['method']['author'])) { $groups[] = $annotations['method']['author']; } elseif (isset($annotations['class']['author'])) { $groups[] = $annotations['class']['author']; } if (isset($annotations['class']['group'])) { $groups[] = $annotations['class']['group']; } if (isset($annotations['method']['group'])) { $groups[] = $annotations['method']['group']; } if (isset($annotations['class']['ticket'])) { $groups[] = $annotations['class']['ticket']; } if (isset($annotations['method']['ticket'])) { $groups[] = $annotations['method']['ticket']; } foreach (['method', 'class'] as $element) { foreach (['small', 'medium', 'large'] as $size) { if (isset($annotations[$element][$size])) { $groups[] = [$size]; break 2; } } } return array_unique(array_merge([], ...$groups)); } public static function getSize(string $className, ?string $methodName): int { $groups = array_flip(self::getGroups($className, $methodName)); if (isset($groups['large'])) { return self::LARGE; } if (isset($groups['medium'])) { return self::MEDIUM; } if (isset($groups['small'])) { return self::SMALL; } return self::UNKNOWN; } public static function getProcessIsolationSettings(string $className, string $methodName): bool { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); } public static function getClassProcessIsolationSettings(string $className, string $methodName): bool { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); return isset($annotations['class']['runClassInSeparateProcess']); } public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool { return self::getBooleanAnnotationSetting( $className, $methodName, 'preserveGlobalState' ); } public static function getHookMethods(string $className): array { if (!class_exists($className, false)) { return self::emptyHookMethodsArray(); } if (!isset(self::$hookMethods[$className])) { self::$hookMethods[$className] = self::emptyHookMethodsArray(); try { foreach ((new Reflection)->methodsInTestClass(new ReflectionClass($className)) as $method) { $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); if ($method->isStatic()) { if ($docBlock->isHookToBeExecutedBeforeClass()) { array_unshift( self::$hookMethods[$className]['beforeClass'], $method->getName() ); } if ($docBlock->isHookToBeExecutedAfterClass()) { self::$hookMethods[$className]['afterClass'][] = $method->getName(); } } if ($docBlock->isToBeExecutedBeforeTest()) { array_unshift( self::$hookMethods[$className]['before'], $method->getName() ); } if ($docBlock->isToBeExecutedAfterTest()) { self::$hookMethods[$className]['after'][] = $method->getName(); } } } catch (ReflectionException $e) { } } return self::$hookMethods[$className]; } public static function isTestMethod(ReflectionMethod $method): bool { if (!$method->isPublic()) { return false; } if (strpos($method->getName(), 'test') === 0) { return true; } return array_key_exists( 'test', Registry::getInstance()->forMethod( $method->getDeclaringClass()->getName(), $method->getName() ) ->symbolAnnotations() ); } private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $classShortcut = null; if (!empty($annotations['class'][$mode . 'DefaultClass'])) { if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { throw new CodeCoverageException( sprintf( 'More than one @%sClass annotation in class or interface "%s".', $mode, $className ) ); } $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; } $list = $annotations['class'][$mode] ?? []; if (isset($annotations['method'][$mode])) { $list = array_merge($list, $annotations['method'][$mode]); } $codeList = []; foreach (array_unique($list) as $element) { if ($classShortcut && strncmp($element, '::', 2) === 0) { $element = $classShortcut . $element; } $element = preg_replace('/[\s()]+$/', '', $element); $element = explode(' ', $element); $element = $element[0]; if ($mode === 'covers' && interface_exists($element)) { throw new InvalidCoversTargetException( sprintf( 'Trying to @cover interface "%s".', $element ) ); } $codeList[] = self::resolveElementToReflectionObjects($element); } return self::resolveReflectionObjectsToLines(array_merge([], ...$codeList)); } private static function emptyHookMethodsArray(): array { return [ 'beforeClass' => ['setUpBeforeClass'], 'before' => ['setUp'], 'after' => ['tearDown'], 'afterClass' => ['tearDownAfterClass'], ]; } private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['method'][$settingName])) { if ($annotations['method'][$settingName][0] === 'enabled') { return true; } if ($annotations['method'][$settingName][0] === 'disabled') { return false; } } if (isset($annotations['class'][$settingName])) { if ($annotations['class'][$settingName][0] === 'enabled') { return true; } if ($annotations['class'][$settingName][0] === 'disabled') { return false; } } return null; } private static function resolveElementToReflectionObjects(string $element): array { $codeToCoverList = []; if (function_exists($element) && strpos($element, '\\') !== false) { try { $codeToCoverList[] = new ReflectionFunction($element); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } elseif (strpos($element, '::') !== false) { [$className, $methodName] = explode('::', $element); if (isset($methodName[0]) && $methodName[0] === '<') { $classes = [$className]; foreach ($classes as $className) { if (!class_exists($className) && !interface_exists($className) && !trait_exists($className)) { throw new InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } try { $methods = (new ReflectionClass($className))->getMethods(); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } $inverse = isset($methodName[1]) && $methodName[1] === '!'; $visibility = 'isPublic'; if (strpos($methodName, 'protected')) { $visibility = 'isProtected'; } elseif (strpos($methodName, 'private')) { $visibility = 'isPrivate'; } foreach ($methods as $method) { if ($inverse && !$method->{$visibility}()) { $codeToCoverList[] = $method; } elseif (!$inverse && $method->{$visibility}()) { $codeToCoverList[] = $method; } } } } else { $classes = [$className]; foreach ($classes as $className) { if ($className === '' && function_exists($methodName)) { try { $codeToCoverList[] = new ReflectionFunction( $methodName ); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } else { if (!((class_exists($className) || interface_exists($className) || trait_exists($className)) && method_exists($className, $methodName))) { throw new InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing method "%s::%s".', $className, $methodName ) ); } try { $codeToCoverList[] = new ReflectionMethod( $className, $methodName ); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } } } } else { $extended = false; if (strpos($element, '') !== false) { $element = str_replace('', '', $element); $extended = true; } $classes = [$element]; if ($extended) { $classes = array_merge( $classes, class_implements($element), class_parents($element) ); } foreach ($classes as $className) { if (!class_exists($className) && !interface_exists($className) && !trait_exists($className)) { throw new InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } try { $codeToCoverList[] = new ReflectionClass($className); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } } } return $codeToCoverList; } private static function resolveReflectionObjectsToLines(array $reflectors): array { $result = []; foreach ($reflectors as $reflector) { if ($reflector instanceof ReflectionClass) { foreach ($reflector->getTraits() as $trait) { $reflectors[] = $trait; } } } foreach ($reflectors as $reflector) { $filename = $reflector->getFileName(); if (!isset($result[$filename])) { $result[$filename] = []; } $result[$filename] = array_merge( $result[$filename], range($reflector->getStartLine(), $reflector->getEndLine()) ); } foreach ($result as $filename => $lineNumbers) { $result[$filename] = array_keys(array_flip($lineNumbers)); } return $result; } private static function sanitizeVersionNumber(string $version) { return preg_replace( '/^(\d+\.\d+(?:.\d+)?).*$/', '$1', $version ); } private static function shouldCoversAnnotationBeUsed(array $annotations): bool { if (isset($annotations['method']['coversNothing'])) { return false; } if (isset($annotations['method']['covers'])) { return true; } if (isset($annotations['class']['coversNothing'])) { return false; } return true; } private static function mergeArraysRecursively(array $a, array $b): array { foreach ($b as $key => $value) { if (array_key_exists($key, $a)) { if (is_int($key)) { $a[] = $value; } elseif (is_array($value) && is_array($a[$key])) { $a[$key] = self::mergeArraysRecursively($a[$key], $value); } else { $a[$key] = $value; } } else { $a[$key] = $value; } } return $a; } } filterMethods($class, ReflectionMethod::IS_PUBLIC); } public function methodsInTestClass(ReflectionClass $class): array { return $this->filterMethods($class, null); } private function filterMethods(ReflectionClass $class, ?int $filter): array { $methods = []; $classMethods = $filter === null ? $class->getMethods() : $class->getMethods($filter); foreach ($classMethods as $method) { if ($method->getDeclaringClass()->getName() === TestCase::class) { continue; } if ($method->getDeclaringClass()->getName() === Assert::class) { continue; } $methods[] = $method; } return $methods; } } = 0; $i--) { $file = $files[$i]; if (!empty($GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST']) && in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'], true)) { continue; } if ($prefix !== false && strpos($file, $prefix) === 0) { continue; } if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { continue; } if (!$blacklist->isBlacklisted($file) && is_file($file)) { $result = 'require_once \'' . $file . "';\n" . $result; } } return $result; } public static function getIniSettingsAsString(): string { $result = ''; foreach (ini_get_all(null, false) as $key => $value) { $result .= sprintf( '@ini_set(%s, %s);' . "\n", self::exportVariable($key), self::exportVariable((string) $value) ); } return $result; } public static function getConstantsAsString(): string { $constants = get_defined_constants(true); $result = ''; if (isset($constants['user'])) { foreach ($constants['user'] as $name => $value) { $result .= sprintf( 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, self::exportVariable($value) ); } } return $result; } public static function getGlobalsAsString(): string { $result = ''; foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { continue; } $result .= sprintf( '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, self::exportVariable($GLOBALS[$superGlobalArray][$key]) ); } } } $blacklist = self::SUPER_GLOBAL_ARRAYS; $blacklist[] = 'GLOBALS'; foreach (array_keys($GLOBALS) as $key) { if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $blacklist, true)) { $result .= sprintf( '$GLOBALS[\'%s\'] = %s;' . "\n", $key, self::exportVariable($GLOBALS[$key]) ); } } return $result; } private static function exportVariable($variable): string { if (is_scalar($variable) || $variable === null || (is_array($variable) && self::arrayOnlyContainsScalars($variable))) { return var_export($variable, true); } return 'unserialize(' . var_export(serialize($variable), true) . ')'; } private static function arrayOnlyContainsScalars(array $array): bool { $result = true; foreach ($array as $element) { if (is_array($element)) { $result = self::arrayOnlyContainsScalars($element); } elseif (!is_scalar($element) && $element !== null) { $result = false; } if (!$result) { break; } } return $result; } } openMemory(); $writer->setIndent(true); $writer->startDocument(); $writer->startElement('tests'); $currentTestCase = null; foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { if ($test instanceof TestCase) { if (get_class($test) !== $currentTestCase) { if ($currentTestCase !== null) { $writer->endElement(); } $writer->startElement('testCaseClass'); $writer->writeAttribute('name', get_class($test)); $currentTestCase = get_class($test); } $writer->startElement('testCaseMethod'); $writer->writeAttribute('name', $test->getName(false)); $writer->writeAttribute('groups', implode(',', $test->getGroups())); if (!empty($test->getDataSetAsString(false))) { $writer->writeAttribute( 'dataSet', str_replace( ' with data set ', '', $test->getDataSetAsString(false) ) ); } $writer->endElement(); } elseif ($test instanceof PhptTestCase) { if ($currentTestCase !== null) { $writer->endElement(); $currentTestCase = null; } $writer->startElement('phptFile'); $writer->writeAttribute('path', $test->getName()); $writer->endElement(); } } if ($currentTestCase !== null) { $writer->endElement(); } $writer->endElement(); return $writer->outputMemory(); } } ensureOperatorIsValid($operator); $this->operator = $operator; } public function asString(): string { return $this->operator; } private function ensureOperatorIsValid(string $operator): void { if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], true)) { throw new Exception( sprintf( '"%s" is not a valid version_compare() operator', $operator ) ); } } } prettifier = new NamePrettifier($this->colors); } public function setOriginalExecutionOrder(array $order): void { $this->originalExecutionOrder = $order; $this->enableOutputBuffer = !empty($order); } public function setShowProgressAnimation(bool $showProgress): void { $this->showProgress = $showProgress; } public function printResult(TestResult $result): void { } public function endTest(Test $test, float $time): void { if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { return; } if ($this->testHasPassed()) { $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, false); } if ($test instanceof TestCase || $test instanceof PhptTestCase) { $this->testIndex++; } parent::endTest($test, $time); } public function addError(Test $test, Throwable $t, float $time): void { $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, true); } public function addWarning(Test $test, Warning $e, float $time): void { $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, true); } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, true); } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, false); } public function addRiskyTest(Test $test, Throwable $t, float $time): void { $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, false); } public function addSkippedTest(Test $test, Throwable $t, float $time): void { $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, false); } public function writeProgress(string $progress): void { $this->flushOutputBuffer(); } public function flush(): void { $this->flushOutputBuffer(true); } protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void { $testName = TestSuiteSorter::getTestSorterUID($test); $result = [ 'className' => $this->formatClassName($test), 'testName' => $testName, 'testMethod' => $this->formatTestName($test), 'message' => '', 'status' => $status, 'time' => $time, 'verbose' => $verbose, ]; if ($t !== null) { $result['message'] = $this->formatTestResultMessage($t, $result); } $this->testResults[$this->testIndex] = $result; $this->testNameResultIndex[$testName] = $this->testIndex; } protected function formatTestName(Test $test): string { return $test->getName(); } protected function formatClassName(Test $test): string { return get_class($test); } protected function testHasPassed(): bool { if (!isset($this->testResults[$this->testIndex]['status'])) { return true; } if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { return true; } return false; } protected function flushOutputBuffer(bool $forceFlush = false): void { if ($this->testFlushIndex === $this->testIndex) { return; } if ($this->testFlushIndex > 0) { if ($this->enableOutputBuffer) { $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); } else { $prevResult = $this->testResults[$this->testFlushIndex - 1]; } } else { $prevResult = $this->getEmptyTestResult(); } if (!$this->enableOutputBuffer) { $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); } else { do { $flushed = false; if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); } else { $result = $this->testResults[$this->testFlushIndex]; } if (!empty($result)) { $this->hideSpinner(); $this->writeTestResult($prevResult, $result); $this->testFlushIndex++; $prevResult = $result; $flushed = true; } else { $this->showSpinner(); } } while ($flushed && $this->testFlushIndex < $this->testIndex); } } protected function showSpinner(): void { if (!$this->showProgress) { return; } if ($this->spinState) { $this->undrawSpinner(); } $this->spinState++; $this->drawSpinner(); } protected function hideSpinner(): void { if (!$this->showProgress) { return; } if ($this->spinState) { $this->undrawSpinner(); } $this->spinState = 0; } protected function drawSpinner(): void { } protected function undrawSpinner(): void { } protected function writeTestResult(array $prevResult, array $result): void { } protected function getEmptyTestResult(): array { return [ 'className' => '', 'testName' => '', 'message' => '', 'failed' => '', 'verbose' => '', ]; } protected function getTestResultByName(?string $testName): array { if (isset($this->testNameResultIndex[$testName])) { return $this->testResults[$this->testNameResultIndex[$testName]]; } return []; } protected function formatThrowable(Throwable $t, ?int $status = null): string { $message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); if ($message) { $message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t); } else { $message = $this->formatStacktrace($t); } return $message; } protected function formatStacktrace(Throwable $t): string { return \PHPUnit\Util\Filter::getFilteredStacktrace($t); } protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│'): string { $message = $this->formatThrowable($t, $result['status']); if ($message === '') { return ''; } if (!($this->verbose || $result['verbose'])) { return ''; } return $this->prefixLines($prefix, $message); } protected function prefixLines(string $prefix, string $message): string { $message = trim($message); return implode( PHP_EOL, array_map( static function (string $text) use ($prefix) { return ' ' . $prefix . ($text ? ' ' . $text : ''); }, preg_split('/\r\n|\r|\n/', $message) ) ); } } document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('tests'); $this->document->appendChild($this->root); $this->prettifier = new NamePrettifier; parent::__construct($out); } public function flush(): void { $this->write($this->document->saveXML()); parent::flush(); } public function addError(Test $test, Throwable $t, float $time): void { $this->exception = $t; } public function addWarning(Test $test, Warning $e, float $time): void { } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->exception = $e; } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { } public function addRiskyTest(Test $test, Throwable $t, float $time): void { } public function addSkippedTest(Test $test, Throwable $t, float $time): void { } public function startTestSuite(TestSuite $suite): void { } public function endTestSuite(TestSuite $suite): void { } public function startTest(Test $test): void { $this->exception = null; } public function endTest(Test $test, float $time): void { if (!$test instanceof TestCase) { return; } $groups = array_filter( $test->getGroups(), static function ($group) { return !($group === 'small' || $group === 'medium' || $group === 'large'); } ); $testNode = $this->document->createElement('test'); $testNode->setAttribute('className', get_class($test)); $testNode->setAttribute('methodName', $test->getName()); $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); $testNode->setAttribute('status', (string) $test->getStatus()); $testNode->setAttribute('time', (string) $time); $testNode->setAttribute('size', (string) $test->getSize()); $testNode->setAttribute('groups', implode(',', $groups)); foreach ($groups as $group) { $groupNode = $this->document->createElement('group'); $groupNode->setAttribute('name', $group); $testNode->appendChild($groupNode); } $annotations = $test->getAnnotations(); foreach (['class', 'method'] as $type) { foreach ($annotations[$type] as $annotation => $values) { if ($annotation !== 'covers' && $annotation !== 'uses') { continue; } foreach ($values as $value) { $coversNode = $this->document->createElement($annotation); $coversNode->setAttribute('target', $value); $testNode->appendChild($coversNode); } } } foreach ($test->doubledTypes() as $doubledType) { $testDoubleNode = $this->document->createElement('testDouble'); $testDoubleNode->setAttribute('type', $doubledType); $testNode->appendChild($testDoubleNode); } $inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(get_class($test), $test->getName(false)); if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { $testNode->setAttribute('given', $inlineAnnotations['given']['value']); $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); $testNode->setAttribute('when', $inlineAnnotations['when']['value']); $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); $testNode->setAttribute('then', $inlineAnnotations['then']['value']); $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); } if ($this->exception !== null) { if ($this->exception instanceof Exception) { $steps = $this->exception->getSerializableTrace(); } else { $steps = $this->exception->getTrace(); } try { $file = (new ReflectionClass($test))->getFileName(); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } foreach ($steps as $step) { if (isset($step['file']) && $step['file'] === $file) { $testNode->setAttribute('exceptionLine', (string) $step['line']); break; } } $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); } $this->root->appendChild($testNode); } } Test Documentation EOT; private const CLASS_HEADER = <<<'EOT'

%s

    EOT; private const CLASS_FOOTER = <<<'EOT'
EOT; private const PAGE_FOOTER = <<<'EOT' EOT; protected function startRun(): void { $this->write(self::PAGE_HEADER); } protected function startClass(string $name): void { $this->write( sprintf( self::CLASS_HEADER, $name, $this->currentTestClassPrettified ) ); } protected function onTest($name, bool $success = true): void { $this->write( sprintf( "
  • %s %s
  • \n", $success ? '#555753' : '#ef2929', $success ? '✓' : '❌', $name ) ); } protected function endClass(string $name): void { $this->write(self::CLASS_FOOTER); } protected function endRun(): void { $this->write(self::PAGE_FOOTER); } } '│', 'start' => '│', 'message' => '│', 'diff' => '│', 'trace' => '│', 'last' => '│', ]; private const PREFIX_DECORATED = [ 'default' => '│', 'start' => '┐', 'message' => '├', 'diff' => '┊', 'trace' => '╵', 'last' => '┴', ]; private const SPINNER_ICONS = [ " \e[36m◐\e[0m running tests", " \e[36m◓\e[0m running tests", " \e[36m◑\e[0m running tests", " \e[36m◒\e[0m running tests", ]; private const STATUS_STYLES = [ BaseTestRunner::STATUS_PASSED => [ 'symbol' => '✔', 'color' => 'fg-green', ], BaseTestRunner::STATUS_ERROR => [ 'symbol' => '✘', 'color' => 'fg-yellow', 'message' => 'bg-yellow,fg-black', ], BaseTestRunner::STATUS_FAILURE => [ 'symbol' => '✘', 'color' => 'fg-red', 'message' => 'bg-red,fg-white', ], BaseTestRunner::STATUS_SKIPPED => [ 'symbol' => '↩', 'color' => 'fg-cyan', 'message' => 'fg-cyan', ], BaseTestRunner::STATUS_RISKY => [ 'symbol' => '☢', 'color' => 'fg-yellow', 'message' => 'fg-yellow', ], BaseTestRunner::STATUS_INCOMPLETE => [ 'symbol' => '∅', 'color' => 'fg-yellow', 'message' => 'fg-yellow', ], BaseTestRunner::STATUS_WARNING => [ 'symbol' => '⚠', 'color' => 'fg-yellow', 'message' => 'fg-yellow', ], BaseTestRunner::STATUS_UNKNOWN => [ 'symbol' => '?', 'color' => 'fg-blue', 'message' => 'fg-white,bg-blue', ], ]; private $nonSuccessfulTestResults = []; public function printResult(TestResult $result): void { $this->printHeader($result); $this->printNonSuccessfulTestsSummary($result->count()); $this->printFooter($result); } protected function printHeader(TestResult $result): void { $this->write("\n" . Timer::resourceUsage() . "\n\n"); } protected function formatClassName(Test $test): string { if ($test instanceof TestCase) { return $this->prettifier->prettifyTestClass(get_class($test)); } return get_class($test); } protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void { if ($status !== BaseTestRunner::STATUS_PASSED) { $this->nonSuccessfulTestResults[] = $this->testIndex; } parent::registerTestResult($test, $t, $status, $time, $verbose); } protected function formatTestName(Test $test): string { if ($test instanceof TestCase) { return $this->prettifier->prettifyTestCase($test); } return parent::formatTestName($test); } protected function writeTestResult(array $prevResult, array $result): void { if ($prevResult['testName'] !== '' && (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { $this->write(PHP_EOL); } if ($prevResult['className'] !== $result['className']) { $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL); } if ($this->colors && $result['className'] === PhptTestCase::class) { $testName = Color::colorizePath($result['testName'], $prevResult['testName'], true); } else { $testName = $result['testMethod']; } $style = self::STATUS_STYLES[$result['status']]; $line = sprintf( ' %s %s%s' . PHP_EOL, $this->colorizeTextBox($style['color'], $style['symbol']), $testName, $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : '' ); $this->write($line); $this->write($result['message']); } protected function formatThrowable(Throwable $t, ?int $status = null): string { return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); } protected function colorizeMessageAndDiff(string $style, string $buffer): array { $lines = $buffer ? array_map('\rtrim', explode(PHP_EOL, $buffer)) : []; $message = []; $diff = []; $insideDiff = false; foreach ($lines as $line) { if ($line === '--- Expected') { $insideDiff = true; } if (!$insideDiff) { $message[] = $line; } else { if (strpos($line, '-') === 0) { $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); } elseif (strpos($line, '+') === 0) { $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); } elseif ($line === '@@ @@') { $line = Color::colorize('fg-cyan', $line); } $diff[] = $line; } } $diff = implode(PHP_EOL, $diff); if (!empty($message)) { $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message)); } return [$message, $diff]; } protected function formatStacktrace(Throwable $t): string { $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t); if (!$this->colors) { return $trace; } $lines = []; $prevPath = ''; foreach (explode(PHP_EOL, $trace) as $line) { if (preg_match('/^(.*):(\d+)$/', $line, $matches)) { $lines[] = Color::colorizePath($matches[1], $prevPath) . Color::dim(':') . Color::colorize('fg-blue', $matches[2]) . "\n"; $prevPath = $matches[1]; } else { $lines[] = $line; $prevPath = ''; } } return implode('', $lines); } protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null): string { $message = $this->formatThrowable($t, $result['status']); $diff = ''; if (!($this->verbose || $result['verbose'])) { return ''; } if ($message && $this->colors) { $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); } if ($prefix === null || !$this->colors) { $prefix = self::PREFIX_SIMPLE; } if ($this->colors) { $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; $prefix = array_map(static function ($p) use ($color) { return Color::colorize($color, $p); }, self::PREFIX_DECORATED); } $trace = $this->formatStacktrace($t); $out = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL; if ($message) { $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL; } if ($diff) { $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL; } if ($trace) { if ($message || $diff) { $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL; } $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL; } $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL; return $out; } protected function drawSpinner(): void { if ($this->colors) { $id = $this->spinState % count(self::SPINNER_ICONS); $this->write(self::SPINNER_ICONS[$id]); } } protected function undrawSpinner(): void { if ($this->colors) { $id = $this->spinState % count(self::SPINNER_ICONS); $this->write("\e[1K\e[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); } } private function formatRuntime(float $time, string $color = ''): string { if (!$this->colors) { return sprintf('[%.2f ms]', $time * 1000); } if ($time > 1) { $color = 'fg-magenta'; } return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); } private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void { if (empty($this->nonSuccessfulTestResults)) { return; } if ((count($this->nonSuccessfulTestResults) / $numberOfExecutedTests) >= 0.7) { return; } $this->write("Summary of non-successful tests:\n\n"); $prevResult = $this->getEmptyTestResult(); foreach ($this->nonSuccessfulTestResults as $testIndex) { $result = $this->testResults[$testIndex]; $this->writeTestResult($prevResult, $result); $prevResult = $result; } } } write($this->currentTestClassPrettified . "\n"); } protected function onTest($name, bool $success = true): void { if ($success) { $this->write(' [x] '); } else { $this->write(' [ ] '); } $this->write($name . "\n"); } protected function endClass(string $name): void { $this->write("\n"); } } groups = $groups; $this->excludeGroups = $excludeGroups; $this->prettifier = new NamePrettifier; $this->startRun(); } public function flush(): void { $this->doEndClass(); $this->endRun(); parent::flush(); } public function addError(Test $test, Throwable $t, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_ERROR; $this->failed++; } public function addWarning(Test $test, Warning $e, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_WARNING; $this->warned++; } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_FAILURE; $this->failed++; } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } public function addRiskyTest(Test $test, Throwable $t, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_RISKY; $this->risky++; } public function addSkippedTest(Test $test, Throwable $t, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } public function startTestSuite(TestSuite $suite): void { } public function endTestSuite(TestSuite $suite): void { } public function startTest(Test $test): void { if (!$this->isOfInterest($test)) { return; } $class = get_class($test); if ($this->testClass !== $class) { if ($this->testClass !== '') { $this->doEndClass(); } $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); $this->testClass = $class; $this->tests = []; $this->startClass($class); } if ($test instanceof TestCase) { $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); } $this->testStatus = BaseTestRunner::STATUS_PASSED; } public function endTest(Test $test, float $time): void { if (!$this->isOfInterest($test)) { return; } $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; $this->currentTestClassPrettified = null; $this->currentTestMethodPrettified = null; } protected function doEndClass(): void { foreach ($this->tests as $test) { $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); } $this->endClass($this->testClass); } protected function startRun(): void { } protected function startClass(string $name): void { } protected function onTest($name, bool $success = true): void { } protected function endClass(string $name): void { } protected function endRun(): void { } private function isOfInterest(Test $test): bool { if (!$test instanceof TestCase) { return false; } if ($test instanceof WarningTestCase) { return false; } if (!empty($this->groups)) { foreach ($test->getGroups() as $group) { if (in_array($group, $this->groups, true)) { return true; } } return false; } if (!empty($this->excludeGroups)) { foreach ($test->getGroups() as $group) { if (in_array($group, $this->excludeGroups, true)) { return false; } } return true; } return true; } } useColor = $useColor; } public function prettifyTestClass(string $className): string { try { $annotations = Test::parseTestMethodAnnotations($className); if (isset($annotations['class']['testdox'][0])) { return $annotations['class']['testdox'][0]; } } catch (UtilException $e) { } $parts = explode('\\', $className); $className = array_pop($parts); if (substr($className, -1 * strlen('Test')) === 'Test') { $className = substr($className, 0, strlen($className) - strlen('Test')); } if (strpos($className, 'Tests') === 0) { $className = substr($className, strlen('Tests')); } elseif (strpos($className, 'Test') === 0) { $className = substr($className, strlen('Test')); } if (empty($className)) { $className = 'UnnamedTests'; } if (!empty($parts)) { $parts[] = $className; $fullyQualifiedName = implode('\\', $parts); } else { $fullyQualifiedName = $className; } $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); if ($fullyQualifiedName !== $className) { return $result . ' (' . $fullyQualifiedName . ')'; } return $result; } public function prettifyTestCase(TestCase $test): string { $annotations = $test->getAnnotations(); $annotationWithPlaceholders = false; $callback = static function (string $variable): string { return sprintf('/%s(?=\b)/', preg_quote($variable, '/')); }; if (isset($annotations['method']['testdox'][0])) { $result = $annotations['method']['testdox'][0]; if (strpos($result, '$') !== false) { $annotation = $annotations['method']['testdox'][0]; $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); $variables = array_map($callback, array_keys($providedData)); $result = trim(preg_replace($variables, $providedData, $annotation)); $annotationWithPlaceholders = true; } } else { $result = $this->prettifyTestMethod($test->getName(false)); } if (!$annotationWithPlaceholders && $test->usesDataProvider()) { $result .= $this->prettifyDataSet($test); } return $result; } public function prettifyDataSet(TestCase $test): string { if (!$this->useColor) { return $test->getDataSetAsString(false); } if (is_int($test->dataName())) { $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); } else { $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); } return $data; } public function prettifyTestMethod(string $name): string { $buffer = ''; if ($name === '') { return $buffer; } $string = (string) preg_replace('#\d+$#', '', $name, -1, $count); if (in_array($string, $this->strings, true)) { $name = $string; } elseif ($count === 0) { $this->strings[] = $string; } if (strpos($name, 'test_') === 0) { $name = substr($name, 5); } elseif (strpos($name, 'test') === 0) { $name = substr($name, 4); } if ($name === '') { return $buffer; } $name[0] = strtoupper($name[0]); if (strpos($name, '_') !== false) { return trim(str_replace('_', ' ', $name)); } $wasNumeric = false; foreach (range(0, strlen($name) - 1) as $i) { if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { $buffer .= ' ' . strtolower($name[$i]); } else { $isNumeric = is_numeric($name[$i]); if (!$wasNumeric && $isNumeric) { $buffer .= ' '; $wasNumeric = true; } if ($wasNumeric && !$isNumeric) { $wasNumeric = false; } $buffer .= $name[$i]; } } return $buffer; } private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test): array { try { $reflector = new ReflectionMethod(get_class($test), $test->getName(false)); } catch (ReflectionException $e) { throw new UtilException( $e->getMessage(), (int) $e->getCode(), $e ); } $providedData = []; $providedDataValues = array_values($test->getProvidedData()); $i = 0; $providedData['$_dataName'] = $test->dataName(); foreach ($reflector->getParameters() as $parameter) { if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { try { $providedDataValues[$i] = $parameter->getDefaultValue(); } catch (ReflectionException $e) { throw new UtilException( $e->getMessage(), (int) $e->getCode(), $e ); } } $value = $providedDataValues[$i++] ?? null; if (is_object($value)) { $reflector = new ReflectionObject($value); if ($reflector->hasMethod('__toString')) { $value = (string) $value; } else { $value = get_class($value); } } if (!is_scalar($value)) { $value = gettype($value); } if (is_bool($value) || is_int($value) || is_float($value)) { $value = (new Exporter)->export($value); } if (is_string($value) && $value === '') { if ($this->useColor) { $value = Color::colorize('dim,underlined', 'empty'); } else { $value = "''"; } } $providedData['$' . $parameter->getName()] = $value; } if ($this->useColor) { $providedData = array_map(static function ($value) { return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)); }, $providedData); } return $providedData; } } codeCoverageFilter = $filter; $this->loader = $loader; $this->runtime = new Runtime; } public function doRun(Test $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult { if (isset($arguments['configuration'])) { $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; } $this->handleConfiguration($arguments); if (is_int($arguments['columns']) && $arguments['columns'] < 16) { $arguments['columns'] = 16; $tooFewColumnsRequested = true; } if (isset($arguments['bootstrap'])) { $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } if ($suite instanceof TestCase || $suite instanceof TestSuite) { if ($arguments['backupGlobals'] === true) { $suite->setBackupGlobals(true); } if ($arguments['backupStaticAttributes'] === true) { $suite->setBackupStaticAttributes(true); } if ($arguments['beStrictAboutChangesToGlobalState'] === true) { $suite->setBeStrictAboutChangesToGlobalState(true); } } if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { mt_srand($arguments['randomOrderSeed']); } if ($arguments['cacheResult']) { if (!isset($arguments['cacheResultFile'])) { if (isset($arguments['configuration']) && $arguments['configuration'] instanceof Configuration) { $cacheLocation = $arguments['configuration']->getFilename(); } else { $cacheLocation = $_SERVER['PHP_SELF']; } $arguments['cacheResultFile'] = null; $cacheResultFile = realpath($cacheLocation); if ($cacheResultFile !== false) { $arguments['cacheResultFile'] = dirname($cacheResultFile); } } $cache = new DefaultTestResultCache($arguments['cacheResultFile']); $this->addExtension(new ResultCacheExtension($cache)); } if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { $cache = $cache ?? new NullTestResultCache; $cache->load(); $sorter = new TestSuiteSorter($cache); $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); unset($sorter); } if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { $_suite = new TestSuite; foreach (range(1, $arguments['repeat']) as $step) { $_suite->addTest($suite); } $suite = $_suite; unset($_suite); } $result = $this->createTestResult(); $listener = new TestListenerAdapter; $listenerNeeded = false; foreach ($this->extensions as $extension) { if ($extension instanceof TestHook) { $listener->add($extension); $listenerNeeded = true; } } if ($listenerNeeded) { $result->addListener($listener); } unset($listener, $listenerNeeded); if ($arguments['convertDeprecationsToExceptions']) { $result->convertDeprecationsToExceptions(true); } if (!$arguments['convertErrorsToExceptions']) { $result->convertErrorsToExceptions(false); } if (!$arguments['convertNoticesToExceptions']) { $result->convertNoticesToExceptions(false); } if (!$arguments['convertWarningsToExceptions']) { $result->convertWarningsToExceptions(false); } if ($arguments['stopOnError']) { $result->stopOnError(true); } if ($arguments['stopOnFailure']) { $result->stopOnFailure(true); } if ($arguments['stopOnWarning']) { $result->stopOnWarning(true); } if ($arguments['stopOnIncomplete']) { $result->stopOnIncomplete(true); } if ($arguments['stopOnRisky']) { $result->stopOnRisky(true); } if ($arguments['stopOnSkipped']) { $result->stopOnSkipped(true); } if ($arguments['stopOnDefect']) { $result->stopOnDefect(true); } if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); } if ($this->printer === null) { if (isset($arguments['printer'])) { if ($arguments['printer'] instanceof Printer && $arguments['printer'] instanceof TestListener) { $this->printer = $arguments['printer']; } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { try { new ReflectionClass($arguments['printer']); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (is_subclass_of($arguments['printer'], ResultPrinter::class)) { $this->printer = $this->createPrinter($arguments['printer'], $arguments); } } } else { $this->printer = $this->createPrinter(ResultPrinter::class, $arguments); } } if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { assert($this->printer instanceof CliTestDoxPrinter); $this->printer->setOriginalExecutionOrder($originalExecutionOrder); $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); } if ($arguments['colors'] !== ResultPrinter::COLOR_NEVER) { $this->write( 'PHPUnit ' . Version::id() . ' ' . Color::colorize('bg-blue,fg-white', '#StandWith') . Color::colorize('bg-yellow,fg-black', 'Ukraine') . "\n" ); } else { $this->write(Version::getVersionString() . "\n"); } if ($arguments['verbose']) { $this->writeMessage('Runtime', $this->runtime->getNameWithVersionAndCodeCoverageDriver()); if (isset($arguments['configuration'])) { $this->writeMessage( 'Configuration', $arguments['configuration']->getFilename() ); } foreach ($arguments['loadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } foreach ($arguments['notLoadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } } foreach ($warnings as $warning) { $this->writeMessage('Warning', $warning); } if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { $this->writeMessage( 'Random seed', (string) $arguments['randomOrderSeed'] ); } if (isset($tooFewColumnsRequested)) { $this->writeMessage('Error', 'Less than 16 columns requested, number of columns set to 16'); } if ($this->runtime->discardsComments()) { $this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work'); } if (isset($arguments['configuration']) && $arguments['configuration']->hasValidationErrors()) { $this->write( "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n" ); foreach ($arguments['configuration']->getValidationErrors() as $line => $errors) { $this->write(sprintf("\n Line %d:\n", $line)); foreach ($errors as $msg) { $this->write(sprintf(" - %s\n", $msg)); } } $this->write("\n Test results may not be as expected.\n\n"); } if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { $this->writeMessage('Warning', 'Directives printerClass and testdox are mutually exclusive'); } foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $result->addListener($this->printer); $codeCoverageReports = 0; if (!isset($arguments['noLogging'])) { if (isset($arguments['testdoxHTMLFile'])) { $result->addListener( new HtmlResultPrinter( $arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxTextFile'])) { $result->addListener( new TextResultPrinter( $arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxXMLFile'])) { $result->addListener( new XmlResultPrinter( $arguments['testdoxXMLFile'] ) ); } if (isset($arguments['teamcityLogfile'])) { $result->addListener( new TeamCity($arguments['teamcityLogfile']) ); } if (isset($arguments['junitLogfile'])) { $result->addListener( new JUnit( $arguments['junitLogfile'], $arguments['reportUselessTests'] ) ); } if (isset($arguments['coverageClover'])) { $codeCoverageReports++; } if (isset($arguments['coverageCrap4J'])) { $codeCoverageReports++; } if (isset($arguments['coverageHtml'])) { $codeCoverageReports++; } if (isset($arguments['coveragePHP'])) { $codeCoverageReports++; } if (isset($arguments['coverageText'])) { $codeCoverageReports++; } if (isset($arguments['coverageXml'])) { $codeCoverageReports++; } } if (isset($arguments['noCoverage'])) { $codeCoverageReports = 0; } if ($codeCoverageReports > 0 && PHP_MAJOR_VERSION < 8 && !$this->runtime->canCollectCodeCoverage()) { $this->writeMessage('Error', 'No code coverage driver is available'); $codeCoverageReports = 0; } if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { $whitelistFromConfigurationFile = false; $whitelistFromOption = false; if (isset($arguments['whitelist'])) { $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']); $whitelistFromOption = true; } if (isset($arguments['configuration'])) { $filterConfiguration = $arguments['configuration']->getFilterConfiguration(); if (!empty($filterConfiguration['whitelist'])) { $whitelistFromConfigurationFile = true; } if (!empty($filterConfiguration['whitelist'])) { foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromWhitelist($file); } } } } if ($codeCoverageReports > 0) { if (PHP_MAJOR_VERSION >= 8) { $this->writeMessage('Error', 'This version of PHPUnit does not support code coverage on PHP 8'); $codeCoverageReports = 0; } else { try { $codeCoverage = new CodeCoverage( null, $this->codeCoverageFilter ); $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist( [Comparator::class] ); $codeCoverage->setCheckForUnintentionallyCoveredCode( $arguments['strictCoverage'] ); $codeCoverage->setCheckForMissingCoversAnnotation( $arguments['strictCoverage'] ); if (isset($arguments['forceCoversAnnotation'])) { $codeCoverage->setForceCoversAnnotation( $arguments['forceCoversAnnotation'] ); } if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $codeCoverage->setIgnoreDeprecatedCode( $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ); } if (isset($arguments['disableCodeCoverageIgnore'])) { $codeCoverage->setDisableIgnoredLines(true); } if (!empty($filterConfiguration['whitelist'])) { $codeCoverage->setAddUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist'] ); $codeCoverage->setProcessUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist'] ); } if (!$this->codeCoverageFilter->hasWhitelist()) { if (!$whitelistFromConfigurationFile && !$whitelistFromOption) { $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.'); } else { $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.'); } $codeCoverageReports = 0; unset($codeCoverage); } } catch (CodeCoverageException $e) { $this->writeMessage('Error', $e->getMessage()); $codeCoverageReports = 0; } } } if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) { $this->write("\n"); $script = (new XdebugFilterScriptGenerator)->generate($filterConfiguration['whitelist']); if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::EXCEPTION_EXIT); } file_put_contents($arguments['xdebugFilterFile'], $script); $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::SUCCESS_EXIT); } $this->write("\n"); if (isset($codeCoverage)) { $result->setCodeCoverage($codeCoverage); if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) { $codeCoverage->setCacheTokens($arguments['cacheTokens']); } } $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); if ($arguments['enforceTimeLimit'] === true) { if (!class_exists(Invoker::class)) { $this->writeMessage('Error', 'Package phpunit/php-invoker is required for enforcing time limits'); } if (!extension_loaded('pcntl') || strpos(ini_get('disable_functions'), 'pcntl') !== false) { $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); } } $result->enforceTimeLimit($arguments['enforceTimeLimit']); $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); if ($suite instanceof TestSuite) { $this->processSuiteFilters($suite, $arguments); $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } foreach ($this->extensions as $extension) { if ($extension instanceof BeforeFirstTestHook) { $extension->executeBeforeFirstTest(); } } $suite->run($result); foreach ($this->extensions as $extension) { if ($extension instanceof AfterLastTestHook) { $extension->executeAfterLastTest(); } } $result->flushListeners(); if ($this->printer instanceof ResultPrinter) { $this->printer->printResult($result); } if (isset($codeCoverage)) { if (isset($arguments['coverageClover'])) { $this->codeCoverageGenerationStart('Clover XML'); try { $writer = new CloverReport; $writer->process($codeCoverage, $arguments['coverageClover']); $this->codeCoverageGenerationSucceeded(); unset($writer); } catch (CodeCoverageException $e) { $this->codeCoverageGenerationFailed($e); } } if (isset($arguments['coverageCrap4J'])) { $this->codeCoverageGenerationStart('Crap4J XML'); try { $writer = new Crap4jReport($arguments['crap4jThreshold']); $writer->process($codeCoverage, $arguments['coverageCrap4J']); $this->codeCoverageGenerationSucceeded(); unset($writer); } catch (CodeCoverageException $e) { $this->codeCoverageGenerationFailed($e); } } if (isset($arguments['coverageHtml'])) { $this->codeCoverageGenerationStart('HTML'); try { $writer = new HtmlReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], sprintf( ' and PHPUnit %s', Version::id() ) ); $writer->process($codeCoverage, $arguments['coverageHtml']); $this->codeCoverageGenerationSucceeded(); unset($writer); } catch (CodeCoverageException $e) { $this->codeCoverageGenerationFailed($e); } } if (isset($arguments['coveragePHP'])) { $this->codeCoverageGenerationStart('PHP'); try { $writer = new PhpReport; $writer->process($codeCoverage, $arguments['coveragePHP']); $this->codeCoverageGenerationSucceeded(); unset($writer); } catch (CodeCoverageException $e) { $this->codeCoverageGenerationFailed($e); } } if (isset($arguments['coverageText'])) { if ($arguments['coverageText'] === 'php://stdout') { $outputStream = $this->printer; $colors = $arguments['colors'] && $arguments['colors'] !== ResultPrinter::COLOR_NEVER; } else { $outputStream = new Printer($arguments['coverageText']); $colors = false; } $processor = new TextReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary'] ); $outputStream->write( $processor->process($codeCoverage, $colors) ); } if (isset($arguments['coverageXml'])) { $this->codeCoverageGenerationStart('PHPUnit XML'); try { $writer = new XmlReport(Version::id()); $writer->process($codeCoverage, $arguments['coverageXml']); $this->codeCoverageGenerationSucceeded(); unset($writer); } catch (CodeCoverageException $e) { $this->codeCoverageGenerationFailed($e); } } } if ($exit) { if ($result->wasSuccessfulIgnoringWarnings()) { if ($arguments['failOnRisky'] && !$result->allHarmless()) { exit(self::FAILURE_EXIT); } if ($arguments['failOnWarning'] && $result->warningCount() > 0) { exit(self::FAILURE_EXIT); } exit(self::SUCCESS_EXIT); } if ($result->errorCount() > 0) { exit(self::EXCEPTION_EXIT); } if ($result->failureCount() > 0) { exit(self::FAILURE_EXIT); } } return $result; } public function setPrinter(ResultPrinter $resultPrinter): void { $this->printer = $resultPrinter; } public function getLoader(): TestSuiteLoader { if ($this->loader === null) { $this->loader = new StandardTestSuiteLoader; } return $this->loader; } public function addExtension(Hook $extension): void { $this->extensions[] = $extension; } protected function runFailed(string $message): void { $this->write($message . PHP_EOL); exit(self::FAILURE_EXIT); } private function createTestResult(): TestResult { return new TestResult; } private function write(string $buffer): void { if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { $buffer = htmlspecialchars($buffer); } if ($this->printer !== null) { $this->printer->write($buffer); } else { print $buffer; } } private function handleConfiguration(array &$arguments): void { if (isset($arguments['configuration']) && !$arguments['configuration'] instanceof Configuration) { $arguments['configuration'] = Configuration::getInstance( $arguments['configuration'] ); } $arguments['debug'] = $arguments['debug'] ?? false; $arguments['filter'] = $arguments['filter'] ?? false; $arguments['listeners'] = $arguments['listeners'] ?? []; if (isset($arguments['configuration'])) { $arguments['configuration']->handlePHPConfiguration(); $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration(); if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) { $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals']; } if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) { $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes']; } if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) { $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState']; } if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) { $arguments['bootstrap'] = $phpunitConfiguration['bootstrap']; } if (isset($phpunitConfiguration['cacheResult']) && !isset($arguments['cacheResult'])) { $arguments['cacheResult'] = $phpunitConfiguration['cacheResult']; } if (isset($phpunitConfiguration['cacheResultFile']) && !isset($arguments['cacheResultFile'])) { $arguments['cacheResultFile'] = $phpunitConfiguration['cacheResultFile']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) { $arguments['colors'] = $phpunitConfiguration['colors']; } if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) { $arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions']; } if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) { $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions']; } if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) { $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions']; } if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) { $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions']; } if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) { $arguments['processIsolation'] = $phpunitConfiguration['processIsolation']; } if (isset($phpunitConfiguration['stopOnDefect']) && !isset($arguments['stopOnDefect'])) { $arguments['stopOnDefect'] = $phpunitConfiguration['stopOnDefect']; } if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) { $arguments['stopOnError'] = $phpunitConfiguration['stopOnError']; } if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) { $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure']; } if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) { $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning']; } if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) { $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete']; } if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) { $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky']; } if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) { $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped']; } if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) { $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning']; } if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) { $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky']; } if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) { $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests']; } if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) { $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests']; } if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) { $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests']; } if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) { $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests']; } if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) { $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage']; } if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']; } if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) { $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput']; } if (isset($phpunitConfiguration['defaultTimeLimit']) && !isset($arguments['defaultTimeLimit'])) { $arguments['defaultTimeLimit'] = $phpunitConfiguration['defaultTimeLimit']; } if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) { $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit']; } if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) { $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests']; } if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) { $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']; } if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) { $arguments['verbose'] = $phpunitConfiguration['verbose']; } if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) { $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList']; } if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) { $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation']; } if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) { $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore']; } if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) { $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']; } if (isset($phpunitConfiguration['executionOrder']) && !isset($arguments['executionOrder'])) { $arguments['executionOrder'] = $phpunitConfiguration['executionOrder']; } if (isset($phpunitConfiguration['executionOrderDefects']) && !isset($arguments['executionOrderDefects'])) { $arguments['executionOrderDefects'] = $phpunitConfiguration['executionOrderDefects']; } if (isset($phpunitConfiguration['resolveDependencies']) && !isset($arguments['resolveDependencies'])) { $arguments['resolveDependencies'] = $phpunitConfiguration['resolveDependencies']; } if (isset($phpunitConfiguration['noInteraction']) && !isset($arguments['noInteraction'])) { $arguments['noInteraction'] = $phpunitConfiguration['noInteraction']; } if (isset($phpunitConfiguration['conflictBetweenPrinterClassAndTestdox'])) { $arguments['conflictBetweenPrinterClassAndTestdox'] = true; } $groupCliArgs = []; if (!empty($arguments['groups'])) { $groupCliArgs = $arguments['groups']; } $groupConfiguration = $arguments['configuration']->getGroupConfiguration(); if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) { $arguments['groups'] = $groupConfiguration['include']; } if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) { $arguments['excludeGroups'] = array_diff($groupConfiguration['exclude'], $groupCliArgs); } foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) { if ($extension['file'] !== '' && !class_exists($extension['class'], false)) { require_once $extension['file']; } if (!class_exists($extension['class'])) { throw new Exception( sprintf( 'Class "%s" does not exist', $extension['class'] ) ); } try { $extensionClass = new ReflectionClass($extension['class']); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$extensionClass->implementsInterface(Hook::class)) { throw new Exception( sprintf( 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', $extension['class'] ) ); } if (count($extension['arguments']) === 0) { $extensionObject = $extensionClass->newInstance(); } else { $extensionObject = $extensionClass->newInstanceArgs( $extension['arguments'] ); } assert($extensionObject instanceof Hook); $this->addExtension($extensionObject); } foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if ($listener['file'] !== '' && !class_exists($listener['class'], false)) { require_once $listener['file']; } if (!class_exists($listener['class'])) { throw new Exception( sprintf( 'Class "%s" does not exist', $listener['class'] ) ); } try { $listenerClass = new ReflectionClass($listener['class']); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$listenerClass->implementsInterface(TestListener::class)) { throw new Exception( sprintf( 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', $listener['class'] ) ); } if (count($listener['arguments']) === 0) { $listener = new $listener['class']; } else { $listener = $listenerClass->newInstanceArgs( $listener['arguments'] ); } $arguments['listeners'][] = $listener; } $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration(); if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) { $arguments['coverageClover'] = $loggingConfiguration['coverage-clover']; } if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) { $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j']; if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) { $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold']; } } if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) { if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) { $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound']; } if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) { $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound']; } $arguments['coverageHtml'] = $loggingConfiguration['coverage-html']; } if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) { $arguments['coveragePHP'] = $loggingConfiguration['coverage-php']; } if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) { $arguments['coverageText'] = $loggingConfiguration['coverage-text']; $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles'] ?? false; $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary'] ?? false; } if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) { $arguments['coverageXml'] = $loggingConfiguration['coverage-xml']; } if (isset($loggingConfiguration['plain'])) { $arguments['listeners'][] = new ResultPrinter( $loggingConfiguration['plain'], true ); } if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) { $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity']; } if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) { $arguments['junitLogfile'] = $loggingConfiguration['junit']; } if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) { $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html']; } if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) { $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text']; } if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) { $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml']; } $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration(); if (isset($testdoxGroupConfiguration['include']) && !isset($arguments['testdoxGroups'])) { $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include']; } if (isset($testdoxGroupConfiguration['exclude']) && !isset($arguments['testdoxExcludeGroups'])) { $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude']; } } $arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true; $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false; $arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT; $arguments['columns'] = $arguments['columns'] ?? 80; $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? false; $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; $arguments['groups'] = $arguments['groups'] ?? []; $arguments['noInteraction'] = $arguments['noInteraction'] ?? false; $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; $arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false; $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; $arguments['repeat'] = $arguments['repeat'] ?? false; $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; $arguments['reverseList'] = $arguments['reverseList'] ?? false; $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; $arguments['verbose'] = $arguments['verbose'] ?? false; if ($arguments['reportLowUpperBound'] > $arguments['reportHighLowerBound']) { $arguments['reportLowUpperBound'] = 50; $arguments['reportHighLowerBound'] = 90; } } private function processSuiteFilters(TestSuite $suite, array $arguments): void { if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups'])) { return; } $filterFactory = new Factory; if (!empty($arguments['excludeGroups'])) { $filterFactory->addFilter( new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups'] ); } if (!empty($arguments['groups'])) { $filterFactory->addFilter( new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new ReflectionClass(NameFilterIterator::class), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } private function writeMessage(string $type, string $message): void { if (!$this->messagePrinted) { $this->write("\n"); } $this->write( sprintf( "%-15s%s\n", $type . ':', $message ) ); $this->messagePrinted = true; } private function createPrinter(string $class, array $arguments): Printer { return new $class( (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList'] ); } private function codeCoverageGenerationStart(string $format): void { $this->write( sprintf( "\nGenerating code coverage report in %s format ... ", $format ) ); Timer::start(); } private function codeCoverageGenerationSucceeded(): void { $this->write( sprintf( "done [%s]\n", Timer::secondsToTimeString(Timer::stop()) ) ); } private function codeCoverageGenerationFailed(\Exception $e): void { $this->write( sprintf( "failed [%s]\n%s\n", Timer::secondsToTimeString(Timer::stop()), $e->getMessage() ) ); } } getNumberOfColumns(); if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) { $numberOfColumns = $maxNumberOfColumns; } $this->numberOfColumns = $numberOfColumns; $this->verbose = $verbose; $this->debug = $debug; $this->reverse = $reverse; if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { $this->colors = true; } else { $this->colors = (self::COLOR_ALWAYS === $colors); } } public function printResult(TestResult $result): void { $this->printHeader($result); $this->printErrors($result); $this->printWarnings($result); $this->printFailures($result); $this->printRisky($result); if ($this->verbose) { $this->printIncompletes($result); $this->printSkipped($result); } $this->printFooter($result); } public function addError(Test $test, Throwable $t, float $time): void { $this->writeProgressWithColor('fg-red, bold', 'E'); $this->lastTestFailed = true; } public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->writeProgressWithColor('bg-red, fg-white', 'F'); $this->lastTestFailed = true; } public function addWarning(Test $test, Warning $e, float $time): void { $this->writeProgressWithColor('fg-yellow, bold', 'W'); $this->lastTestFailed = true; } public function addIncompleteTest(Test $test, Throwable $t, float $time): void { $this->writeProgressWithColor('fg-yellow, bold', 'I'); $this->lastTestFailed = true; } public function addRiskyTest(Test $test, Throwable $t, float $time): void { $this->writeProgressWithColor('fg-yellow, bold', 'R'); $this->lastTestFailed = true; } public function addSkippedTest(Test $test, Throwable $t, float $time): void { $this->writeProgressWithColor('fg-cyan, bold', 'S'); $this->lastTestFailed = true; } public function startTestSuite(TestSuite $suite): void { if ($this->numTests == -1) { $this->numTests = count($suite); $this->numTestsWidth = strlen((string) $this->numTests); $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth); } } public function endTestSuite(TestSuite $suite): void { } public function startTest(Test $test): void { if ($this->debug) { $this->write( sprintf( "Test '%s' started\n", \PHPUnit\Util\Test::describeAsString($test) ) ); } } public function endTest(Test $test, float $time): void { if ($this->debug) { $this->write( sprintf( "Test '%s' ended\n", \PHPUnit\Util\Test::describeAsString($test) ) ); } if (!$this->lastTestFailed) { $this->writeProgress('.'); } if ($test instanceof TestCase) { $this->numAssertions += $test->getNumAssertions(); } elseif ($test instanceof PhptTestCase) { $this->numAssertions++; } $this->lastTestFailed = false; if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { $this->write($test->getActualOutput()); } } protected function printDefects(array $defects, string $type): void { $count = count($defects); if ($count == 0) { return; } if ($this->defectListPrinted) { $this->write("\n--\n\n"); } $this->write( sprintf( "There %s %d %s%s:\n", ($count == 1) ? 'was' : 'were', $count, $type, ($count == 1) ? '' : 's' ) ); $i = 1; if ($this->reverse) { $defects = array_reverse($defects); } foreach ($defects as $defect) { $this->printDefect($defect, $i++); } $this->defectListPrinted = true; } protected function printDefect(TestFailure $defect, int $count): void { $this->printDefectHeader($defect, $count); $this->printDefectTrace($defect); } protected function printDefectHeader(TestFailure $defect, int $count): void { $this->write( sprintf( "\n%d) %s\n", $count, $defect->getTestName() ) ); } protected function printDefectTrace(TestFailure $defect): void { $e = $defect->thrownException(); $this->write((string) $e); while ($e = $e->getPrevious()) { $this->write("\nCaused by\n" . $e); } } protected function printErrors(TestResult $result): void { $this->printDefects($result->errors(), 'error'); } protected function printFailures(TestResult $result): void { $this->printDefects($result->failures(), 'failure'); } protected function printWarnings(TestResult $result): void { $this->printDefects($result->warnings(), 'warning'); } protected function printIncompletes(TestResult $result): void { $this->printDefects($result->notImplemented(), 'incomplete test'); } protected function printRisky(TestResult $result): void { $this->printDefects($result->risky(), 'risky test'); } protected function printSkipped(TestResult $result): void { $this->printDefects($result->skipped(), 'skipped test'); } protected function printHeader(TestResult $result): void { if (count($result) > 0) { $this->write(PHP_EOL . PHP_EOL . Timer::resourceUsage() . PHP_EOL . PHP_EOL); } } protected function printFooter(TestResult $result): void { if (count($result) === 0) { $this->writeWithColor( 'fg-black, bg-yellow', 'No tests executed!' ); return; } if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { $this->writeWithColor( 'fg-black, bg-green', sprintf( 'OK (%d test%s, %d assertion%s)', count($result), (count($result) == 1) ? '' : 's', $this->numAssertions, ($this->numAssertions == 1) ? '' : 's' ) ); return; } $color = 'fg-black, bg-yellow'; if ($result->wasSuccessful()) { if ($this->verbose || !$result->allHarmless()) { $this->write("\n"); } $this->writeWithColor( $color, 'OK, but incomplete, skipped, or risky tests!' ); } else { $this->write("\n"); if ($result->errorCount()) { $color = 'fg-white, bg-red'; $this->writeWithColor( $color, 'ERRORS!' ); } elseif ($result->failureCount()) { $color = 'fg-white, bg-red'; $this->writeWithColor( $color, 'FAILURES!' ); } elseif ($result->warningCount()) { $color = 'fg-black, bg-yellow'; $this->writeWithColor( $color, 'WARNINGS!' ); } } $this->writeCountString(count($result), 'Tests', $color, true); $this->writeCountString($this->numAssertions, 'Assertions', $color, true); $this->writeCountString($result->errorCount(), 'Errors', $color); $this->writeCountString($result->failureCount(), 'Failures', $color); $this->writeCountString($result->warningCount(), 'Warnings', $color); $this->writeCountString($result->skippedCount(), 'Skipped', $color); $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); $this->writeCountString($result->riskyCount(), 'Risky', $color); $this->writeWithColor($color, '.'); } protected function writeProgress(string $progress): void { if ($this->debug) { return; } $this->write($progress); $this->column++; $this->numTestsRun++; if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { if ($this->numTestsRun == $this->numTests) { $this->write(str_repeat(' ', $this->maxColumn - $this->column)); } $this->write( sprintf( ' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', $this->numTestsRun, $this->numTests, floor(($this->numTestsRun / $this->numTests) * 100) ) ); if ($this->column == $this->maxColumn) { $this->writeNewLine(); } } } protected function writeNewLine(): void { $this->column = 0; $this->write("\n"); } protected function colorizeTextBox(string $color, string $buffer): string { if (!$this->colors) { return $buffer; } $lines = preg_split('/\r\n|\r|\n/', $buffer); $padding = max(array_map('\strlen', $lines)); $styledLines = []; foreach ($lines as $line) { $styledLines[] = Color::colorize($color, str_pad($line, $padding)); } return implode(PHP_EOL, $styledLines); } protected function writeWithColor(string $color, string $buffer, bool $lf = true): void { $this->write($this->colorizeTextBox($color, $buffer)); if ($lf) { $this->write(PHP_EOL); } } protected function writeProgressWithColor(string $color, string $buffer): void { $buffer = $this->colorizeTextBox($color, $buffer); $this->writeProgress($buffer); } private function writeCountString(int $count, string $name, string $color, bool $always = false): void { static $first = true; if ($always || $count > 0) { $this->writeWithColor( $color, sprintf( '%s%s: %d', !$first ? ', ' : '', $name, $count ), false ); $first = false; } } } [ ['text' => 'phpunit [options] UnitTest [UnitTest.php]'], ['text' => 'phpunit [options] '], ], 'Code Coverage Options' => [ ['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], ['arg' => '--whitelist ', 'desc' => 'Whitelist for code coverage analysis'], ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration'], ['arg' => '--dump-xdebug-filter ', 'desc' => 'Generate script to set Xdebug code coverage filter'], ], 'Logging Options' => [ ['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration'], ], 'Test Selection Options' => [ ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--list-groups', 'desc' => 'List available test groups'], ['arg' => '--list-suites', 'desc' => 'List available test suites'], ['arg' => '--list-tests', 'desc' => 'List available tests'], ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt'], ], 'Test Execution Options' => [ ['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], ['arg' => '--default-time-limit=', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], ['spacer' => ''], ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], ['spacer' => ''], ['arg' => '--colors=', 'desc' => 'Use colors in output ("never", "auto" or "always")'], ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], ['arg' => '--debug', 'desc' => 'Display debugging information'], ['spacer' => ''], ['arg' => '--loader ', 'desc' => 'TestSuiteLoader implementation to use'], ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], ['spacer' => ''], ['arg' => '--order-by=', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], ['arg' => '--random-order-seed=', 'desc' => 'Use a specific random seed for random order'], ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], ], 'Configuration Options' => [ ['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], ['arg' => '--cache-result-file=', 'desc' => 'Specify result cache path and filename'], ], 'Miscellaneous Options' => [ ['arg' => '-h|--help', 'desc' => 'Prints this usage information'], ['arg' => '--version', 'desc' => 'Prints the version and exits'], ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], ['arg' => '--check-version', 'desc' => 'Check whether PHPUnit is the latest version'], ], ]; private $maxArgLength = 0; private $maxDescLength; private $hasColor = false; public function __construct(?int $width = null, ?bool $withColor = null) { if ($width === null) { $width = (new Console)->getNumberOfColumns(); } if ($withColor === null) { $this->hasColor = (new Console)->hasColorSupport(); } else { $this->hasColor = $withColor; } foreach (self::HELP_TEXT as $options) { foreach ($options as $option) { if (isset($option['arg'])) { $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); } } } $this->maxDescLength = $width - $this->maxArgLength - 4; } public function writeToConsole(): void { if ($this->hasColor) { $this->writeWithColor(); } else { $this->writePlaintext(); } } private function writePlaintext(): void { foreach (self::HELP_TEXT as $section => $options) { print "{$section}:" . PHP_EOL; if ($section !== 'Usage') { print PHP_EOL; } foreach ($options as $option) { if (isset($option['spacer'])) { print PHP_EOL; } if (isset($option['text'])) { print self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { $arg = str_pad($option['arg'], $this->maxArgLength); print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; } } print PHP_EOL; } } private function writeWithColor(): void { foreach (self::HELP_TEXT as $section => $options) { print Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; foreach ($options as $option) { if (isset($option['spacer'])) { print PHP_EOL; } if (isset($option['text'])) { print self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); $arg = preg_replace_callback( '/(<[^>]+>)/', static function ($matches) { return Color::colorize('fg-cyan', $matches[0]); }, $arg ); $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, PHP_EOL)); print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; for ($i = 1; $i < count($desc); $i++) { print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . PHP_EOL; } } } print PHP_EOL; } } } false, 'listSuites' => false, 'listTests' => false, 'listTestsXml' => false, 'loader' => null, 'useDefaultConfiguration' => true, 'loadedExtensions' => [], 'notLoadedExtensions' => [], ]; protected $options = []; protected $longOptions = [ 'atleast-version=' => null, 'prepend=' => null, 'bootstrap=' => null, 'cache-result' => null, 'do-not-cache-result' => null, 'cache-result-file=' => null, 'check-version' => null, 'colors==' => null, 'columns=' => null, 'configuration=' => null, 'coverage-clover=' => null, 'coverage-crap4j=' => null, 'coverage-html=' => null, 'coverage-php=' => null, 'coverage-text==' => null, 'coverage-xml=' => null, 'debug' => null, 'disallow-test-output' => null, 'disallow-resource-usage' => null, 'disallow-todo-tests' => null, 'default-time-limit=' => null, 'enforce-time-limit' => null, 'exclude-group=' => null, 'filter=' => null, 'generate-configuration' => null, 'globals-backup' => null, 'group=' => null, 'help' => null, 'resolve-dependencies' => null, 'ignore-dependencies' => null, 'include-path=' => null, 'list-groups' => null, 'list-suites' => null, 'list-tests' => null, 'list-tests-xml=' => null, 'loader=' => null, 'log-junit=' => null, 'log-teamcity=' => null, 'no-configuration' => null, 'no-coverage' => null, 'no-logging' => null, 'no-interaction' => null, 'no-extensions' => null, 'order-by=' => null, 'printer=' => null, 'process-isolation' => null, 'repeat=' => null, 'dont-report-useless-tests' => null, 'random-order' => null, 'random-order-seed=' => null, 'reverse-order' => null, 'reverse-list' => null, 'static-backup' => null, 'stderr' => null, 'stop-on-defect' => null, 'stop-on-error' => null, 'stop-on-failure' => null, 'stop-on-warning' => null, 'stop-on-incomplete' => null, 'stop-on-risky' => null, 'stop-on-skipped' => null, 'fail-on-warning' => null, 'fail-on-risky' => null, 'strict-coverage' => null, 'disable-coverage-ignore' => null, 'strict-global-state' => null, 'teamcity' => null, 'testdox' => null, 'testdox-group=' => null, 'testdox-exclude-group=' => null, 'testdox-html=' => null, 'testdox-text=' => null, 'testdox-xml=' => null, 'test-suffix=' => null, 'testsuite=' => null, 'verbose' => null, 'version' => null, 'whitelist=' => null, 'dump-xdebug-filter=' => null, ]; private $warnings = []; private $versionStringPrinted = false; public static function main(bool $exit = true): int { return (new static)->run($_SERVER['argv'], $exit); } public function run(array $argv, bool $exit = true): int { $this->handleArguments($argv); $runner = $this->createRunner(); if ($this->arguments['test'] instanceof Test) { $suite = $this->arguments['test']; } else { $suite = $runner->getTest( $this->arguments['test'], $this->arguments['testFile'], $this->arguments['testSuffixes'] ); } if ($this->arguments['listGroups']) { return $this->handleListGroups($suite, $exit); } if ($this->arguments['listSuites']) { return $this->handleListSuites($exit); } if ($this->arguments['listTests']) { return $this->handleListTests($suite, $exit); } if ($this->arguments['listTestsXml']) { return $this->handleListTestsXml($suite, $this->arguments['listTestsXml'], $exit); } unset($this->arguments['test'], $this->arguments['testFile']); try { $result = $runner->doRun($suite, $this->arguments, $this->warnings, $exit); } catch (Exception $e) { print $e->getMessage() . PHP_EOL; } $return = TestRunner::FAILURE_EXIT; if (isset($result) && $result->wasSuccessful()) { $return = TestRunner::SUCCESS_EXIT; } elseif (!isset($result) || $result->errorCount() > 0) { $return = TestRunner::EXCEPTION_EXIT; } if ($exit) { exit($return); } return $return; } protected function createRunner(): TestRunner { return new TestRunner($this->arguments['loader']); } protected function handleArguments(array $argv): void { try { $this->options = Getopt::parse( $argv, 'd:c:hv', array_keys($this->longOptions) ); } catch (Exception $t) { $this->exitWithErrorMessage($t->getMessage()); } foreach ($this->options[0] as $option) { switch ($option[0]) { case '--colors': $this->arguments['colors'] = $option[1] ?: ResultPrinter::COLOR_AUTO; break; case '--bootstrap': $this->arguments['bootstrap'] = $option[1]; break; case '--cache-result': $this->arguments['cacheResult'] = true; break; case '--do-not-cache-result': $this->arguments['cacheResult'] = false; break; case '--cache-result-file': $this->arguments['cacheResultFile'] = $option[1]; break; case '--columns': if (is_numeric($option[1])) { $this->arguments['columns'] = (int) $option[1]; } elseif ($option[1] === 'max') { $this->arguments['columns'] = 'max'; } break; case 'c': case '--configuration': $this->arguments['configuration'] = $option[1]; break; case '--coverage-clover': $this->arguments['coverageClover'] = $option[1]; break; case '--coverage-crap4j': $this->arguments['coverageCrap4J'] = $option[1]; break; case '--coverage-html': $this->arguments['coverageHtml'] = $option[1]; break; case '--coverage-php': $this->arguments['coveragePHP'] = $option[1]; break; case '--coverage-text': if ($option[1] === null) { $option[1] = 'php://stdout'; } $this->arguments['coverageText'] = $option[1]; $this->arguments['coverageTextShowUncoveredFiles'] = false; $this->arguments['coverageTextShowOnlySummary'] = false; break; case '--coverage-xml': $this->arguments['coverageXml'] = $option[1]; break; case 'd': $ini = explode('=', $option[1]); if (isset($ini[0])) { if (isset($ini[1])) { ini_set($ini[0], $ini[1]); } else { ini_set($ini[0], '1'); } } break; case '--debug': $this->arguments['debug'] = true; break; case 'h': case '--help': $this->showHelp(); exit(TestRunner::SUCCESS_EXIT); break; case '--filter': $this->arguments['filter'] = $option[1]; break; case '--testsuite': $this->arguments['testsuite'] = $option[1]; break; case '--generate-configuration': $this->printVersionString(); print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL; print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; $bootstrapScript = trim(fgets(STDIN)); print 'Tests directory (relative to path shown above; default: tests): '; $testsDirectory = trim(fgets(STDIN)); print 'Source directory (relative to path shown above; default: src): '; $src = trim(fgets(STDIN)); if ($bootstrapScript === '') { $bootstrapScript = 'vendor/autoload.php'; } if ($testsDirectory === '') { $testsDirectory = 'tests'; } if ($src === '') { $src = 'src'; } $generator = new ConfigurationGenerator; file_put_contents( 'phpunit.xml', $generator->generateDefaultConfiguration( Version::series(), $bootstrapScript, $testsDirectory, $src ) ); print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . PHP_EOL; exit(TestRunner::SUCCESS_EXIT); break; case '--group': $this->arguments['groups'] = explode(',', $option[1]); break; case '--exclude-group': $this->arguments['excludeGroups'] = explode( ',', $option[1] ); break; case '--test-suffix': $this->arguments['testSuffixes'] = explode( ',', $option[1] ); break; case '--include-path': $includePath = $option[1]; break; case '--list-groups': $this->arguments['listGroups'] = true; break; case '--list-suites': $this->arguments['listSuites'] = true; break; case '--list-tests': $this->arguments['listTests'] = true; break; case '--list-tests-xml': $this->arguments['listTestsXml'] = $option[1]; break; case '--printer': $this->arguments['printer'] = $option[1]; break; case '--loader': $this->arguments['loader'] = $option[1]; break; case '--log-junit': $this->arguments['junitLogfile'] = $option[1]; break; case '--log-teamcity': $this->arguments['teamcityLogfile'] = $option[1]; break; case '--order-by': $this->handleOrderByOption($option[1]); break; case '--process-isolation': $this->arguments['processIsolation'] = true; break; case '--repeat': $this->arguments['repeat'] = (int) $option[1]; break; case '--stderr': $this->arguments['stderr'] = true; break; case '--stop-on-defect': $this->arguments['stopOnDefect'] = true; break; case '--stop-on-error': $this->arguments['stopOnError'] = true; break; case '--stop-on-failure': $this->arguments['stopOnFailure'] = true; break; case '--stop-on-warning': $this->arguments['stopOnWarning'] = true; break; case '--stop-on-incomplete': $this->arguments['stopOnIncomplete'] = true; break; case '--stop-on-risky': $this->arguments['stopOnRisky'] = true; break; case '--stop-on-skipped': $this->arguments['stopOnSkipped'] = true; break; case '--fail-on-warning': $this->arguments['failOnWarning'] = true; break; case '--fail-on-risky': $this->arguments['failOnRisky'] = true; break; case '--teamcity': $this->arguments['printer'] = TeamCity::class; break; case '--testdox': $this->arguments['printer'] = CliTestDoxPrinter::class; break; case '--testdox-group': $this->arguments['testdoxGroups'] = explode( ',', $option[1] ); break; case '--testdox-exclude-group': $this->arguments['testdoxExcludeGroups'] = explode( ',', $option[1] ); break; case '--testdox-html': $this->arguments['testdoxHTMLFile'] = $option[1]; break; case '--testdox-text': $this->arguments['testdoxTextFile'] = $option[1]; break; case '--testdox-xml': $this->arguments['testdoxXMLFile'] = $option[1]; break; case '--no-configuration': $this->arguments['useDefaultConfiguration'] = false; break; case '--no-extensions': $this->arguments['noExtensions'] = true; break; case '--no-coverage': $this->arguments['noCoverage'] = true; break; case '--no-logging': $this->arguments['noLogging'] = true; break; case '--no-interaction': $this->arguments['noInteraction'] = true; break; case '--globals-backup': $this->arguments['backupGlobals'] = true; break; case '--static-backup': $this->arguments['backupStaticAttributes'] = true; break; case 'v': case '--verbose': $this->arguments['verbose'] = true; break; case '--atleast-version': if (version_compare(Version::id(), $option[1], '>=')) { exit(TestRunner::SUCCESS_EXIT); } exit(TestRunner::FAILURE_EXIT); break; case '--version': $this->printVersionString(); exit(TestRunner::SUCCESS_EXIT); break; case '--dont-report-useless-tests': $this->arguments['reportUselessTests'] = false; break; case '--strict-coverage': $this->arguments['strictCoverage'] = true; break; case '--disable-coverage-ignore': $this->arguments['disableCodeCoverageIgnore'] = true; break; case '--strict-global-state': $this->arguments['beStrictAboutChangesToGlobalState'] = true; break; case '--disallow-test-output': $this->arguments['disallowTestOutput'] = true; break; case '--disallow-resource-usage': $this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true; break; case '--default-time-limit': $this->arguments['defaultTimeLimit'] = (int) $option[1]; break; case '--enforce-time-limit': $this->arguments['enforceTimeLimit'] = true; break; case '--disallow-todo-tests': $this->arguments['disallowTodoAnnotatedTests'] = true; break; case '--reverse-list': $this->arguments['reverseList'] = true; break; case '--check-version': $this->handleVersionCheck(); break; case '--whitelist': $this->arguments['whitelist'] = $option[1]; break; case '--random-order': $this->handleOrderByOption('random'); break; case '--random-order-seed': $this->arguments['randomOrderSeed'] = (int) $option[1]; break; case '--resolve-dependencies': $this->handleOrderByOption('depends'); break; case '--ignore-dependencies': $this->handleOrderByOption('no-depends'); break; case '--reverse-order': $this->handleOrderByOption('reverse'); break; case '--dump-xdebug-filter': $this->arguments['xdebugFilterFile'] = $option[1]; break; default: $optionName = str_replace('--', '', $option[0]); $handler = null; if (isset($this->longOptions[$optionName])) { $handler = $this->longOptions[$optionName]; } elseif (isset($this->longOptions[$optionName . '='])) { $handler = $this->longOptions[$optionName . '=']; } if (isset($handler) && is_callable([$this, $handler])) { $this->{$handler}($option[1]); } } } $this->handleCustomTestSuite(); if (!isset($this->arguments['testSuffixes'])) { $this->arguments['testSuffixes'] = ['Test.php', '.phpt']; } if (isset($this->options[1][0]) && substr($this->options[1][0], -5, 5) !== '.phpt' && substr($this->options[1][0], -4, 4) !== '.php' && substr($this->options[1][0], -1, 1) !== '/' && !is_dir($this->options[1][0])) { $this->warnings[] = 'Invocation with class name is deprecated'; } if (!isset($this->arguments['test'])) { if (isset($this->options[1][0])) { $this->arguments['test'] = $this->options[1][0]; } if (isset($this->options[1][1])) { $testFile = realpath($this->options[1][1]); if ($testFile === false) { $this->exitWithErrorMessage( sprintf( 'Cannot open file "%s".', $this->options[1][1] ) ); } $this->arguments['testFile'] = $testFile; } else { $this->arguments['testFile'] = ''; } if (isset($this->arguments['test']) && is_file($this->arguments['test']) && strrpos($this->arguments['test'], '.') !== false && substr($this->arguments['test'], -5, 5) !== '.phpt') { $this->arguments['testFile'] = realpath($this->arguments['test']); $this->arguments['test'] = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.')); } if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) === '.phpt') { $suite = new TestSuite; $suite->addTestFile($this->arguments['test']); $this->arguments['test'] = $suite; } } if (isset($includePath)) { ini_set( 'include_path', $includePath . PATH_SEPARATOR . ini_get('include_path') ); } if ($this->arguments['loader'] !== null) { $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); } if (isset($this->arguments['configuration']) && is_dir($this->arguments['configuration'])) { $configurationFile = $this->arguments['configuration'] . '/phpunit.xml'; if (file_exists($configurationFile)) { $this->arguments['configuration'] = realpath( $configurationFile ); } elseif (file_exists($configurationFile . '.dist')) { $this->arguments['configuration'] = realpath( $configurationFile . '.dist' ); } } elseif (!isset($this->arguments['configuration']) && $this->arguments['useDefaultConfiguration']) { if (file_exists('phpunit.xml')) { $this->arguments['configuration'] = realpath('phpunit.xml'); } elseif (file_exists('phpunit.xml.dist')) { $this->arguments['configuration'] = realpath( 'phpunit.xml.dist' ); } } if (isset($this->arguments['configuration'])) { try { $configuration = Configuration::getInstance( $this->arguments['configuration'] ); } catch (Throwable $t) { print $t->getMessage() . PHP_EOL; exit(TestRunner::FAILURE_EXIT); } $phpunitConfiguration = $configuration->getPHPUnitConfiguration(); $configuration->handlePHPConfiguration(); if (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } elseif (isset($phpunitConfiguration['bootstrap'])) { $this->handleBootstrap($phpunitConfiguration['bootstrap']); } if (isset($phpunitConfiguration['stderr']) && !isset($this->arguments['stderr'])) { $this->arguments['stderr'] = $phpunitConfiguration['stderr']; } if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && extension_loaded('phar')) { $this->handleExtensions($phpunitConfiguration['extensionsDirectory']); } if (isset($phpunitConfiguration['columns']) && !isset($this->arguments['columns'])) { $this->arguments['columns'] = $phpunitConfiguration['columns']; } if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) { $file = $phpunitConfiguration['printerFile'] ?? ''; $this->arguments['printer'] = $this->handlePrinter( $phpunitConfiguration['printerClass'], $file ); } if (isset($phpunitConfiguration['testSuiteLoaderClass'])) { $file = $phpunitConfiguration['testSuiteLoaderFile'] ?? ''; $this->arguments['loader'] = $this->handleLoader( $phpunitConfiguration['testSuiteLoaderClass'], $file ); } if (!isset($this->arguments['testsuite']) && isset($phpunitConfiguration['defaultTestSuite'])) { $this->arguments['testsuite'] = $phpunitConfiguration['defaultTestSuite']; } if (!isset($this->arguments['test'])) { $testSuite = $configuration->getTestSuiteConfiguration($this->arguments['testsuite'] ?? ''); if ($testSuite !== null) { $this->arguments['test'] = $testSuite; } } } elseif (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) { $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); } if (!isset($this->arguments['test'])) { $this->showHelp(); exit(TestRunner::EXCEPTION_EXIT); } } protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader { if (!class_exists($loaderClass, false)) { if ($loaderFile == '') { $loaderFile = Filesystem::classNameToFilename( $loaderClass ); } $loaderFile = stream_resolve_include_path($loaderFile); if ($loaderFile) { require $loaderFile; } } if (class_exists($loaderClass, false)) { try { $class = new ReflectionClass($loaderClass); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) { $object = $class->newInstance(); assert($object instanceof TestSuiteLoader); return $object; } } if ($loaderClass == StandardTestSuiteLoader::class) { return null; } $this->exitWithErrorMessage( sprintf( 'Could not use "%s" as loader.', $loaderClass ) ); return null; } protected function handlePrinter(string $printerClass, string $printerFile = '') { if (!class_exists($printerClass, false)) { if ($printerFile == '') { $printerFile = Filesystem::classNameToFilename( $printerClass ); } $printerFile = stream_resolve_include_path($printerFile); if ($printerFile) { require $printerFile; } } if (!class_exists($printerClass)) { $this->exitWithErrorMessage( sprintf( 'Could not use "%s" as printer: class does not exist', $printerClass ) ); } try { $class = new ReflectionClass($printerClass); } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), (int) $e->getCode(), $e ); } if (!$class->implementsInterface(TestListener::class)) { $this->exitWithErrorMessage( sprintf( 'Could not use "%s" as printer: class does not implement %s', $printerClass, TestListener::class ) ); } if (!$class->isSubclassOf(Printer::class)) { $this->exitWithErrorMessage( sprintf( 'Could not use "%s" as printer: class does not extend %s', $printerClass, Printer::class ) ); } if (!$class->isInstantiable()) { $this->exitWithErrorMessage( sprintf( 'Could not use "%s" as printer: class cannot be instantiated', $printerClass ) ); } if ($class->isSubclassOf(ResultPrinter::class)) { return $printerClass; } $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; return $class->newInstance($outputStream); } protected function handleBootstrap(string $filename): void { try { FileLoader::checkAndLoad($filename); } catch (Exception $e) { $this->exitWithErrorMessage($e->getMessage()); } } protected function handleVersionCheck(): void { $this->printVersionString(); $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); $isOutdated = version_compare($latestVersion, Version::id(), '>'); if ($isOutdated) { printf( 'You are not using the latest version of PHPUnit.' . PHP_EOL . 'The latest version is PHPUnit %s.' . PHP_EOL, $latestVersion ); } else { print 'You are using the latest version of PHPUnit.' . PHP_EOL; } exit(TestRunner::SUCCESS_EXIT); } protected function showHelp(): void { $this->printVersionString(); (new Help)->writeToConsole(); } protected function handleCustomTestSuite(): void { } private function printVersionString(): void { if ($this->versionStringPrinted) { return; } print Version::getVersionString() . PHP_EOL . PHP_EOL; $this->versionStringPrinted = true; } private function exitWithErrorMessage(string $message): void { $this->printVersionString(); print $message . PHP_EOL; exit(TestRunner::FAILURE_EXIT); } private function handleExtensions(string $directory): void { foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) { if (!file_exists('phar://' . $file . '/manifest.xml')) { $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; continue; } try { $applicationName = new ApplicationName('phpunit/phpunit'); $version = new PharIoVersion(Version::series()); $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); if (!$manifest->isExtensionFor($applicationName)) { $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; continue; } if (!$manifest->isExtensionFor($applicationName, $version)) { $this->arguments['notLoadedExtensions'][] = $file . ' is not compatible with this version of PHPUnit'; continue; } } catch (ManifestException $e) { $this->arguments['notLoadedExtensions'][] = $file . ': ' . $e->getMessage(); continue; } require $file; $this->arguments['loadedExtensions'][] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); } } private function handleListGroups(TestSuite $suite, bool $exit): int { $this->printVersionString(); print 'Available test group(s):' . PHP_EOL; $groups = $suite->getGroups(); sort($groups); foreach ($groups as $group) { printf( ' - %s' . PHP_EOL, $group ); } if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } private function handleListSuites(bool $exit): int { $this->printVersionString(); print 'Available test suite(s):' . PHP_EOL; $configuration = Configuration::getInstance( $this->arguments['configuration'] ); foreach ($configuration->getTestSuiteNames() as $suiteName) { printf( ' - %s' . PHP_EOL, $suiteName ); } if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } private function handleListTests(TestSuite $suite, bool $exit): int { $this->printVersionString(); $renderer = new TextTestListRenderer; print $renderer->render($suite); if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int { $this->printVersionString(); $renderer = new XmlTestListRenderer; file_put_contents($target, $renderer->render($suite)); printf( 'Wrote list of tests that would have been run to %s' . PHP_EOL, $target ); if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } private function handleOrderByOption(string $value): void { foreach (explode(',', $value) as $order) { switch ($order) { case 'default': $this->arguments['executionOrder'] = TestSuiteSorter::ORDER_DEFAULT; $this->arguments['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFAULT; $this->arguments['resolveDependencies'] = true; break; case 'defects': $this->arguments['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFECTS_FIRST; break; case 'depends': $this->arguments['resolveDependencies'] = true; break; case 'duration': $this->arguments['executionOrder'] = TestSuiteSorter::ORDER_DURATION; break; case 'no-depends': $this->arguments['resolveDependencies'] = false; break; case 'random': $this->arguments['executionOrder'] = TestSuiteSorter::ORDER_RANDOMIZED; break; case 'reverse': $this->arguments['executionOrder'] = TestSuiteSorter::ORDER_REVERSED; break; case 'size': $this->arguments['executionOrder'] = TestSuiteSorter::ORDER_SIZE; break; default: $this->exitWithErrorMessage("unrecognized --order-by option: {$order}"); } } } } 'PHP_Token_OPEN_BRACKET', ')' => 'PHP_Token_CLOSE_BRACKET', '[' => 'PHP_Token_OPEN_SQUARE', ']' => 'PHP_Token_CLOSE_SQUARE', '{' => 'PHP_Token_OPEN_CURLY', '}' => 'PHP_Token_CLOSE_CURLY', ';' => 'PHP_Token_SEMICOLON', '.' => 'PHP_Token_DOT', ',' => 'PHP_Token_COMMA', '=' => 'PHP_Token_EQUAL', '<' => 'PHP_Token_LT', '>' => 'PHP_Token_GT', '+' => 'PHP_Token_PLUS', '-' => 'PHP_Token_MINUS', '*' => 'PHP_Token_MULT', '/' => 'PHP_Token_DIV', '?' => 'PHP_Token_QUESTION_MARK', '!' => 'PHP_Token_EXCLAMATION_MARK', ':' => 'PHP_Token_COLON', '"' => 'PHP_Token_DOUBLE_QUOTES', '@' => 'PHP_Token_AT', '&' => 'PHP_Token_AMPERSAND', '%' => 'PHP_Token_PERCENT', '|' => 'PHP_Token_PIPE', '$' => 'PHP_Token_DOLLAR', '^' => 'PHP_Token_CARET', '~' => 'PHP_Token_TILDE', '`' => 'PHP_Token_BACKTICK' ]; protected $filename; protected $tokens = []; protected $position = 0; protected $linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0]; protected $classes; protected $functions; protected $includes; protected $interfaces; protected $traits; protected $lineToFunctionMap = []; public function __construct($sourceCode) { if (is_file($sourceCode)) { $this->filename = $sourceCode; $sourceCode = file_get_contents($sourceCode); } $this->scan($sourceCode); } public function __destruct() { $this->tokens = []; } public function __toString() { $buffer = ''; foreach ($this as $token) { $buffer .= $token; } return $buffer; } public function getFilename() { return $this->filename; } protected function scan($sourceCode) { $id = 0; $line = 1; $tokens = token_get_all($sourceCode); $numTokens = count($tokens); $lastNonWhitespaceTokenWasDoubleColon = false; for ($i = 0; $i < $numTokens; ++$i) { $token = $tokens[$i]; $skip = 0; if (is_array($token)) { $name = substr(token_name($token[0]), 2); $text = $token[1]; if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') { $name = 'CLASS_NAME_CONSTANT'; } elseif ($name == 'USE' && isset($tokens[$i + 2][0]) && $tokens[$i + 2][0] == T_FUNCTION) { $name = 'USE_FUNCTION'; $text .= $tokens[$i + 1][1] . $tokens[$i + 2][1]; $skip = 2; } $tokenClass = 'PHP_Token_' . $name; } else { $text = $token; $tokenClass = self::$customTokens[$token]; } $this->tokens[] = new $tokenClass($text, $line, $this, $id++); $lines = substr_count($text, "\n"); $line += $lines; if ($tokenClass == 'PHP_Token_HALT_COMPILER') { break; } elseif ($tokenClass == 'PHP_Token_COMMENT' || $tokenClass == 'PHP_Token_DOC_COMMENT') { $this->linesOfCode['cloc'] += $lines + 1; } if ($name == 'DOUBLE_COLON') { $lastNonWhitespaceTokenWasDoubleColon = true; } elseif ($name != 'WHITESPACE') { $lastNonWhitespaceTokenWasDoubleColon = false; } $i += $skip; } $this->linesOfCode['loc'] = substr_count($sourceCode, "\n"); $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] - $this->linesOfCode['cloc']; } public function count(): int { return count($this->tokens); } public function tokens() { return $this->tokens; } public function getClasses() { if ($this->classes !== null) { return $this->classes; } $this->parse(); return $this->classes; } public function getFunctions() { if ($this->functions !== null) { return $this->functions; } $this->parse(); return $this->functions; } public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } $this->parse(); return $this->interfaces; } public function getTraits() { if ($this->traits !== null) { return $this->traits; } $this->parse(); return $this->traits; } public function getIncludes($categorize = false, $category = null) { if ($this->includes === null) { $this->includes = [ 'require_once' => [], 'require' => [], 'include_once' => [], 'include' => [] ]; foreach ($this->tokens as $token) { switch (PHP_Token_Util::getClass($token)) { case 'PHP_Token_REQUIRE_ONCE': case 'PHP_Token_REQUIRE': case 'PHP_Token_INCLUDE_ONCE': case 'PHP_Token_INCLUDE': $this->includes[$token->getType()][] = $token->getName(); break; } } } if (isset($this->includes[$category])) { $includes = $this->includes[$category]; } elseif ($categorize === false) { $includes = array_merge( $this->includes['require_once'], $this->includes['require'], $this->includes['include_once'], $this->includes['include'] ); } else { $includes = $this->includes; } return $includes; } public function getFunctionForLine($line) { $this->parse(); if (isset($this->lineToFunctionMap[$line])) { return $this->lineToFunctionMap[$line]; } } protected function parse() { $this->interfaces = []; $this->classes = []; $this->traits = []; $this->functions = []; $class = []; $classEndLine = []; $trait = false; $traitEndLine = false; $interface = false; $interfaceEndLine = false; foreach ($this->tokens as $token) { switch (PHP_Token_Util::getClass($token)) { case 'PHP_Token_HALT_COMPILER': return; case 'PHP_Token_INTERFACE': $interface = $token->getName(); $interfaceEndLine = $token->getEndLine(); $this->interfaces[$interface] = [ 'methods' => [], 'parent' => $token->getParent(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $interfaceEndLine, 'package' => $token->getPackage(), 'file' => $this->filename ]; break; case 'PHP_Token_CLASS': case 'PHP_Token_TRAIT': $tmp = [ 'methods' => [], 'parent' => $token->getParent(), 'interfaces'=> $token->getInterfaces(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'package' => $token->getPackage(), 'file' => $this->filename ]; if ($token->getName() !== null) { if ($token instanceof PHP_Token_CLASS) { $class[] = $token->getName(); $classEndLine[] = $token->getEndLine(); $this->classes[$class[count($class) - 1]] = $tmp; } else { $trait = $token->getName(); $traitEndLine = $token->getEndLine(); $this->traits[$trait] = $tmp; } } break; case 'PHP_Token_FUNCTION': $name = $token->getName(); $tmp = [ 'docblock' => $token->getDocblock(), 'keywords' => $token->getKeywords(), 'visibility'=> $token->getVisibility(), 'signature' => $token->getSignature(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'ccn' => $token->getCCN(), 'file' => $this->filename ]; if (empty($class) && $trait === false && $interface === false) { $this->functions[$name] = $tmp; $this->addFunctionToMap( $name, $tmp['startLine'], $tmp['endLine'] ); } elseif (!empty($class)) { $this->classes[$class[count($class) - 1]]['methods'][$name] = $tmp; $this->addFunctionToMap( $class[count($class) - 1] . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } elseif ($trait !== false) { $this->traits[$trait]['methods'][$name] = $tmp; $this->addFunctionToMap( $trait . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } else { $this->interfaces[$interface]['methods'][$name] = $tmp; } break; case 'PHP_Token_CLOSE_CURLY': if (!empty($classEndLine) && $classEndLine[count($classEndLine) - 1] == $token->getLine()) { array_pop($classEndLine); array_pop($class); } elseif ($traitEndLine !== false && $traitEndLine == $token->getLine()) { $trait = false; $traitEndLine = false; } elseif ($interfaceEndLine !== false && $interfaceEndLine == $token->getLine()) { $interface = false; $interfaceEndLine = false; } break; } } } public function getLinesOfCode() { return $this->linesOfCode; } public function rewind(): void { $this->position = 0; } public function valid(): bool { return isset($this->tokens[$this->position]); } #[\ReturnTypeWillChange] public function key() { return $this->position; } #[\ReturnTypeWillChange] public function current() { return $this->tokens[$this->position]; } public function next(): void { $this->position++; } public function offsetExists($offset): bool { return isset($this->tokens[$offset]); } #[\ReturnTypeWillChange] public function offsetGet($offset) { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } return $this->tokens[$offset]; } public function offsetSet($offset, $value): void { $this->tokens[$offset] = $value; } public function offsetUnset($offset): void { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } unset($this->tokens[$offset]); } public function seek($position): void { $this->position = $position; if (!$this->valid()) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $this->position ) ); } } private function addFunctionToMap($name, $startLine, $endLine) { for ($line = $startLine; $line <= $endLine; $line++) { $this->lineToFunctionMap[$line] = $name; } } } text = $text; $this->line = $line; $this->tokenStream = $tokenStream; $this->id = $id; } public function __toString() { return $this->text; } public function getLine() { return $this->line; } public function getId() { return $this->id; } } abstract class PHP_TokenWithScope extends PHP_Token { protected $endTokenId; public function getDocblock() { $tokens = $this->tokenStream->tokens(); $currentLineNumber = $tokens[$this->id]->getLine(); $prevLineNumber = $currentLineNumber - 1; for ($i = $this->id - 1; $i; $i--) { if (!isset($tokens[$i])) { return; } if ($tokens[$i] instanceof PHP_Token_FUNCTION || $tokens[$i] instanceof PHP_Token_CLASS || $tokens[$i] instanceof PHP_Token_TRAIT) { break; } $line = $tokens[$i]->getLine(); if ($line == $currentLineNumber || ($line == $prevLineNumber && $tokens[$i] instanceof PHP_Token_WHITESPACE)) { continue; } if ($line < $currentLineNumber && !$tokens[$i] instanceof PHP_Token_DOC_COMMENT) { break; } return (string) $tokens[$i]; } } public function getEndTokenId() { $block = 0; $i = $this->id; $tokens = $this->tokenStream->tokens(); while ($this->endTokenId === null && isset($tokens[$i])) { if ($tokens[$i] instanceof PHP_Token_OPEN_CURLY || $tokens[$i] instanceof PHP_Token_DOLLAR_OPEN_CURLY_BRACES || $tokens[$i] instanceof PHP_Token_CURLY_OPEN) { $block++; } elseif ($tokens[$i] instanceof PHP_Token_CLOSE_CURLY) { $block--; if ($block === 0) { $this->endTokenId = $i; } } elseif (($this instanceof PHP_Token_FUNCTION || $this instanceof PHP_Token_NAMESPACE) && $tokens[$i] instanceof PHP_Token_SEMICOLON) { if ($block === 0) { $this->endTokenId = $i; } } $i++; } if ($this->endTokenId === null) { $this->endTokenId = $this->id; } return $this->endTokenId; } public function getEndLine() { return $this->tokenStream[$this->getEndTokenId()]->getLine(); } } abstract class PHP_TokenWithScopeAndVisibility extends PHP_TokenWithScope { public function getVisibility() { $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { return strtolower( str_replace('PHP_Token_', '', PHP_Token_Util::getClass($tokens[$i])) ); } if (isset($tokens[$i]) && !($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { break; } } } public function getKeywords() { $keywords = []; $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { continue; } if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { $keywords[] = strtolower( str_replace('PHP_Token_', '', PHP_Token_Util::getClass($tokens[$i])) ); } } return implode(',', $keywords); } } abstract class PHP_Token_Includes extends PHP_Token { protected $name; protected $type; public function getName() { if ($this->name === null) { $this->process(); } return $this->name; } public function getType() { if ($this->type === null) { $this->process(); } return $this->type; } private function process() { $tokens = $this->tokenStream->tokens(); if ($tokens[$this->id + 2] instanceof PHP_Token_CONSTANT_ENCAPSED_STRING) { $this->name = trim($tokens[$this->id + 2], "'\""); $this->type = strtolower( str_replace('PHP_Token_', '', PHP_Token_Util::getClass($tokens[$this->id])) ); } } } class PHP_Token_FUNCTION extends PHP_TokenWithScopeAndVisibility { protected $arguments; protected $ccn; protected $name; protected $signature; private $anonymous = false; public function getArguments() { if ($this->arguments !== null) { return $this->arguments; } $this->arguments = []; $tokens = $this->tokenStream->tokens(); $typeDeclaration = null; $i = $this->id + 2; while (!$tokens[$i - 1] instanceof PHP_Token_OPEN_BRACKET) { $i++; } while (!$tokens[$i] instanceof PHP_Token_CLOSE_BRACKET) { if ($tokens[$i] instanceof PHP_Token_STRING) { $typeDeclaration = (string) $tokens[$i]; } elseif ($tokens[$i] instanceof PHP_Token_VARIABLE) { $this->arguments[(string) $tokens[$i]] = $typeDeclaration; $typeDeclaration = null; } $i++; } return $this->arguments; } public function getName() { if ($this->name !== null) { return $this->name; } $tokens = $this->tokenStream->tokens(); $i = $this->id + 1; if ($tokens[$i] instanceof PHP_Token_WHITESPACE) { $i++; } if ($tokens[$i] instanceof PHP_Token_AMPERSAND) { $i++; } if ($tokens[$i + 1] instanceof PHP_Token_OPEN_BRACKET) { $this->name = (string) $tokens[$i]; } elseif ($tokens[$i + 1] instanceof PHP_Token_WHITESPACE && $tokens[$i + 2] instanceof PHP_Token_OPEN_BRACKET) { $this->name = (string) $tokens[$i]; } else { $this->anonymous = true; $this->name = sprintf( 'anonymousFunction:%s#%s', $this->getLine(), $this->getId() ); } if (!$this->isAnonymous()) { for ($i = $this->id; $i; --$i) { if ($tokens[$i] instanceof PHP_Token_NAMESPACE) { $this->name = $tokens[$i]->getName() . '\\' . $this->name; break; } if ($tokens[$i] instanceof PHP_Token_INTERFACE) { break; } } } return $this->name; } public function getCCN() { if ($this->ccn !== null) { return $this->ccn; } $this->ccn = 1; $end = $this->getEndTokenId(); $tokens = $this->tokenStream->tokens(); for ($i = $this->id; $i <= $end; $i++) { switch (PHP_Token_Util::getClass($tokens[$i])) { case 'PHP_Token_IF': case 'PHP_Token_ELSEIF': case 'PHP_Token_FOR': case 'PHP_Token_FOREACH': case 'PHP_Token_WHILE': case 'PHP_Token_CASE': case 'PHP_Token_CATCH': case 'PHP_Token_BOOLEAN_AND': case 'PHP_Token_LOGICAL_AND': case 'PHP_Token_BOOLEAN_OR': case 'PHP_Token_LOGICAL_OR': case 'PHP_Token_QUESTION_MARK': $this->ccn++; break; } } return $this->ccn; } public function getSignature() { if ($this->signature !== null) { return $this->signature; } if ($this->isAnonymous()) { $this->signature = 'anonymousFunction'; $i = $this->id + 1; } else { $this->signature = ''; $i = $this->id + 2; } $tokens = $this->tokenStream->tokens(); while (isset($tokens[$i]) && !$tokens[$i] instanceof PHP_Token_OPEN_CURLY && !$tokens[$i] instanceof PHP_Token_SEMICOLON) { $this->signature .= $tokens[$i++]; } $this->signature = trim($this->signature); return $this->signature; } public function isAnonymous() { return $this->anonymous; } } class PHP_Token_INTERFACE extends PHP_TokenWithScopeAndVisibility { protected $interfaces; public function getName() { return (string) $this->tokenStream[$this->id + 2]; } public function hasParent() { return $this->tokenStream[$this->id + 4] instanceof PHP_Token_EXTENDS; } public function getPackage() { $className = $this->getName(); $docComment = $this->getDocblock(); $result = [ 'namespace' => '', 'fullPackage' => '', 'category' => '', 'package' => '', 'subpackage' => '' ]; for ($i = $this->id; $i; --$i) { if ($this->tokenStream[$i] instanceof PHP_Token_NAMESPACE) { $result['namespace'] = $this->tokenStream[$i]->getName(); break; } } if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) { $result['category'] = $matches[1]; } if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) { $result['package'] = $matches[1]; $result['fullPackage'] = $matches[1]; } if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) { $result['subpackage'] = $matches[1]; $result['fullPackage'] .= '.' . $matches[1]; } if (empty($result['fullPackage'])) { $result['fullPackage'] = $this->arrayToName( explode('_', str_replace('\\', '_', $className)), '.' ); } return $result; } protected function arrayToName(array $parts, $join = '\\') { $result = ''; if (count($parts) > 1) { array_pop($parts); $result = implode($join, $parts); } return $result; } public function getParent() { if (!$this->hasParent()) { return false; } $i = $this->id + 6; $tokens = $this->tokenStream->tokens(); $className = (string) $tokens[$i]; while (isset($tokens[$i + 1]) && !$tokens[$i + 1] instanceof PHP_Token_WHITESPACE) { $className .= (string) $tokens[++$i]; } return $className; } public function hasInterfaces() { return (isset($this->tokenStream[$this->id + 4]) && $this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) || (isset($this->tokenStream[$this->id + 8]) && $this->tokenStream[$this->id + 8] instanceof PHP_Token_IMPLEMENTS); } public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } if (!$this->hasInterfaces()) { return ($this->interfaces = false); } if ($this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) { $i = $this->id + 3; } else { $i = $this->id + 7; } $tokens = $this->tokenStream->tokens(); while (!$tokens[$i + 1] instanceof PHP_Token_OPEN_CURLY) { $i++; if ($tokens[$i] instanceof PHP_Token_STRING) { $this->interfaces[] = (string) $tokens[$i]; } } return $this->interfaces; } } class PHP_Token_ABSTRACT extends PHP_Token { } class PHP_Token_AMPERSAND extends PHP_Token { } class PHP_Token_AND_EQUAL extends PHP_Token { } class PHP_Token_ARRAY extends PHP_Token { } class PHP_Token_ARRAY_CAST extends PHP_Token { } class PHP_Token_AS extends PHP_Token { } class PHP_Token_AT extends PHP_Token { } class PHP_Token_BACKTICK extends PHP_Token { } class PHP_Token_BAD_CHARACTER extends PHP_Token { } class PHP_Token_BOOLEAN_AND extends PHP_Token { } class PHP_Token_BOOLEAN_OR extends PHP_Token { } class PHP_Token_BOOL_CAST extends PHP_Token { } class PHP_Token_BREAK extends PHP_Token { } class PHP_Token_CARET extends PHP_Token { } class PHP_Token_CASE extends PHP_Token { } class PHP_Token_CATCH extends PHP_Token { } class PHP_Token_CHARACTER extends PHP_Token { } class PHP_Token_CLASS extends PHP_Token_INTERFACE { private $anonymous = false; private $name; public function getName() { if ($this->name !== null) { return $this->name; } $next = $this->tokenStream[$this->id + 1]; if ($next instanceof PHP_Token_WHITESPACE) { $next = $this->tokenStream[$this->id + 2]; } if ($next instanceof PHP_Token_STRING) { $this->name =(string) $next; return $this->name; } if ($next instanceof PHP_Token_OPEN_CURLY || $next instanceof PHP_Token_EXTENDS || $next instanceof PHP_Token_IMPLEMENTS) { $this->name = sprintf( 'AnonymousClass:%s#%s', $this->getLine(), $this->getId() ); $this->anonymous = true; return $this->name; } } public function isAnonymous() { return $this->anonymous; } } class PHP_Token_CLASS_C extends PHP_Token { } class PHP_Token_CLASS_NAME_CONSTANT extends PHP_Token { } class PHP_Token_CLONE extends PHP_Token { } class PHP_Token_CLOSE_BRACKET extends PHP_Token { } class PHP_Token_CLOSE_CURLY extends PHP_Token { } class PHP_Token_CLOSE_SQUARE extends PHP_Token { } class PHP_Token_CLOSE_TAG extends PHP_Token { } class PHP_Token_COLON extends PHP_Token { } class PHP_Token_COMMA extends PHP_Token { } class PHP_Token_COMMENT extends PHP_Token { } class PHP_Token_CONCAT_EQUAL extends PHP_Token { } class PHP_Token_CONST extends PHP_Token { } class PHP_Token_CONSTANT_ENCAPSED_STRING extends PHP_Token { } class PHP_Token_CONTINUE extends PHP_Token { } class PHP_Token_CURLY_OPEN extends PHP_Token { } class PHP_Token_DEC extends PHP_Token { } class PHP_Token_DECLARE extends PHP_Token { } class PHP_Token_DEFAULT extends PHP_Token { } class PHP_Token_DIV extends PHP_Token { } class PHP_Token_DIV_EQUAL extends PHP_Token { } class PHP_Token_DNUMBER extends PHP_Token { } class PHP_Token_DO extends PHP_Token { } class PHP_Token_DOC_COMMENT extends PHP_Token { } class PHP_Token_DOLLAR extends PHP_Token { } class PHP_Token_DOLLAR_OPEN_CURLY_BRACES extends PHP_Token { } class PHP_Token_DOT extends PHP_Token { } class PHP_Token_DOUBLE_ARROW extends PHP_Token { } class PHP_Token_DOUBLE_CAST extends PHP_Token { } class PHP_Token_DOUBLE_COLON extends PHP_Token { } class PHP_Token_DOUBLE_QUOTES extends PHP_Token { } class PHP_Token_ECHO extends PHP_Token { } class PHP_Token_ELSE extends PHP_Token { } class PHP_Token_ELSEIF extends PHP_Token { } class PHP_Token_EMPTY extends PHP_Token { } class PHP_Token_ENCAPSED_AND_WHITESPACE extends PHP_Token { } class PHP_Token_ENDDECLARE extends PHP_Token { } class PHP_Token_ENDFOR extends PHP_Token { } class PHP_Token_ENDFOREACH extends PHP_Token { } class PHP_Token_ENDIF extends PHP_Token { } class PHP_Token_ENDSWITCH extends PHP_Token { } class PHP_Token_ENDWHILE extends PHP_Token { } class PHP_Token_END_HEREDOC extends PHP_Token { } class PHP_Token_EQUAL extends PHP_Token { } class PHP_Token_EVAL extends PHP_Token { } class PHP_Token_EXCLAMATION_MARK extends PHP_Token { } class PHP_Token_EXIT extends PHP_Token { } class PHP_Token_EXTENDS extends PHP_Token { } class PHP_Token_FILE extends PHP_Token { } class PHP_Token_FINAL extends PHP_Token { } class PHP_Token_FOR extends PHP_Token { } class PHP_Token_FOREACH extends PHP_Token { } class PHP_Token_FUNC_C extends PHP_Token { } class PHP_Token_GLOBAL extends PHP_Token { } class PHP_Token_GT extends PHP_Token { } class PHP_Token_IF extends PHP_Token { } class PHP_Token_IMPLEMENTS extends PHP_Token { } class PHP_Token_INC extends PHP_Token { } class PHP_Token_INCLUDE extends PHP_Token_Includes { } class PHP_Token_INCLUDE_ONCE extends PHP_Token_Includes { } class PHP_Token_INLINE_HTML extends PHP_Token { } class PHP_Token_INSTANCEOF extends PHP_Token { } class PHP_Token_INT_CAST extends PHP_Token { } class PHP_Token_ISSET extends PHP_Token { } class PHP_Token_IS_EQUAL extends PHP_Token { } class PHP_Token_IS_GREATER_OR_EQUAL extends PHP_Token { } class PHP_Token_IS_IDENTICAL extends PHP_Token { } class PHP_Token_IS_NOT_EQUAL extends PHP_Token { } class PHP_Token_IS_NOT_IDENTICAL extends PHP_Token { } class PHP_Token_IS_SMALLER_OR_EQUAL extends PHP_Token { } class PHP_Token_LINE extends PHP_Token { } class PHP_Token_LIST extends PHP_Token { } class PHP_Token_LNUMBER extends PHP_Token { } class PHP_Token_LOGICAL_AND extends PHP_Token { } class PHP_Token_LOGICAL_OR extends PHP_Token { } class PHP_Token_LOGICAL_XOR extends PHP_Token { } class PHP_Token_LT extends PHP_Token { } class PHP_Token_METHOD_C extends PHP_Token { } class PHP_Token_MINUS extends PHP_Token { } class PHP_Token_MINUS_EQUAL extends PHP_Token { } class PHP_Token_MOD_EQUAL extends PHP_Token { } class PHP_Token_MULT extends PHP_Token { } class PHP_Token_MUL_EQUAL extends PHP_Token { } class PHP_Token_NEW extends PHP_Token { } class PHP_Token_NUM_STRING extends PHP_Token { } class PHP_Token_OBJECT_CAST extends PHP_Token { } class PHP_Token_OBJECT_OPERATOR extends PHP_Token { } class PHP_Token_OPEN_BRACKET extends PHP_Token { } class PHP_Token_OPEN_CURLY extends PHP_Token { } class PHP_Token_OPEN_SQUARE extends PHP_Token { } class PHP_Token_OPEN_TAG extends PHP_Token { } class PHP_Token_OPEN_TAG_WITH_ECHO extends PHP_Token { } class PHP_Token_OR_EQUAL extends PHP_Token { } class PHP_Token_PAAMAYIM_NEKUDOTAYIM extends PHP_Token { } class PHP_Token_PERCENT extends PHP_Token { } class PHP_Token_PIPE extends PHP_Token { } class PHP_Token_PLUS extends PHP_Token { } class PHP_Token_PLUS_EQUAL extends PHP_Token { } class PHP_Token_PRINT extends PHP_Token { } class PHP_Token_PRIVATE extends PHP_Token { } class PHP_Token_PROTECTED extends PHP_Token { } class PHP_Token_PUBLIC extends PHP_Token { } class PHP_Token_QUESTION_MARK extends PHP_Token { } class PHP_Token_REQUIRE extends PHP_Token_Includes { } class PHP_Token_REQUIRE_ONCE extends PHP_Token_Includes { } class PHP_Token_RETURN extends PHP_Token { } class PHP_Token_SEMICOLON extends PHP_Token { } class PHP_Token_SL extends PHP_Token { } class PHP_Token_SL_EQUAL extends PHP_Token { } class PHP_Token_SR extends PHP_Token { } class PHP_Token_SR_EQUAL extends PHP_Token { } class PHP_Token_START_HEREDOC extends PHP_Token { } class PHP_Token_STATIC extends PHP_Token { } class PHP_Token_STRING extends PHP_Token { } class PHP_Token_STRING_CAST extends PHP_Token { } class PHP_Token_STRING_VARNAME extends PHP_Token { } class PHP_Token_SWITCH extends PHP_Token { } class PHP_Token_THROW extends PHP_Token { } class PHP_Token_TILDE extends PHP_Token { } class PHP_Token_TRY extends PHP_Token { } class PHP_Token_UNSET extends PHP_Token { } class PHP_Token_UNSET_CAST extends PHP_Token { } class PHP_Token_USE extends PHP_Token { } class PHP_Token_USE_FUNCTION extends PHP_Token { } class PHP_Token_VAR extends PHP_Token { } class PHP_Token_VARIABLE extends PHP_Token { } class PHP_Token_WHILE extends PHP_Token { } class PHP_Token_WHITESPACE extends PHP_Token { } class PHP_Token_XOR_EQUAL extends PHP_Token { } class PHP_Token_HALT_COMPILER extends PHP_Token { } class PHP_Token_DIR extends PHP_Token { } class PHP_Token_GOTO extends PHP_Token { } class PHP_Token_NAMESPACE extends PHP_TokenWithScope { public function getName() { $tokens = $this->tokenStream->tokens(); $namespace = (string) $tokens[$this->id + 2]; for ($i = $this->id + 3;; $i += 2) { if (isset($tokens[$i]) && $tokens[$i] instanceof PHP_Token_NS_SEPARATOR) { $namespace .= '\\' . $tokens[$i + 1]; } else { break; } } return $namespace; } } class PHP_Token_NS_C extends PHP_Token { } class PHP_Token_NS_SEPARATOR extends PHP_Token { } class PHP_Token_CALLABLE extends PHP_Token { } class PHP_Token_INSTEADOF extends PHP_Token { } class PHP_Token_TRAIT extends PHP_Token_INTERFACE { } class PHP_Token_TRAIT_C extends PHP_Token { } class PHP_Token_FINALLY extends PHP_Token { } class PHP_Token_YIELD extends PHP_Token { } class PHP_Token_ELLIPSIS extends PHP_Token { } class PHP_Token_POW extends PHP_Token { } class PHP_Token_POW_EQUAL extends PHP_Token { } class PHP_Token_COALESCE extends PHP_Token { } class PHP_Token_SPACESHIP extends PHP_Token { } class PHP_Token_YIELD_FROM extends PHP_Token { } class PHP_Token_COALESCE_EQUAL extends PHP_Token { } class PHP_Token_FN extends PHP_Token { } =')) { $mode = \getenv('XDEBUG_MODE'); if ($mode === false) { $mode = \ini_get('xdebug.mode'); } if ($mode === false || !\in_array('coverage', \explode(',', $mode), true)) { throw new RuntimeException('XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set'); } } elseif (!\ini_get('xdebug.coverage_enable')) { throw new RuntimeException('xdebug.coverage_enable=On has to be set'); } if ($filter === null) { $filter = new Filter; } $this->filter = $filter; } public function start(bool $determineUnusedAndDead = true): void { if ($determineUnusedAndDead) { \xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); } else { \xdebug_start_code_coverage(); } } public function stop(): array { $data = \xdebug_get_code_coverage(); \xdebug_stop_code_coverage(); return $this->cleanup($data); } private function cleanup(array $data): array { foreach (\array_keys($data) as $file) { unset($data[$file][0]); if (!$this->filter->isFile($file)) { continue; } $numLines = $this->getNumberOfLinesInFile($file); foreach (\array_keys($data[$file]) as $line) { if ($line > $numLines) { unset($data[$file][$line]); } } } return $data; } private function getNumberOfLinesInFile(string $fileName): int { if (!isset($this->cacheNumLines[$fileName])) { $buffer = \file_get_contents($fileName); $lines = \substr_count($buffer, "\n"); if (\substr($buffer, -1) !== "\n") { $lines++; } $this->cacheNumLines[$fileName] = $lines; } return $this->cacheNumLines[$fileName]; } } $newFiles]); } } foreach ($sourceLines as $file => $lines) { foreach ($lines as $lineNo => $numExecuted) { $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; } } $fetchedLines = \array_merge($fetchedLines, $sourceLines); return $this->detectExecutedLines($fetchedLines, $dbgData); } private function detectExecutedLines(array $sourceLines, array $dbgData): array { foreach ($dbgData as $file => $coveredLines) { foreach ($coveredLines as $lineNo => $numExecuted) { if (isset($sourceLines[$file][$lineNo])) { $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; } } } return $sourceLines; } } 0) { $percent = ($a / $b) * 100; } if ($asString) { $format = $fixedWidth ? '%6.2F%%' : '%01.2F%%'; return \sprintf($format, $percent); } return $percent; } } selectDriver($filter); } $this->driver = $driver; $this->filter = $filter; $this->wizard = new Wizard; } public function getReport(): Directory { if ($this->report === null) { $this->report = (new Builder)->build($this); } return $this->report; } public function clear(): void { $this->isInitialized = false; $this->currentId = null; $this->data = []; $this->tests = []; $this->report = null; } public function filter(): Filter { return $this->filter; } public function getData(bool $raw = false): array { if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); } return $this->data; } public function setData(array $data): void { $this->data = $data; $this->report = null; } public function getTests(): array { return $this->tests; } public function setTests(array $tests): void { $this->tests = $tests; } public function start($id, bool $clear = false): void { if ($clear) { $this->clear(); } if ($this->isInitialized === false) { $this->initializeData(); } $this->currentId = $id; $this->driver->start($this->shouldCheckForDeadAndUnused); } public function stop(bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): array { if (!\is_array($linesToBeCovered) && $linesToBeCovered !== false) { throw InvalidArgumentException::create( 2, 'array or false' ); } $data = $this->driver->stop(); $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed, $ignoreForceCoversAnnotation); $this->currentId = null; return $data; } public function append(array $data, $id = null, bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): void { if ($id === null) { $id = $this->currentId; } if ($id === null) { throw new RuntimeException; } $this->applyWhitelistFilter($data); $this->applyIgnoredLinesFilter($data); $this->initializeFilesThatAreSeenTheFirstTime($data); if (!$append) { return; } if ($id !== 'UNCOVERED_FILES_FROM_WHITELIST') { $this->applyCoversAnnotationFilter( $data, $linesToBeCovered, $linesToBeUsed, $ignoreForceCoversAnnotation ); } if (empty($data)) { return; } $size = 'unknown'; $status = -1; if ($id instanceof TestCase) { $_size = $id->getSize(); if ($_size === Test::SMALL) { $size = 'small'; } elseif ($_size === Test::MEDIUM) { $size = 'medium'; } elseif ($_size === Test::LARGE) { $size = 'large'; } $status = $id->getStatus(); $id = \get_class($id) . '::' . $id->getName(); } elseif ($id instanceof PhptTestCase) { $size = 'large'; $id = $id->getName(); } $this->tests[$id] = ['size' => $size, 'status' => $status]; foreach ($data as $file => $lines) { if (!$this->filter->isFile($file)) { continue; } foreach ($lines as $k => $v) { if ($v === Driver::LINE_EXECUTED) { if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) { $this->data[$file][$k][] = $id; } } } } $this->report = null; } public function merge(self $that): void { $this->filter->setWhitelistedFiles( \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); foreach ($that->data as $file => $lines) { if (!isset($this->data[$file])) { if (!$this->filter->isFiltered($file)) { $this->data[$file] = $lines; } continue; } $compareLineNumbers = \array_unique( \array_merge( \array_keys($this->data[$file]), \array_keys($that->data[$file]) ) ); foreach ($compareLineNumbers as $line) { $thatPriority = $this->getLinePriority($that->data[$file], $line); $thisPriority = $this->getLinePriority($this->data[$file], $line); if ($thatPriority > $thisPriority) { $this->data[$file][$line] = $that->data[$file][$line]; } elseif ($thatPriority === $thisPriority && \is_array($this->data[$file][$line])) { $this->data[$file][$line] = \array_unique( \array_merge($this->data[$file][$line], $that->data[$file][$line]) ); } } } $this->tests = \array_merge($this->tests, $that->getTests()); $this->report = null; } public function setCacheTokens(bool $flag): void { $this->cacheTokens = $flag; } public function getCacheTokens(): bool { return $this->cacheTokens; } public function setCheckForUnintentionallyCoveredCode(bool $flag): void { $this->checkForUnintentionallyCoveredCode = $flag; } public function setForceCoversAnnotation(bool $flag): void { $this->forceCoversAnnotation = $flag; } public function setCheckForMissingCoversAnnotation(bool $flag): void { $this->checkForMissingCoversAnnotation = $flag; } public function setCheckForUnexecutedCoveredCode(bool $flag): void { $this->checkForUnexecutedCoveredCode = $flag; } public function setAddUncoveredFilesFromWhitelist(bool $flag): void { $this->addUncoveredFilesFromWhitelist = $flag; } public function setProcessUncoveredFilesFromWhitelist(bool $flag): void { $this->processUncoveredFilesFromWhitelist = $flag; } public function setDisableIgnoredLines(bool $flag): void { $this->disableIgnoredLines = $flag; } public function setIgnoreDeprecatedCode(bool $flag): void { $this->ignoreDeprecatedCode = $flag; } public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist): void { $this->unintentionallyCoveredSubclassesWhitelist = $whitelist; } private function getLinePriority($data, $line) { if (!\array_key_exists($line, $data)) { return 1; } if (\is_array($data[$line]) && \count($data[$line]) === 0) { return 2; } if ($data[$line] === null) { return 3; } return 4; } private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed, bool $ignoreForceCoversAnnotation): void { if ($linesToBeCovered === false || ($this->forceCoversAnnotation && empty($linesToBeCovered) && !$ignoreForceCoversAnnotation)) { if ($this->checkForMissingCoversAnnotation) { throw new MissingCoversAnnotationException; } $data = []; return; } if (empty($linesToBeCovered)) { return; } if ($this->checkForUnintentionallyCoveredCode && (!$this->currentId instanceof TestCase || (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { $this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed); } if ($this->checkForUnexecutedCoveredCode) { $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed); } $data = \array_intersect_key($data, $linesToBeCovered); foreach (\array_keys($data) as $filename) { $_linesToBeCovered = \array_flip($linesToBeCovered[$filename]); $data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered); } } private function applyWhitelistFilter(array &$data): void { foreach (\array_keys($data) as $filename) { if ($this->filter->isFiltered($filename)) { unset($data[$filename]); } } } private function applyIgnoredLinesFilter(array &$data): void { foreach (\array_keys($data) as $filename) { if (!$this->filter->isFile($filename)) { continue; } foreach ($this->getLinesToBeIgnored($filename) as $line) { unset($data[$filename][$line]); } } } private function initializeFilesThatAreSeenTheFirstTime(array $data): void { foreach ($data as $file => $lines) { if (!isset($this->data[$file]) && $this->filter->isFile($file)) { $this->data[$file] = []; foreach ($lines as $k => $v) { $this->data[$file][$k] = $v === -2 ? null : []; } } } } private function addUncoveredFilesFromWhitelist(): void { $data = []; $uncoveredFiles = \array_diff( $this->filter->getWhitelist(), \array_keys($this->data) ); foreach ($uncoveredFiles as $uncoveredFile) { if (!\file_exists($uncoveredFile)) { continue; } $data[$uncoveredFile] = []; $lines = \count(\file($uncoveredFile)); for ($i = 1; $i <= $lines; $i++) { $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED; } } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); } private function getLinesToBeIgnored(string $fileName): array { if (isset($this->ignoredLines[$fileName])) { return $this->ignoredLines[$fileName]; } try { return $this->getLinesToBeIgnoredInner($fileName); } catch (\OutOfBoundsException $e) { return []; } } private function getLinesToBeIgnoredInner(string $fileName): array { $this->ignoredLines[$fileName] = []; $lines = \file($fileName); foreach ($lines as $index => $line) { if (!\trim($line)) { $this->ignoredLines[$fileName][] = $index + 1; } } if ($this->cacheTokens) { $tokens = \PHP_Token_Stream_CachingFactory::get($fileName); } else { $tokens = new \PHP_Token_Stream($fileName); } foreach ($tokens->getInterfaces() as $interface) { $interfaceStartLine = $interface['startLine']; $interfaceEndLine = $interface['endLine']; foreach (\range($interfaceStartLine, $interfaceEndLine) as $line) { $this->ignoredLines[$fileName][] = $line; } } foreach (\array_merge($tokens->getClasses(), $tokens->getTraits()) as $classOrTrait) { $classOrTraitStartLine = $classOrTrait['startLine']; $classOrTraitEndLine = $classOrTrait['endLine']; if (empty($classOrTrait['methods'])) { foreach (\range($classOrTraitStartLine, $classOrTraitEndLine) as $line) { $this->ignoredLines[$fileName][] = $line; } continue; } $firstMethod = \array_shift($classOrTrait['methods']); $firstMethodStartLine = $firstMethod['startLine']; $lastMethodEndLine = $firstMethod['endLine']; do { $lastMethod = \array_pop($classOrTrait['methods']); } while ($lastMethod !== null && 0 === \strpos($lastMethod['signature'], 'anonymousFunction')); if ($lastMethod !== null) { $lastMethodEndLine = $lastMethod['endLine']; } foreach (\range($classOrTraitStartLine, $firstMethodStartLine) as $line) { $this->ignoredLines[$fileName][] = $line; } foreach (\range($lastMethodEndLine + 1, $classOrTraitEndLine) as $line) { $this->ignoredLines[$fileName][] = $line; } } if ($this->disableIgnoredLines) { $this->ignoredLines[$fileName] = \array_unique($this->ignoredLines[$fileName]); \sort($this->ignoredLines[$fileName]); return $this->ignoredLines[$fileName]; } $ignore = false; $stop = false; foreach ($tokens->tokens() as $token) { switch (\get_class($token)) { case \PHP_Token_COMMENT::class: case \PHP_Token_DOC_COMMENT::class: $_token = \trim((string) $token); $_line = \trim($lines[$token->getLine() - 1]); if ($_token === '// @codeCoverageIgnore' || $_token === '//@codeCoverageIgnore') { $ignore = true; $stop = true; } elseif ($_token === '// @codeCoverageIgnoreStart' || $_token === '//@codeCoverageIgnoreStart') { $ignore = true; } elseif ($_token === '// @codeCoverageIgnoreEnd' || $_token === '//@codeCoverageIgnoreEnd') { $stop = true; } if (!$ignore) { $start = $token->getLine(); $end = $start + \substr_count((string) $token, "\n"); if (0 !== \strpos($_token, $_line)) { $start++; } for ($i = $start; $i < $end; $i++) { $this->ignoredLines[$fileName][] = $i; } if (isset($lines[$i - 1]) && 0 === \strpos($_token, '/*') && '*/' === \substr(\trim($lines[$i - 1]), -2)) { $this->ignoredLines[$fileName][] = $i; } } break; case \PHP_Token_INTERFACE::class: case \PHP_Token_TRAIT::class: case \PHP_Token_CLASS::class: case \PHP_Token_FUNCTION::class: $docblock = (string) $token->getDocblock(); $this->ignoredLines[$fileName][] = $token->getLine(); if (\strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && \strpos($docblock, '@deprecated'))) { $endLine = $token->getEndLine(); for ($i = $token->getLine(); $i <= $endLine; $i++) { $this->ignoredLines[$fileName][] = $i; } } break; case \PHP_Token_NAMESPACE::class: $this->ignoredLines[$fileName][] = $token->getEndLine(); case \PHP_Token_DECLARE::class: case \PHP_Token_OPEN_TAG::class: case \PHP_Token_CLOSE_TAG::class: case \PHP_Token_USE::class: case \PHP_Token_USE_FUNCTION::class: $this->ignoredLines[$fileName][] = $token->getLine(); break; } if ($ignore) { $this->ignoredLines[$fileName][] = $token->getLine(); if ($stop) { $ignore = false; $stop = false; } } } $this->ignoredLines[$fileName][] = \count($lines) + 1; $this->ignoredLines[$fileName] = \array_unique( $this->ignoredLines[$fileName] ); $this->ignoredLines[$fileName] = \array_unique($this->ignoredLines[$fileName]); \sort($this->ignoredLines[$fileName]); return $this->ignoredLines[$fileName]; } private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed): void { $allowedLines = $this->getAllowedLines( $linesToBeCovered, $linesToBeUsed ); $unintentionallyCoveredUnits = []; foreach ($data as $file => $_data) { foreach ($_data as $line => $flag) { if ($flag === 1 && !isset($allowedLines[$file][$line])) { $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); } } } $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); if (!empty($unintentionallyCoveredUnits)) { throw new UnintentionallyCoveredCodeException( $unintentionallyCoveredUnits ); } } private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed): void { $executedCodeUnits = $this->coverageToCodeUnits($data); $message = ''; foreach ($this->linesToCodeUnits($linesToBeCovered) as $codeUnit) { if (!\in_array($codeUnit, $executedCodeUnits)) { $message .= \sprintf( '- %s is expected to be executed (@covers) but was not executed' . "\n", $codeUnit ); } } foreach ($this->linesToCodeUnits($linesToBeUsed) as $codeUnit) { if (!\in_array($codeUnit, $executedCodeUnits)) { $message .= \sprintf( '- %s is expected to be executed (@uses) but was not executed' . "\n", $codeUnit ); } } if (!empty($message)) { throw new CoveredCodeNotExecutedException($message); } } private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): array { $allowedLines = []; foreach (\array_keys($linesToBeCovered) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = []; } $allowedLines[$file] = \array_merge( $allowedLines[$file], $linesToBeCovered[$file] ); } foreach (\array_keys($linesToBeUsed) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = []; } $allowedLines[$file] = \array_merge( $allowedLines[$file], $linesToBeUsed[$file] ); } foreach (\array_keys($allowedLines) as $file) { $allowedLines[$file] = \array_flip( \array_unique($allowedLines[$file]) ); } return $allowedLines; } private function selectDriver(Filter $filter): Driver { $runtime = new Runtime; if ($runtime->hasPHPDBGCodeCoverage()) { return new PHPDBG; } if ($runtime->hasPCOV()) { return new PCOV; } if ($runtime->hasXdebug()) { return new Xdebug($filter); } throw new RuntimeException('No code coverage driver available'); } private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits): array { $unintentionallyCoveredUnits = \array_unique($unintentionallyCoveredUnits); \sort($unintentionallyCoveredUnits); foreach (\array_keys($unintentionallyCoveredUnits) as $k => $v) { $unit = \explode('::', $unintentionallyCoveredUnits[$k]); if (\count($unit) !== 2) { continue; } $class = new \ReflectionClass($unit[0]); foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) { if ($class->isSubclassOf($whitelisted)) { unset($unintentionallyCoveredUnits[$k]); break; } } } return \array_values($unintentionallyCoveredUnits); } private function initializeData(): void { $this->isInitialized = true; if ($this->processUncoveredFilesFromWhitelist) { $this->shouldCheckForDeadAndUnused = false; $this->driver->start(); foreach ($this->filter->getWhitelist() as $file) { if ($this->filter->isFile($file)) { include_once $file; } } $data = []; foreach ($this->driver->stop() as $file => $fileCoverage) { if ($this->filter->isFiltered($file)) { continue; } foreach (\array_keys($fileCoverage) as $key) { if ($fileCoverage[$key] === Driver::LINE_EXECUTED) { $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED; } } $data[$file] = $fileCoverage; } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); } } private function coverageToCodeUnits(array $data): array { $codeUnits = []; foreach ($data as $filename => $lines) { foreach ($lines as $line => $flag) { if ($flag === 1) { $codeUnits[] = $this->wizard->lookup($filename, $line); } } } return \array_unique($codeUnits); } private function linesToCodeUnits(array $data): array { $codeUnits = []; foreach ($data as $filename => $lines) { foreach ($lines as $line) { $codeUnits[] = $this->wizard->lookup($filename, $line); } } return \array_unique($codeUnits); } } numFiles === -1) { $this->numFiles = 0; foreach ($this->children as $child) { $this->numFiles += \count($child); } } return $this->numFiles; } public function getIterator(): \RecursiveIteratorIterator { return new \RecursiveIteratorIterator( new Iterator($this), \RecursiveIteratorIterator::SELF_FIRST ); } public function addDirectory(string $name): self { $directory = new self($name, $this); $this->children[] = $directory; $this->directories[] = &$this->children[\count($this->children) - 1]; return $directory; } public function addFile(string $name, array $coverageData, array $testData, bool $cacheTokens): File { $file = new File($name, $this, $coverageData, $testData, $cacheTokens); $this->children[] = $file; $this->files[] = &$this->children[\count($this->children) - 1]; $this->numExecutableLines = -1; $this->numExecutedLines = -1; return $file; } public function getDirectories(): array { return $this->directories; } public function getFiles(): array { return $this->files; } public function getChildNodes(): array { return $this->children; } public function getClasses(): array { if ($this->classes === null) { $this->classes = []; foreach ($this->children as $child) { $this->classes = \array_merge( $this->classes, $child->getClasses() ); } } return $this->classes; } public function getTraits(): array { if ($this->traits === null) { $this->traits = []; foreach ($this->children as $child) { $this->traits = \array_merge( $this->traits, $child->getTraits() ); } } return $this->traits; } public function getFunctions(): array { if ($this->functions === null) { $this->functions = []; foreach ($this->children as $child) { $this->functions = \array_merge( $this->functions, $child->getFunctions() ); } } return $this->functions; } public function getLinesOfCode(): array { if ($this->linesOfCode === null) { $this->linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0]; foreach ($this->children as $child) { $linesOfCode = $child->getLinesOfCode(); $this->linesOfCode['loc'] += $linesOfCode['loc']; $this->linesOfCode['cloc'] += $linesOfCode['cloc']; $this->linesOfCode['ncloc'] += $linesOfCode['ncloc']; } } return $this->linesOfCode; } public function getNumExecutableLines(): int { if ($this->numExecutableLines === -1) { $this->numExecutableLines = 0; foreach ($this->children as $child) { $this->numExecutableLines += $child->getNumExecutableLines(); } } return $this->numExecutableLines; } public function getNumExecutedLines(): int { if ($this->numExecutedLines === -1) { $this->numExecutedLines = 0; foreach ($this->children as $child) { $this->numExecutedLines += $child->getNumExecutedLines(); } } return $this->numExecutedLines; } public function getNumClasses(): int { if ($this->numClasses === -1) { $this->numClasses = 0; foreach ($this->children as $child) { $this->numClasses += $child->getNumClasses(); } } return $this->numClasses; } public function getNumTestedClasses(): int { if ($this->numTestedClasses === -1) { $this->numTestedClasses = 0; foreach ($this->children as $child) { $this->numTestedClasses += $child->getNumTestedClasses(); } } return $this->numTestedClasses; } public function getNumTraits(): int { if ($this->numTraits === -1) { $this->numTraits = 0; foreach ($this->children as $child) { $this->numTraits += $child->getNumTraits(); } } return $this->numTraits; } public function getNumTestedTraits(): int { if ($this->numTestedTraits === -1) { $this->numTestedTraits = 0; foreach ($this->children as $child) { $this->numTestedTraits += $child->getNumTestedTraits(); } } return $this->numTestedTraits; } public function getNumMethods(): int { if ($this->numMethods === -1) { $this->numMethods = 0; foreach ($this->children as $child) { $this->numMethods += $child->getNumMethods(); } } return $this->numMethods; } public function getNumTestedMethods(): int { if ($this->numTestedMethods === -1) { $this->numTestedMethods = 0; foreach ($this->children as $child) { $this->numTestedMethods += $child->getNumTestedMethods(); } } return $this->numTestedMethods; } public function getNumFunctions(): int { if ($this->numFunctions === -1) { $this->numFunctions = 0; foreach ($this->children as $child) { $this->numFunctions += $child->getNumFunctions(); } } return $this->numFunctions; } public function getNumTestedFunctions(): int { if ($this->numTestedFunctions === -1) { $this->numTestedFunctions = 0; foreach ($this->children as $child) { $this->numTestedFunctions += $child->getNumTestedFunctions(); } } return $this->numTestedFunctions; } } nodes = $node->getChildNodes(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->nodes); } public function key(): int { return $this->position; } public function current(): AbstractNode { return $this->valid() ? $this->nodes[$this->position] : null; } public function next(): void { $this->position++; } public function getChildren(): self { return new self($this->nodes[$this->position]); } public function hasChildren(): bool { return $this->nodes[$this->position] instanceof Directory; } } getData(); $commonPath = $this->reducePaths($files); $root = new Directory( $commonPath, null ); $this->addItems( $root, $this->buildDirectoryStructure($files), $coverage->getTests(), $coverage->getCacheTokens() ); return $root; } private function addItems(Directory $root, array $items, array $tests, bool $cacheTokens): void { foreach ($items as $key => $value) { $key = (string) $key; if (\substr($key, -2) === '/f') { $key = \substr($key, 0, -2); if (\file_exists($root->getPath() . \DIRECTORY_SEPARATOR . $key)) { $root->addFile($key, $value, $tests, $cacheTokens); } } else { $child = $root->addDirectory($key); $this->addItems($child, $value, $tests, $cacheTokens); } } } private function buildDirectoryStructure(array $files): array { $result = []; foreach ($files as $path => $file) { $path = \explode(\DIRECTORY_SEPARATOR, $path); $pointer = &$result; $max = \count($path); for ($i = 0; $i < $max; $i++) { $type = ''; if ($i === ($max - 1)) { $type = '/f'; } $pointer = &$pointer[$path[$i] . $type]; } $pointer = $file; } return $result; } private function reducePaths(array &$files): string { if (empty($files)) { return '.'; } $commonPath = ''; $paths = \array_keys($files); if (\count($files) === 1) { $commonPath = \dirname($paths[0]) . \DIRECTORY_SEPARATOR; $files[\basename($paths[0])] = $files[$paths[0]]; unset($files[$paths[0]]); return $commonPath; } $max = \count($paths); for ($i = 0; $i < $max; $i++) { if (\strpos($paths[$i], 'phar://') === 0) { $paths[$i] = \substr($paths[$i], 7); $paths[$i] = \str_replace('/', \DIRECTORY_SEPARATOR, $paths[$i]); } $paths[$i] = \explode(\DIRECTORY_SEPARATOR, $paths[$i]); if (empty($paths[$i][0])) { $paths[$i][0] = \DIRECTORY_SEPARATOR; } } $done = false; $max = \count($paths); while (!$done) { for ($i = 0; $i < $max - 1; $i++) { if (!isset($paths[$i][0]) || !isset($paths[$i + 1][0]) || $paths[$i][0] !== $paths[$i + 1][0]) { $done = true; break; } } if (!$done) { $commonPath .= $paths[0][0]; if ($paths[0][0] !== \DIRECTORY_SEPARATOR) { $commonPath .= \DIRECTORY_SEPARATOR; } for ($i = 0; $i < $max; $i++) { \array_shift($paths[$i]); } } } $original = \array_keys($files); $max = \count($original); for ($i = 0; $i < $max; $i++) { $files[\implode(\DIRECTORY_SEPARATOR, $paths[$i])] = $files[$original[$i]]; unset($files[$original[$i]]); } \ksort($files); return \substr($commonPath, 0, -1); } } name = $name; $this->parent = $parent; } public function getName(): string { return $this->name; } public function getId(): string { if ($this->id === null) { $parent = $this->getParent(); if ($parent === null) { $this->id = 'index'; } else { $parentId = $parent->getId(); if ($parentId === 'index') { $this->id = \str_replace(':', '_', $this->name); } else { $this->id = $parentId . '/' . $this->name; } } } return $this->id; } public function getPath(): string { if ($this->path === null) { if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) { $this->path = $this->name; } else { $this->path = $this->parent->getPath() . \DIRECTORY_SEPARATOR . $this->name; } } return $this->path; } public function getPathAsArray(): array { if ($this->pathArray === null) { if ($this->parent === null) { $this->pathArray = []; } else { $this->pathArray = $this->parent->getPathAsArray(); } $this->pathArray[] = $this; } return $this->pathArray; } public function getParent(): ?self { return $this->parent; } public function getTestedClassesPercent(bool $asString = true) { return Util::percent( $this->getNumTestedClasses(), $this->getNumClasses(), $asString ); } public function getTestedTraitsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedTraits(), $this->getNumTraits(), $asString ); } public function getTestedClassesAndTraitsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedClassesAndTraits(), $this->getNumClassesAndTraits(), $asString ); } public function getTestedFunctionsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedFunctions(), $this->getNumFunctions(), $asString ); } public function getTestedMethodsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedMethods(), $this->getNumMethods(), $asString ); } public function getTestedFunctionsAndMethodsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedFunctionsAndMethods(), $this->getNumFunctionsAndMethods(), $asString ); } public function getLineExecutedPercent(bool $asString = true) { return Util::percent( $this->getNumExecutedLines(), $this->getNumExecutableLines(), $asString ); } public function getNumClassesAndTraits(): int { return $this->getNumClasses() + $this->getNumTraits(); } public function getNumTestedClassesAndTraits(): int { return $this->getNumTestedClasses() + $this->getNumTestedTraits(); } public function getClassesAndTraits(): array { return \array_merge($this->getClasses(), $this->getTraits()); } public function getNumFunctionsAndMethods(): int { return $this->getNumFunctions() + $this->getNumMethods(); } public function getNumTestedFunctionsAndMethods(): int { return $this->getNumTestedFunctions() + $this->getNumTestedMethods(); } public function getFunctionsAndMethods(): array { return \array_merge($this->getFunctions(), $this->getMethods()); } abstract public function getClasses(): array; abstract public function getTraits(): array; abstract public function getFunctions(): array; abstract public function getLinesOfCode(): array; abstract public function getNumExecutableLines(): int; abstract public function getNumExecutedLines(): int; abstract public function getNumClasses(): int; abstract public function getNumTestedClasses(): int; abstract public function getNumTraits(): int; abstract public function getNumTestedTraits(): int; abstract public function getNumMethods(): int; abstract public function getNumTestedMethods(): int; abstract public function getNumFunctions(): int; abstract public function getNumTestedFunctions(): int; } coverageData = $coverageData; $this->testData = $testData; $this->cacheTokens = $cacheTokens; $this->calculateStatistics(); } public function count(): int { return 1; } public function getCoverageData(): array { return $this->coverageData; } public function getTestData(): array { return $this->testData; } public function getClasses(): array { return $this->classes; } public function getTraits(): array { return $this->traits; } public function getFunctions(): array { return $this->functions; } public function getLinesOfCode(): array { return $this->linesOfCode; } public function getNumExecutableLines(): int { return $this->numExecutableLines; } public function getNumExecutedLines(): int { return $this->numExecutedLines; } public function getNumClasses(): int { if ($this->numClasses === null) { $this->numClasses = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numClasses++; continue 2; } } } } return $this->numClasses; } public function getNumTestedClasses(): int { return $this->numTestedClasses; } public function getNumTraits(): int { if ($this->numTraits === null) { $this->numTraits = 0; foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numTraits++; continue 2; } } } } return $this->numTraits; } public function getNumTestedTraits(): int { return $this->numTestedTraits; } public function getNumMethods(): int { if ($this->numMethods === null) { $this->numMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } } return $this->numMethods; } public function getNumTestedMethods(): int { if ($this->numTestedMethods === null) { $this->numTestedMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] === 100) { $this->numTestedMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] === 100) { $this->numTestedMethods++; } } } } return $this->numTestedMethods; } public function getNumFunctions(): int { return \count($this->functions); } public function getNumTestedFunctions(): int { if ($this->numTestedFunctions === null) { $this->numTestedFunctions = 0; foreach ($this->functions as $function) { if ($function['executableLines'] > 0 && $function['coverage'] === 100) { $this->numTestedFunctions++; } } } return $this->numTestedFunctions; } private function calculateStatistics(): void { if ($this->cacheTokens) { $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath()); } else { $tokens = new \PHP_Token_Stream($this->getPath()); } $this->linesOfCode = $tokens->getLinesOfCode(); foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = []; } try { $this->processClasses($tokens); $this->processTraits($tokens); $this->processFunctions($tokens); } catch (\OutOfBoundsException $e) { } unset($tokens); foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) { if (isset($this->coverageData[$lineNumber])) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { $codeUnit['executableLines']++; } unset($codeUnit); $this->numExecutableLines++; if (\count($this->coverageData[$lineNumber]) > 0) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { $codeUnit['executedLines']++; } unset($codeUnit); $this->numExecutedLines++; } } } foreach ($this->traits as &$trait) { foreach ($trait['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $trait['ccn'] += $method['ccn']; } unset($method); if ($trait['executableLines'] > 0) { $trait['coverage'] = ($trait['executedLines'] / $trait['executableLines']) * 100; if ($trait['coverage'] === 100) { $this->numTestedClasses++; } } else { $trait['coverage'] = 100; } $trait['crap'] = $this->crap( $trait['ccn'], $trait['coverage'] ); } unset($trait); foreach ($this->classes as &$class) { foreach ($class['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $class['ccn'] += $method['ccn']; } unset($method); if ($class['executableLines'] > 0) { $class['coverage'] = ($class['executedLines'] / $class['executableLines']) * 100; if ($class['coverage'] === 100) { $this->numTestedClasses++; } } else { $class['coverage'] = 100; } $class['crap'] = $this->crap( $class['ccn'], $class['coverage'] ); } unset($class); foreach ($this->functions as &$function) { if ($function['executableLines'] > 0) { $function['coverage'] = ($function['executedLines'] / $function['executableLines']) * 100; } else { $function['coverage'] = 100; } if ($function['coverage'] === 100) { $this->numTestedFunctions++; } $function['crap'] = $this->crap( $function['ccn'], $function['coverage'] ); } } private function processClasses(\PHP_Token_Stream $tokens): void { $classes = $tokens->getClasses(); $link = $this->getId() . '.html#'; foreach ($classes as $className => $class) { if (\strpos($className, 'anonymous') === 0) { continue; } if (!empty($class['package']['namespace'])) { $className = $class['package']['namespace'] . '\\' . $className; } $this->classes[$className] = [ 'className' => $className, 'methods' => [], 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $class['package'], 'link' => $link . $class['startLine'], ]; foreach ($class['methods'] as $methodName => $method) { if (\strpos($methodName, 'anonymous') === 0) { continue; } $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); foreach (\range($method['startLine'], $method['endLine']) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->classes[$className], &$this->classes[$className]['methods'][$methodName], ]; } } } } private function processTraits(\PHP_Token_Stream $tokens): void { $traits = $tokens->getTraits(); $link = $this->getId() . '.html#'; foreach ($traits as $traitName => $trait) { $this->traits[$traitName] = [ 'traitName' => $traitName, 'methods' => [], 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $trait['package'], 'link' => $link . $trait['startLine'], ]; foreach ($trait['methods'] as $methodName => $method) { if (\strpos($methodName, 'anonymous') === 0) { continue; } $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); foreach (\range($method['startLine'], $method['endLine']) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->traits[$traitName], &$this->traits[$traitName]['methods'][$methodName], ]; } } } } private function processFunctions(\PHP_Token_Stream $tokens): void { $functions = $tokens->getFunctions(); $link = $this->getId() . '.html#'; foreach ($functions as $functionName => $function) { if (\strpos($functionName, 'anonymous') === 0) { continue; } $this->functions[$functionName] = [ 'functionName' => $functionName, 'signature' => $function['signature'], 'startLine' => $function['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $function['startLine'], ]; foreach (\range($function['startLine'], $function['endLine']) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; } } } private function crap(int $ccn, float $coverage): string { if ($coverage === 0.0) { return (string) ($ccn ** 2 + $ccn); } if ($coverage >= 95) { return (string) $ccn; } return \sprintf( '%01.2F', $ccn ** 2 * (1 - $coverage / 100) ** 3 + $ccn ); } private function newMethod(string $methodName, array $method, string $link): array { return [ 'methodName' => $methodName, 'visibility' => $method['visibility'], 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine'], ]; } } getVersion(); } return self::$version; } } getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->addFileToWhitelist($file); } } public function addFileToWhitelist(string $filename): void { $filename = \realpath($filename); if (!$filename) { return; } $this->whitelistedFiles[$filename] = true; } public function addFilesToWhitelist(array $files): void { foreach ($files as $file) { $this->addFileToWhitelist($file); } } public function removeDirectoryFromWhitelist(string $directory, string $suffix = '.php', string $prefix = ''): void { $facade = new FileIteratorFacade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->removeFileFromWhitelist($file); } } public function removeFileFromWhitelist(string $filename): void { $filename = \realpath($filename); if (!$filename || !isset($this->whitelistedFiles[$filename])) { return; } unset($this->whitelistedFiles[$filename]); } public function isFile(string $filename): bool { if (isset($this->isFileCallsCache[$filename])) { return $this->isFileCallsCache[$filename]; } if ($filename === '-' || \strpos($filename, 'vfs://') === 0 || \strpos($filename, 'xdebug://debug-eval') !== false || \strpos($filename, 'eval()\'d code') !== false || \strpos($filename, 'runtime-created function') !== false || \strpos($filename, 'runkit created function') !== false || \strpos($filename, 'assert code') !== false || \strpos($filename, 'regexp code') !== false || \strpos($filename, 'Standard input code') !== false) { $isFile = false; } else { $isFile = \file_exists($filename); } $this->isFileCallsCache[$filename] = $isFile; return $isFile; } public function isFiltered(string $filename): bool { if (!$this->isFile($filename)) { return true; } return !isset($this->whitelistedFiles[$filename]); } public function getWhitelist(): array { return \array_keys($this->whitelistedFiles); } public function hasWhitelist(): bool { return !empty($this->whitelistedFiles); } public function getWhitelistedFiles(): array { return $this->whitelistedFiles; } public function setWhitelistedFiles(array $whitelistedFiles): void { $this->whitelistedFiles = $whitelistedFiles; } } lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->showUncoveredFiles = $showUncoveredFiles; $this->showOnlySummary = $showOnlySummary; } public function process(CodeCoverage $coverage, bool $showColors = false): string { $output = \PHP_EOL . \PHP_EOL; $report = $coverage->getReport(); $colors = [ 'header' => '', 'classes' => '', 'methods' => '', 'lines' => '', 'reset' => '', 'eol' => '', ]; if ($showColors) { $colors['classes'] = $this->getCoverageColor( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $colors['methods'] = $this->getCoverageColor( $report->getNumTestedMethods(), $report->getNumMethods() ); $colors['lines'] = $this->getCoverageColor( $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $colors['reset'] = self::COLOR_RESET; $colors['header'] = self::COLOR_HEADER; $colors['eol'] = self::COLOR_EOL; } $classes = \sprintf( ' Classes: %6s (%d/%d)', Util::percent( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits(), true ), $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $methods = \sprintf( ' Methods: %6s (%d/%d)', Util::percent( $report->getNumTestedMethods(), $report->getNumMethods(), true ), $report->getNumTestedMethods(), $report->getNumMethods() ); $lines = \sprintf( ' Lines: %6s (%d/%d)', Util::percent( $report->getNumExecutedLines(), $report->getNumExecutableLines(), true ), $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $padding = \max(\array_map('strlen', [$classes, $methods, $lines])); if ($this->showOnlySummary) { $title = 'Code Coverage Report Summary:'; $padding = \max($padding, \strlen($title)); $output .= $this->format($colors['header'], $padding, $title); } else { $date = \date(' Y-m-d H:i:s', $_SERVER['REQUEST_TIME']); $title = 'Code Coverage Report:'; $output .= $this->format($colors['header'], $padding, $title); $output .= $this->format($colors['header'], $padding, $date); $output .= $this->format($colors['header'], $padding, ''); $output .= $this->format($colors['header'], $padding, ' Summary:'); } $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); if ($this->showOnlySummary) { return $output . \PHP_EOL; } $classCoverage = []; foreach ($report as $item) { if (!$item instanceof File) { continue; } $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } } $namespace = ''; if (!empty($class['package']['namespace'])) { $namespace = '\\' . $class['package']['namespace'] . '::'; } elseif (!empty($class['package']['fullPackage'])) { $namespace = '@' . $class['package']['fullPackage'] . '::'; } $classCoverage[$namespace . $className] = [ 'namespace' => $namespace, 'className ' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, 'statementsCovered' => $coveredClassStatements, 'statementCount' => $classStatements, ]; } } \ksort($classCoverage); $methodColor = ''; $linesColor = ''; $resetColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { if ($showColors) { $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); $resetColor = $colors['reset']; } $output .= \PHP_EOL . $fullQualifiedPath . \PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor; } } return $output . \PHP_EOL; } private function getCoverageColor(int $numberOfCoveredElements, int $totalNumberOfElements): string { $coverage = Util::percent( $numberOfCoveredElements, $totalNumberOfElements ); if ($coverage >= $this->highLowerBound) { return self::COLOR_GREEN; } if ($coverage > $this->lowUpperBound) { return self::COLOR_YELLOW; } return self::COLOR_RED; } private function printCoverageCounts(int $numberOfCoveredElements, int $totalNumberOfElements, int $precision): string { $format = '%' . $precision . 's'; return Util::percent( $numberOfCoveredElements, $totalNumberOfElements, true, true ) . ' (' . \sprintf($format, $numberOfCoveredElements) . '/' . \sprintf($format, $totalNumberOfElements) . ')'; } private function format($color, $padding, $string): string { $reset = $color ? self::COLOR_RESET : ''; return $color . \str_pad($string, $padding) . $reset . \PHP_EOL; } } filter(); $buffer = \sprintf( 'setData(%s); $coverage->setTests(%s); $filter = $coverage->filter(); $filter->setWhitelistedFiles(%s); return $coverage;', \var_export($coverage->getData(true), true), \var_export($coverage->getTests(), true), \var_export($filter->getWhitelistedFiles(), true) ); if ($target !== null) { if (!$this->createDirectory(\dirname($target))) { throw new \RuntimeException(\sprintf('Directory "%s" was not created', \dirname($target))); } if (@\file_put_contents($target, $buffer) === false) { throw new RuntimeException( \sprintf( 'Could not write to "%s', $target ) ); } } return $buffer; } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } context = $context; } public function setSourceCode(string $source): void { $context = $this->context; $tokens = (new Tokenizer())->parse($source); $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); $context->parentNode->replaceChild( $context->ownerDocument->importNode($srcDom->documentElement, true), $context ); } } contextNode = $contextNode; } public function setRuntimeInformation(Runtime $runtime): void { $runtimeNode = $this->getNodeByName('runtime'); $runtimeNode->setAttribute('name', $runtime->getName()); $runtimeNode->setAttribute('version', $runtime->getVersion()); $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); $driverNode = $this->getNodeByName('driver'); if ($runtime->hasPHPDBGCodeCoverage()) { $driverNode->setAttribute('name', 'phpdbg'); $driverNode->setAttribute('version', \constant('PHPDBG_VERSION')); } if ($runtime->hasXdebug()) { $driverNode->setAttribute('name', 'xdebug'); $driverNode->setAttribute('version', \phpversion('xdebug')); } if ($runtime->hasPCOV()) { $driverNode->setAttribute('name', 'pcov'); $driverNode->setAttribute('version', \phpversion('pcov')); } } public function setBuildTime(\DateTime $date): void { $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); } public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion): void { $this->contextNode->setAttribute('phpunit', $phpUnitVersion); $this->contextNode->setAttribute('coverage', $coverageVersion); } private function getNodeByName(string $name): \DOMElement { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', $name )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', $name ) ); } return $node; } } container = $container; $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'lines' ); $this->methodsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'methods' ); $this->functionsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'functions' ); $this->classesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'classes' ); $this->traitsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'traits' ); $container->appendChild($this->linesNode); $container->appendChild($this->methodsNode); $container->appendChild($this->functionsNode); $container->appendChild($this->classesNode); $container->appendChild($this->traitsNode); } public function getContainer(): \DOMNode { return $this->container; } public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed): void { $this->linesNode->setAttribute('total', (string) $loc); $this->linesNode->setAttribute('comments', (string) $cloc); $this->linesNode->setAttribute('code', (string) $ncloc); $this->linesNode->setAttribute('executable', (string) $executable); $this->linesNode->setAttribute('executed', (string) $executed); $this->linesNode->setAttribute( 'percent', $executable === 0 ? '0' : \sprintf('%01.2F', Util::percent($executed, $executable)) ); } public function setNumClasses(int $count, int $tested): void { $this->classesNode->setAttribute('count', (string) $count); $this->classesNode->setAttribute('tested', (string) $tested); $this->classesNode->setAttribute( 'percent', $count === 0 ? '0' : \sprintf('%01.2F', Util::percent($tested, $count)) ); } public function setNumTraits(int $count, int $tested): void { $this->traitsNode->setAttribute('count', (string) $count); $this->traitsNode->setAttribute('tested', (string) $tested); $this->traitsNode->setAttribute( 'percent', $count === 0 ? '0' : \sprintf('%01.2F', Util::percent($tested, $count)) ); } public function setNumMethods(int $count, int $tested): void { $this->methodsNode->setAttribute('count', (string) $count); $this->methodsNode->setAttribute('tested', (string) $tested); $this->methodsNode->setAttribute( 'percent', $count === 0 ? '0' : \sprintf('%01.2F', Util::percent($tested, $count)) ); } public function setNumFunctions(int $count, int $tested): void { $this->functionsNode->setAttribute('count', (string) $count); $this->functionsNode->setAttribute('tested', (string) $tested); $this->functionsNode->setAttribute( 'percent', $count === 0 ? '0' : \sprintf('%01.2F', Util::percent($tested, $count)) ); } } setContextNode($context); } public function getDom(): \DOMDocument { return $this->dom; } public function getTotals(): Totals { $totalsContainer = $this->getContextNode()->firstChild; if (!$totalsContainer) { $totalsContainer = $this->getContextNode()->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function addDirectory(string $name): Directory { $dirNode = $this->getDom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'directory' ); $dirNode->setAttribute('name', $name); $this->getContextNode()->appendChild($dirNode); return new Directory($dirNode); } public function addFile(string $name, string $href): File { $fileNode = $this->getDom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'file' ); $fileNode->setAttribute('name', $name); $fileNode->setAttribute('href', $href); $this->getContextNode()->appendChild($fileNode); return new File($fileNode); } protected function setContextNode(\DOMElement $context): void { $this->dom = $context->ownerDocument; $this->contextNode = $context; } protected function getContextNode(): \DOMElement { return $this->contextNode; } } loadXML(''); $contextNode = $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'file' )->item(0); parent::__construct($contextNode); $this->setName($name); } public function asDom(): \DOMDocument { return $this->getDomDocument(); } public function getFunctionObject($name): Method { $node = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'function' ) ); return new Method($node, $name); } public function getClassObject($name): Unit { return $this->getUnitObject('class', $name); } public function getTraitObject($name): Unit { return $this->getUnitObject('trait', $name); } public function getSource(): Source { $source = $this->getContextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'source' )->item(0); if (!$source) { $source = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'source' ) ); } return new Source($source); } private function setName($name): void { $this->getContextNode()->setAttribute('name', \basename($name)); $this->getContextNode()->setAttribute('path', \dirname($name)); } private function getUnitObject($tagName, $name): Unit { $node = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', $tagName ) ); return new Unit($node, $name); } } phpUnitVersion = $version; } public function process(CodeCoverage $coverage, string $target): void { if (\substr($target, -1, 1) !== \DIRECTORY_SEPARATOR) { $target .= \DIRECTORY_SEPARATOR; } $this->target = $target; $this->initTargetDirectory($target); $report = $coverage->getReport(); $this->project = new Project( $coverage->getReport()->getName() ); $this->setBuildInformation(); $this->processTests($coverage->getTests()); $this->processDirectory($report, $this->project); $this->saveDocument($this->project->asDom(), 'index'); } private function setBuildInformation(): void { $buildNode = $this->project->getBuildInformation(); $buildNode->setRuntimeInformation(new Runtime()); $buildNode->setBuildTime(\DateTime::createFromFormat('U', (string) $_SERVER['REQUEST_TIME'])); $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); } private function initTargetDirectory(string $directory): void { if (\file_exists($directory)) { if (!\is_dir($directory)) { throw new RuntimeException( "'$directory' exists but is not a directory." ); } if (!\is_writable($directory)) { throw new RuntimeException( "'$directory' exists but is not writable." ); } } elseif (!$this->createDirectory($directory)) { throw new RuntimeException( "'$directory' could not be created." ); } } private function processDirectory(DirectoryNode $directory, Node $context): void { $directoryName = $directory->getName(); if ($this->project->getProjectSourceDirectory() === $directoryName) { $directoryName = '/'; } $directoryObject = $context->addDirectory($directoryName); $this->setTotals($directory, $directoryObject->getTotals()); foreach ($directory->getDirectories() as $node) { $this->processDirectory($node, $directoryObject); } foreach ($directory->getFiles() as $node) { $this->processFile($node, $directoryObject); } } private function processFile(FileNode $file, Directory $context): void { $fileObject = $context->addFile( $file->getName(), $file->getId() . '.xml' ); $this->setTotals($file, $fileObject->getTotals()); $path = \substr( $file->getPath(), \strlen($this->project->getProjectSourceDirectory()) ); $fileReport = new Report($path); $this->setTotals($file, $fileReport->getTotals()); foreach ($file->getClassesAndTraits() as $unit) { $this->processUnit($unit, $fileReport); } foreach ($file->getFunctions() as $function) { $this->processFunction($function, $fileReport); } foreach ($file->getCoverageData() as $line => $tests) { if (!\is_array($tests) || \count($tests) === 0) { continue; } $coverage = $fileReport->getLineCoverage((string) $line); foreach ($tests as $test) { $coverage->addTest($test); } $coverage->finalize(); } $fileReport->getSource()->setSourceCode( \file_get_contents($file->getPath()) ); $this->saveDocument($fileReport->asDom(), $file->getId()); } private function processUnit(array $unit, Report $report): void { if (isset($unit['className'])) { $unitObject = $report->getClassObject($unit['className']); } else { $unitObject = $report->getTraitObject($unit['traitName']); } $unitObject->setLines( $unit['startLine'], $unit['executableLines'], $unit['executedLines'] ); $unitObject->setCrap((float) $unit['crap']); $unitObject->setPackage( $unit['package']['fullPackage'], $unit['package']['package'], $unit['package']['subpackage'], $unit['package']['category'] ); $unitObject->setNamespace($unit['package']['namespace']); foreach ($unit['methods'] as $method) { $methodObject = $unitObject->addMethod($method['methodName']); $methodObject->setSignature($method['signature']); $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); $methodObject->setCrap($method['crap']); $methodObject->setTotals( (string) $method['executableLines'], (string) $method['executedLines'], (string) $method['coverage'] ); } } private function processFunction(array $function, Report $report): void { $functionObject = $report->getFunctionObject($function['functionName']); $functionObject->setSignature($function['signature']); $functionObject->setLines((string) $function['startLine']); $functionObject->setCrap($function['crap']); $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); } private function processTests(array $tests): void { $testsObject = $this->project->getTests(); foreach ($tests as $test => $result) { if ($test === 'UNCOVERED_FILES_FROM_WHITELIST') { continue; } $testsObject->addTest($test, $result); } } private function setTotals(AbstractNode $node, Totals $totals): void { $loc = $node->getLinesOfCode(); $totals->setNumLines( $loc['loc'], $loc['cloc'], $loc['ncloc'], $node->getNumExecutableLines(), $node->getNumExecutedLines() ); $totals->setNumClasses( $node->getNumClasses(), $node->getNumTestedClasses() ); $totals->setNumTraits( $node->getNumTraits(), $node->getNumTestedTraits() ); $totals->setNumMethods( $node->getNumMethods(), $node->getNumTestedMethods() ); $totals->setNumFunctions( $node->getNumFunctions(), $node->getNumTestedFunctions() ); } private function getTargetDirectory(): string { return $this->target; } private function saveDocument(\DOMDocument $document, string $name): void { $filename = \sprintf('%s/%s.xml', $this->getTargetDirectory(), $name); $document->formatOutput = true; $document->preserveWhiteSpace = false; $this->initTargetDirectory(\dirname($filename)); $document->save($filename); } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } contextNode = $context; $this->writer = new \XMLWriter(); $this->writer->openMemory(); $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); $this->writer->writeAttribute('nr', $line); } public function addTest(string $test): void { if ($this->finalized) { throw new RuntimeException('Coverage Report already finalized'); } $this->writer->startElement('covered'); $this->writer->writeAttribute('by', $test); $this->writer->endElement(); } public function finalize(): void { $this->writer->endElement(); $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); $fragment->appendXML($this->writer->outputMemory()); $this->contextNode->parentNode->replaceChild( $fragment, $this->contextNode ); $this->finalized = true; } } contextNode = $context; $this->setName($name); } public function setLines(int $start, int $executable, int $executed): void { $this->contextNode->setAttribute('start', (string) $start); $this->contextNode->setAttribute('executable', (string) $executable); $this->contextNode->setAttribute('executed', (string) $executed); } public function setCrap(float $crap): void { $this->contextNode->setAttribute('crap', (string) $crap); } public function setPackage(string $full, string $package, string $sub, string $category): void { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'package' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'package' ) ); } $node->setAttribute('full', $full); $node->setAttribute('name', $package); $node->setAttribute('sub', $sub); $node->setAttribute('category', $category); } public function setNamespace(string $namespace): void { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'namespace' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'namespace' ) ); } $node->setAttribute('name', $namespace); } public function addMethod(string $name): Method { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'method' ) ); return new Method($node, $name); } private function setName(string $name): void { $this->contextNode->setAttribute('name', $name); } } contextNode = $context; $this->setName($name); } public function setSignature(string $signature): void { $this->contextNode->setAttribute('signature', $signature); } public function setLines(string $start, ?string $end = null): void { $this->contextNode->setAttribute('start', $start); if ($end !== null) { $this->contextNode->setAttribute('end', $end); } } public function setTotals(string $executable, string $executed, string $coverage): void { $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); } public function setCrap(string $crap): void { $this->contextNode->setAttribute('crap', $crap); } private function setName(string $name): void { $this->contextNode->setAttribute('name', $name); } } dom = $context->ownerDocument; $this->contextNode = $context; } public function getTotals(): Totals { $totalsContainer = $this->contextNode->firstChild; if (!$totalsContainer) { $totalsContainer = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function getLineCoverage(string $line): Coverage { $coverage = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'coverage' )->item(0); if (!$coverage) { $coverage = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'coverage' ) ); } $lineNode = $coverage->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'line' ) ); return new Coverage($lineNode, $line); } protected function getContextNode(): \DOMElement { return $this->contextNode; } protected function getDomDocument(): \DOMDocument { return $this->dom; } } 'UNKNOWN', 0 => 'PASSED', 1 => 'SKIPPED', 2 => 'INCOMPLETE', 3 => 'FAILURE', 4 => 'ERROR', 5 => 'RISKY', 6 => 'WARNING', ]; public function __construct(\DOMElement $context) { $this->contextNode = $context; } public function addTest(string $test, array $result): void { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'test' ) ); $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); $node->setAttribute('result', (string) $result['status']); $node->setAttribute('status', $this->codeMap[(int) $result['status']]); } } init(); $this->setProjectSourceDirectory($directory); } public function getProjectSourceDirectory(): string { return $this->getContextNode()->getAttribute('source'); } public function getBuildInformation(): BuildInformation { $buildNode = $this->getDom()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'build' )->item(0); if (!$buildNode) { $buildNode = $this->getDom()->documentElement->appendChild( $this->getDom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'build' ) ); } return new BuildInformation($buildNode); } public function getTests(): Tests { $testsNode = $this->getContextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'tests' )->item(0); if (!$testsNode) { $testsNode = $this->getContextNode()->appendChild( $this->getDom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', 'tests' ) ); } return new Tests($testsNode); } public function asDom(): \DOMDocument { return $this->getDom(); } private function init(): void { $dom = new \DOMDocument; $dom->loadXML(''); $this->setContextNode( $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', 'project' )->item(0) ); } private function setProjectSourceDirectory(string $name): void { $this->getContextNode()->setAttribute('source', $name); } } templatePath = $templatePath; $this->generator = $generator; $this->date = $date; $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->version = Version::id(); } protected function renderItemTemplate(\Text_Template $template, array $data): string { $numSeparator = ' / '; if (isset($data['numClasses']) && $data['numClasses'] > 0) { $classesLevel = $this->getColorLevel($data['testedClassesPercent']); $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; $classesBar = $this->getCoverageBar( $data['testedClassesPercent'] ); } else { $classesLevel = ''; $classesNumber = '0' . $numSeparator . '0'; $classesBar = ''; $data['testedClassesPercentAsString'] = 'n/a'; } if ($data['numMethods'] > 0) { $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; $methodsBar = $this->getCoverageBar( $data['testedMethodsPercent'] ); } else { $methodsLevel = ''; $methodsNumber = '0' . $numSeparator . '0'; $methodsBar = ''; $data['testedMethodsPercentAsString'] = 'n/a'; } if ($data['numExecutableLines'] > 0) { $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; $linesBar = $this->getCoverageBar( $data['linesExecutedPercent'] ); } else { $linesLevel = ''; $linesNumber = '0' . $numSeparator . '0'; $linesBar = ''; $data['linesExecutedPercentAsString'] = 'n/a'; } $template->setVar( [ 'icon' => $data['icon'] ?? '', 'crap' => $data['crap'] ?? '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber, ] ); return $template->render(); } protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node): void { $template->setVar( [ 'id' => $node->getId(), 'full_path' => $node->getPath(), 'path_to_root' => $this->getPathToRoot($node), 'breadcrumbs' => $this->getBreadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime' => $this->getRuntimeString(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound, ] ); } protected function getBreadcrumbs(AbstractNode $node): string { $breadcrumbs = ''; $path = $node->getPathAsArray(); $pathToRoot = []; $max = \count($path); if ($node instanceof FileNode) { $max--; } for ($i = 0; $i < $max; $i++) { $pathToRoot[] = \str_repeat('../', $i); } foreach ($path as $step) { if ($step !== $node) { $breadcrumbs .= $this->getInactiveBreadcrumb( $step, \array_pop($pathToRoot) ); } else { $breadcrumbs .= $this->getActiveBreadcrumb($step); } } return $breadcrumbs; } protected function getActiveBreadcrumb(AbstractNode $node): string { $buffer = \sprintf( ' ' . "\n", $node->getName() ); if ($node instanceof DirectoryNode) { $buffer .= ' ' . "\n"; } return $buffer; } protected function getInactiveBreadcrumb(AbstractNode $node, string $pathToRoot): string { return \sprintf( ' ' . "\n", $pathToRoot, $node->getName() ); } protected function getPathToRoot(AbstractNode $node): string { $id = $node->getId(); $depth = \substr_count($id, '/'); if ($id !== 'index' && $node instanceof DirectoryNode) { $depth++; } return \str_repeat('../', $depth); } protected function getCoverageBar(float $percent): string { $level = $this->getColorLevel($percent); $template = new \Text_Template( $this->templatePath . 'coverage_bar.html', '{{', '}}' ); $template->setVar(['level' => $level, 'percent' => \sprintf('%.2F', $percent)]); return $template->render(); } protected function getColorLevel(float $percent): string { if ($percent <= $this->lowUpperBound) { return 'danger'; } if ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { return 'warning'; } return 'success'; } private function getRuntimeString(): string { $runtime = new Runtime; $buffer = \sprintf( '%s %s', $runtime->getVendorUrl(), $runtime->getName(), $runtime->getVersion() ); if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) { $buffer .= \sprintf( ' with Xdebug %s', \phpversion('xdebug') ); } if ($runtime->hasPCOV() && !$runtime->hasPHPDBGCodeCoverage()) { $buffer .= \sprintf( ' with PCOV %s', \phpversion('pcov') ); } return $buffer; } } generator = $generator; $this->highLowerBound = $highLowerBound; $this->lowUpperBound = $lowUpperBound; $this->templatePath = __DIR__ . '/Renderer/Template/'; } public function process(CodeCoverage $coverage, string $target): void { $target = $this->getDirectory($target); $report = $coverage->getReport(); if (!isset($_SERVER['REQUEST_TIME'])) { $_SERVER['REQUEST_TIME'] = \time(); } $date = \date('D M j G:i:s T Y', $_SERVER['REQUEST_TIME']); $dashboard = new Dashboard( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory = new Directory( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $file = new File( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory->render($report, $target . 'index.html'); $dashboard->render($report, $target . 'dashboard.html'); foreach ($report as $node) { $id = $node->getId(); if ($node instanceof DirectoryNode) { if (!$this->createDirectory($target . $id)) { throw new \RuntimeException(\sprintf('Directory "%s" was not created', $target . $id)); } $directory->render($node, $target . $id . '/index.html'); $dashboard->render($node, $target . $id . '/dashboard.html'); } else { $dir = \dirname($target . $id); if (!$this->createDirectory($dir)) { throw new \RuntimeException(\sprintf('Directory "%s" was not created', $dir)); } $file->render($node, $target . $id . '.html'); } } $this->copyFiles($target); } private function copyFiles(string $target): void { $dir = $this->getDirectory($target . '_css'); \copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); \copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); \copy($this->templatePath . 'css/style.css', $dir . 'style.css'); \copy($this->templatePath . 'css/custom.css', $dir . 'custom.css'); \copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css'); $dir = $this->getDirectory($target . '_icons'); \copy($this->templatePath . 'icons/file-code.svg', $dir . 'file-code.svg'); \copy($this->templatePath . 'icons/file-directory.svg', $dir . 'file-directory.svg'); $dir = $this->getDirectory($target . '_js'); \copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); \copy($this->templatePath . 'js/popper.min.js', $dir . 'popper.min.js'); \copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); \copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); \copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); \copy($this->templatePath . 'js/file.js', $dir . 'file.js'); } private function getDirectory(string $directory): string { if (\substr($directory, -1, 1) != \DIRECTORY_SEPARATOR) { $directory .= \DIRECTORY_SEPARATOR; } if (!$this->createDirectory($directory)) { throw new RuntimeException( \sprintf( 'Directory "%s" does not exist.', $directory ) ); } return $directory; } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } templatePath . 'directory.html', '{{', '}}'); $this->setCommonTemplateVariables($template, $node); $items = $this->renderItem($node, true); foreach ($node->getDirectories() as $item) { $items .= $this->renderItem($item); } foreach ($node->getFiles() as $item) { $items .= $this->renderItem($item); } $template->setVar( [ 'id' => $node->getId(), 'items' => $items, ] ); $template->renderTo($file); } protected function renderItem(Node $node, bool $total = false): string { $data = [ 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumFunctionsAndMethods(), 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), ]; if ($total) { $data['name'] = 'Total'; } else { if ($node instanceof DirectoryNode) { $data['name'] = \sprintf( '%s', $node->getName(), $node->getName() ); $up = \str_repeat('../', \count($node->getPathAsArray()) - 2); $data['icon'] = \sprintf('', $up); } else { $data['name'] = \sprintf( '%s', $node->getName(), $node->getName() ); $up = \str_repeat('../', \count($node->getPathAsArray()) - 2); $data['icon'] = \sprintf('', $up); } } return $this->renderItemTemplate( new \Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'), $data ); } } getClassesAndTraits(); $template = new \Text_Template( $this->templatePath . 'dashboard.html', '{{', '}}' ); $this->setCommonTemplateVariables($template, $node); $baseLink = $node->getId() . '/'; $complexity = $this->complexity($classes, $baseLink); $coverageDistribution = $this->coverageDistribution($classes); $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); $projectRisks = $this->projectRisks($classes, $baseLink); $template->setVar( [ 'insufficient_coverage_classes' => $insufficientCoverage['class'], 'insufficient_coverage_methods' => $insufficientCoverage['method'], 'project_risks_classes' => $projectRisks['class'], 'project_risks_methods' => $projectRisks['method'], 'complexity_class' => $complexity['class'], 'complexity_method' => $complexity['method'], 'class_coverage_distribution' => $coverageDistribution['class'], 'method_coverage_distribution' => $coverageDistribution['method'], ] ); $template->renderTo($file); } protected function complexity(array $classes, string $baseLink): array { $result = ['class' => [], 'method' => []]; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($className !== '*') { $methodName = $className . '::' . $methodName; } $result['method'][] = [ $method['coverage'], $method['ccn'], \sprintf( '%s', \str_replace($baseLink, '', $method['link']), $methodName ), ]; } $result['class'][] = [ $class['coverage'], $class['ccn'], \sprintf( '%s', \str_replace($baseLink, '', $class['link']), $className ), ]; } return [ 'class' => \json_encode($result['class']), 'method' => \json_encode($result['method']), ]; } protected function coverageDistribution(array $classes): array { $result = [ 'class' => [ '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0, ], 'method' => [ '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0, ], ]; foreach ($classes as $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] === 0) { $result['method']['0%']++; } elseif ($method['coverage'] === 100) { $result['method']['100%']++; } else { $key = \floor($method['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['method'][$key]++; } } if ($class['coverage'] === 0) { $result['class']['0%']++; } elseif ($class['coverage'] === 100) { $result['class']['100%']++; } else { $key = \floor($class['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['class'][$key]++; } } return [ 'class' => \json_encode(\array_values($result['class'])), 'method' => \json_encode(\array_values($result['method'])), ]; } protected function insufficientCoverage(array $classes, string $baseLink): array { $leastTestedClasses = []; $leastTestedMethods = []; $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound) { $key = $methodName; if ($className !== '*') { $key = $className . '::' . $methodName; } $leastTestedMethods[$key] = $method['coverage']; } } if ($class['coverage'] < $this->highLowerBound) { $leastTestedClasses[$className] = $class['coverage']; } } \asort($leastTestedClasses); \asort($leastTestedMethods); foreach ($leastTestedClasses as $className => $coverage) { $result['class'] .= \sprintf( ' %s%d%%' . "\n", \str_replace($baseLink, '', $classes[$className]['link']), $className, $coverage ); } foreach ($leastTestedMethods as $methodName => $coverage) { [$class, $method] = \explode('::', $methodName); $result['method'] .= \sprintf( ' %s%d%%' . "\n", \str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $coverage ); } return $result; } protected function projectRisks(array $classes, string $baseLink): array { $classRisks = []; $methodRisks = []; $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { $key = $methodName; if ($className !== '*') { $key = $className . '::' . $methodName; } $methodRisks[$key] = $method['crap']; } } if ($class['coverage'] < $this->highLowerBound && $class['ccn'] > \count($class['methods'])) { $classRisks[$className] = $class['crap']; } } \arsort($classRisks); \arsort($methodRisks); foreach ($classRisks as $className => $crap) { $result['class'] .= \sprintf( ' %s%d' . "\n", \str_replace($baseLink, '', $classes[$className]['link']), $className, $crap ); } foreach ($methodRisks as $methodName => $crap) { [$class, $method] = \explode('::', $methodName); $result['method'] .= \sprintf( ' %s%d' . "\n", \str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $crap ); } return $result; } protected function getActiveBreadcrumb(AbstractNode $node): string { return \sprintf( ' ' . "\n" . ' ' . "\n", $node->getName() ); } } Code Coverage for {{full_path}}
    {{items}}
     
    Code Coverage
     
    Classes and Traits
    Functions and Methods
    Lines
    {{lines}}
    Dashboard for {{full_path}}

    Classes

    Coverage Distribution

    Complexity

    Insufficient Coverage

    {{insufficient_coverage_classes}}
    Class Coverage

    Project Risks

    {{project_risks_classes}}
    Class CRAP

    Methods

    Coverage Distribution

    Complexity

    Insufficient Coverage

    {{insufficient_coverage_methods}}
    Method Coverage

    Project Risks

    {{project_risks_methods}}
    Method CRAP
    {{name}} {{methods_bar}}
    {{methods_tested_percent}}
    {{methods_number}}
    {{crap}} {{lines_bar}}
    {{lines_executed_percent}}
    {{lines_number}}
    Code Coverage for {{full_path}}
    {{items}}
     
    Code Coverage
     
    Lines
    Functions and Methods
    Classes and Traits

    Legend

    Low: 0% to {{low_upper_bound}}% Medium: {{low_upper_bound}}% to {{high_lower_bound}}% High: {{high_lower_bound}}% to 100%

    Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}.

    {{name}} {{classes_bar}}
    {{classes_tested_percent}}
    {{classes_number}}
    {{methods_bar}}
    {{methods_tested_percent}}
    {{methods_number}}
    {{crap}} {{lines_bar}}
    {{lines_executed_percent}}
    {{lines_number}}
    /*! * Bootstrap v4.3.1 (https://getbootstrap.com/) * Copyright 2011-2019 The Bootstrap Authors * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} /*# sourceMappingURL=bootstrap.min.css.map */.octicon { display: inline-block; vertical-align: text-top; fill: currentColor; } body { padding-top: 10px; } .popover { max-width: none; } .octicon { margin-right:.25em; } .table-bordered>thead>tr>td { border-bottom-width: 1px; } .table tbody>tr>td, .table thead>tr>td { padding-top: 3px; padding-bottom: 3px; } .table-condensed tbody>tr>td { padding-top: 0; padding-bottom: 0; } .table .progress { margin-bottom: inherit; } .table-borderless th, .table-borderless td { border: 0 !important; } .table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { background-color: #dff0d8; } .table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { background-color: #c3e3b5; } .table tbody tr.covered-by-small-tests, li.covered-by-small-tests { background-color: #99cb84; } .table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { background-color: #f2dede; } .table tbody td.warning, li.warning, span.warning { background-color: #fcf8e3; } .table tbody td.info { background-color: #d9edf7; } td.big { width: 117px; } td.small { } td.codeLine { font-family: "Source Code Pro", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; white-space: pre; } td span.comment { color: #888a85; } td span.default { color: #2e3436; } td span.html { color: #888a85; } td span.keyword { color: #2e3436; font-weight: bold; } pre span.string { color: #2e3436; } span.success, span.warning, span.danger { margin-right: 2px; padding-left: 10px; padding-right: 10px; text-align: center; } #classCoverageDistribution, #classComplexity { height: 200px; width: 475px; } #toplink { position: fixed; left: 5px; bottom: 5px; outline: 0; } svg text { font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #666; fill: #666; } .scrollbox { height:245px; overflow-x:hidden; overflow-y:scroll; } .nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} {{icon}}{{name}} {{lines_bar}}
    {{lines_executed_percent}}
    {{lines_number}}
    {{methods_bar}}
    {{methods_tested_percent}}
    {{methods_number}}
    {{classes_bar}}
    {{classes_tested_percent}}
    {{classes_number}}
    /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0this._items.length-1||t<0))if(this._isSliding)g(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
    ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Ee},je="show",He="out",Re={HIDE:"hide"+De,HIDDEN:"hidden"+De,SHOW:"show"+De,SHOWN:"shown"+De,INSERTED:"inserted"+De,CLICK:"click"+De,FOCUSIN:"focusin"+De,FOCUSOUT:"focusout"+De,MOUSEENTER:"mouseenter"+De,MOUSELEAVE:"mouseleave"+De},xe="fade",Fe="show",Ue=".tooltip-inner",We=".arrow",qe="hover",Me="focus",Ke="click",Qe="manual",Be=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Fe))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(xe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:We},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),g(o).addClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===He&&e._leave(null,e)};if(g(this.tip).hasClass(xe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=g.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==je&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),g(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(g(this.element).trigger(i),!i.isDefaultPrevented()){if(g(n).removeClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ke]=!1,this._activeTrigger[Me]=!1,this._activeTrigger[qe]=!1,g(this.tip).hasClass(xe)){var r=_.getTransitionDurationFromElement(n);g(n).one(_.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Ae+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ue)),this.getTitle()),g(t).removeClass(xe+" "+Fe)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=Se(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Pe[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Qe){var e=t===qe?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===qe?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),g(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Me:qe]=!0),g(e.getTipElement()).hasClass(Fe)||e._hoverState===je?e._hoverState=je:(clearTimeout(e._timeout),e._hoverState=je,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===je&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Me:qe]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=He,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===He&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==Oe.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(be,t,this.constructor.DefaultType),t.sanitize&&(t.template=Se(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ne);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(xe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ie),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ie,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return Le}},{key:"NAME",get:function(){return be}},{key:"DATA_KEY",get:function(){return Ie}},{key:"Event",get:function(){return Re}},{key:"EVENT_KEY",get:function(){return De}},{key:"DefaultType",get:function(){return ke}}]),i}();g.fn[be]=Be._jQueryInterface,g.fn[be].Constructor=Be,g.fn[be].noConflict=function(){return g.fn[be]=we,Be._jQueryInterface};var Ve="popover",Ye="bs.popover",ze="."+Ye,Xe=g.fn[Ve],$e="bs-popover",Ge=new RegExp("(^|\\s)"+$e+"\\S+","g"),Je=l({},Be.Default,{placement:"right",trigger:"click",content:"",template:''}),Ze=l({},Be.DefaultType,{content:"(string|element|function)"}),tn="fade",en="show",nn=".popover-header",on=".popover-body",rn={HIDE:"hide"+ze,HIDDEN:"hidden"+ze,SHOW:"show"+ze,SHOWN:"shown"+ze,INSERTED:"inserted"+ze,CLICK:"click"+ze,FOCUSIN:"focusin"+ze,FOCUSOUT:"focusout"+ze,MOUSEENTER:"mouseenter"+ze,MOUSELEAVE:"mouseleave"+ze},sn=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){g(this.getTipElement()).addClass($e+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},o.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(nn),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(on),e),t.removeClass(tn+" "+en)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ge);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||tf&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
    open:"+b.yAxis.tickFormat()(c.open)+"
    close:"+b.yAxis.tickFormat()(c.close)+"
    high"+b.yAxis.tickFormat()(c.high)+"
    low:"+b.yAxis.tickFormat()(c.low)+"
    "}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
    open:"+b.yAxis.tickFormat()(c.open)+"
    close:"+b.yAxis.tickFormat()(c.close)+"
    high"+b.yAxis.tickFormat()(c.high)+"
    low:"+b.yAxis.tickFormat()(c.low)+"
    "}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] }if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left }},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) }},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();/* Copyright (C) Federico Zivolo 2019 Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?pe:10===e?se:pe||se}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),le({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=fe({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},le(n,m,$(v)),le(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ge.FLIP:p=[n,i];break;case ge.CLOCKWISE:p=G(n);break;case ge.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=fe({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!me),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=fe({},E,e.attributes),e.styles=fe({},m,e.styles),e.arrowStyles=fe({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ue}); //# sourceMappingURL=popper.min.js.map !function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); $(function() { var $window = $(window) , $top_link = $('#toplink') , $body = $('body, html') , offset = $('#code').offset().top , hidePopover = function ($target) { $target.data('popover-hover', false); setTimeout(function () { if (!$target.data('popover-hover')) { $target.popover('hide'); } }, 300); }; $top_link.hide().click(function(event) { event.preventDefault(); $body.animate({scrollTop:0}, 800); }); $window.scroll(function() { if($window.scrollTop() > offset) { $top_link.fadeIn(); } else { $top_link.fadeOut(); } }).scroll(); $('.popin') .popover({trigger: 'manual'}) .on({ 'mouseenter.popover': function () { var $target = $(this); var $container = $target.children().first(); $target.data('popover-hover', true); // popover already displayed if ($target.next('.popover').length) { return; } // show the popover $container.popover('show'); // register mouse events on the popover $target.next('.popover:not(.popover-initialized)') .on({ 'mouseenter': function () { $target.data('popover-hover', true); }, 'mouseleave': function () { hidePopover($container); } }) .addClass('popover-initialized'); }, 'mouseleave.popover': function () { hidePopover($(this).children().first()); } }); });
    {{percent}}% covered ({{level}})
    templatePath . 'file.html', '{{', '}}'); $template->setVar( [ 'items' => $this->renderItems($node), 'lines' => $this->renderSource($node), ] ); $this->setCommonTemplateVariables($template, $node); $template->renderTo($file); } protected function renderItems(FileNode $node): string { $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); $methodItemTemplate = new \Text_Template( $this->templatePath . 'method_item.html', '{{', '}}' ); $items = $this->renderItemTemplate( $template, [ 'name' => 'Total', 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumFunctionsAndMethods(), 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), 'crap' => 'CRAP', ] ); $items .= $this->renderFunctionItems( $node->getFunctions(), $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getTraits(), $template, $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getClasses(), $template, $methodItemTemplate ); return $items; } protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate): string { $buffer = ''; if (empty($items)) { return $buffer; } foreach ($items as $name => $item) { $numMethods = 0; $numTestedMethods = 0; foreach ($item['methods'] as $method) { if ($method['executableLines'] > 0) { $numMethods++; if ($method['executedLines'] === $method['executableLines']) { $numTestedMethods++; } } } if ($item['executableLines'] > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0; $linesExecutedPercentAsString = Util::percent( $item['executedLines'], $item['executableLines'], true ); } else { $numClasses = 'n/a'; $numTestedClasses = 'n/a'; $linesExecutedPercentAsString = 'n/a'; } $buffer .= $this->renderItemTemplate( $template, [ 'name' => $this->abbreviateClassName($name), 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedMethods, $numMethods ), 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, $numMethods, true ), 'testedClassesPercent' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1 ), 'testedClassesPercentAsString' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, true ), 'crap' => $item['crap'], ] ); foreach ($item['methods'] as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, ' ' ); } } return $buffer; } protected function renderFunctionItems(array $functions, \Text_Template $template): string { if (empty($functions)) { return ''; } $buffer = ''; foreach ($functions as $function) { $buffer .= $this->renderFunctionOrMethodItem( $template, $function ); } return $buffer; } protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, string $indent = ''): string { $numMethods = 0; $numTestedMethods = 0; if ($item['executableLines'] > 0) { $numMethods = 1; if ($item['executedLines'] === $item['executableLines']) { $numTestedMethods = 1; } } return $this->renderItemTemplate( $template, [ 'name' => \sprintf( '%s%s', $indent, $item['startLine'], \htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName'] ), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'] ), 'linesExecutedPercentAsString' => Util::percent( $item['executedLines'], $item['executableLines'], true ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedMethods, 1 ), 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, 1, true ), 'crap' => $item['crap'], ] ); } protected function renderSource(FileNode $node): string { $coverageData = $node->getCoverageData(); $testData = $node->getTestData(); $codeLines = $this->loadFile($node->getPath()); $lines = ''; $i = 1; foreach ($codeLines as $line) { $trClass = ''; $popoverContent = ''; $popoverTitle = ''; if (\array_key_exists($i, $coverageData)) { $numTests = ($coverageData[$i] ? \count($coverageData[$i]) : 0); if ($coverageData[$i] === null) { $trClass = ' class="warning"'; } elseif ($numTests == 0) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; $popoverContent = '
      '; if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } foreach ($coverageData[$i] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { $lineCss = 'covered-by-small-tests'; } switch ($testData[$test]['status']) { case 0: switch ($testData[$test]['size']) { case 'small': $testCSS = ' class="covered-by-small-tests"'; break; case 'medium': $testCSS = ' class="covered-by-medium-tests"'; break; default: $testCSS = ' class="covered-by-large-tests"'; break; } break; case 1: case 2: $testCSS = ' class="warning"'; break; case 3: $testCSS = ' class="danger"'; break; case 4: $testCSS = ' class="danger"'; break; default: $testCSS = ''; } $popoverContent .= \sprintf( '%s', $testCSS, \htmlspecialchars($test, $this->htmlSpecialCharsFlags) ); } $popoverContent .= '
    '; $trClass = ' class="' . $lineCss . ' popin"'; } } $popover = ''; if (!empty($popoverTitle)) { $popover = \sprintf( ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', $popoverTitle, \htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) ); } $lines .= \sprintf( ' %s' . "\n", $trClass, $popover, $i, $i, $i, $line ); $i++; } return $lines; } protected function loadFile($file): array { $buffer = \file_get_contents($file); $tokens = \token_get_all($buffer); $result = ['']; $i = 0; $stringFlag = false; $fileEndsWithNewLine = \substr($buffer, -1) == "\n"; unset($buffer); foreach ($tokens as $j => $token) { if (\is_string($token)) { if ($token === '"' && $tokens[$j - 1] !== '\\') { $result[$i] .= \sprintf( '%s', \htmlspecialchars($token, $this->htmlSpecialCharsFlags) ); $stringFlag = !$stringFlag; } else { $result[$i] .= \sprintf( '%s', \htmlspecialchars($token, $this->htmlSpecialCharsFlags) ); } continue; } [$token, $value] = $token; $value = \str_replace( ["\t", ' '], ['    ', ' '], \htmlspecialchars($value, $this->htmlSpecialCharsFlags) ); if ($value === "\n") { $result[++$i] = ''; } else { $lines = \explode("\n", $value); foreach ($lines as $jj => $line) { $line = \trim($line); if ($line !== '') { if ($stringFlag) { $colour = 'string'; } else { switch ($token) { case \T_INLINE_HTML: $colour = 'html'; break; case \T_COMMENT: case \T_DOC_COMMENT: $colour = 'comment'; break; case \T_ABSTRACT: case \T_ARRAY: case \T_AS: case \T_BREAK: case \T_CALLABLE: case \T_CASE: case \T_CATCH: case \T_CLASS: case \T_CLONE: case \T_CONTINUE: case \T_DEFAULT: case \T_ECHO: case \T_ELSE: case \T_ELSEIF: case \T_EMPTY: case \T_ENDDECLARE: case \T_ENDFOR: case \T_ENDFOREACH: case \T_ENDIF: case \T_ENDSWITCH: case \T_ENDWHILE: case \T_EXIT: case \T_EXTENDS: case \T_FINAL: case \T_FINALLY: case \T_FOREACH: case \T_FUNCTION: case \T_GLOBAL: case \T_IF: case \T_IMPLEMENTS: case \T_INCLUDE: case \T_INCLUDE_ONCE: case \T_INSTANCEOF: case \T_INSTEADOF: case \T_INTERFACE: case \T_ISSET: case \T_LOGICAL_AND: case \T_LOGICAL_OR: case \T_LOGICAL_XOR: case \T_NAMESPACE: case \T_NEW: case \T_PRIVATE: case \T_PROTECTED: case \T_PUBLIC: case \T_REQUIRE: case \T_REQUIRE_ONCE: case \T_RETURN: case \T_STATIC: case \T_THROW: case \T_TRAIT: case \T_TRY: case \T_UNSET: case \T_USE: case \T_VAR: case \T_WHILE: case \T_YIELD: $colour = 'keyword'; break; default: $colour = 'default'; } } $result[$i] .= \sprintf( '%s', $colour, $line ); } if (isset($lines[$jj + 1])) { $result[++$i] = ''; } } } } if ($fileEndsWithNewLine) { unset($result[\count($result) - 1]); } return $result; } private function abbreviateClassName(string $className): string { $tmp = \explode('\\', $className); if (\count($tmp) > 1) { $className = \sprintf( '%s', $className, \array_pop($tmp) ); } return $className; } } formatOutput = true; $xmlCoverage = $xmlDocument->createElement('coverage'); $xmlCoverage->setAttribute('generated', (string) $_SERVER['REQUEST_TIME']); $xmlDocument->appendChild($xmlCoverage); $xmlProject = $xmlDocument->createElement('project'); $xmlProject->setAttribute('timestamp', (string) $_SERVER['REQUEST_TIME']); if (\is_string($name)) { $xmlProject->setAttribute('name', $name); } $xmlCoverage->appendChild($xmlProject); $packages = []; $report = $coverage->getReport(); foreach ($report as $item) { if (!$item instanceof File) { continue; } $xmlFile = $xmlDocument->createElement('file'); $xmlFile->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); $coverageData = $item->getCoverageData(); $lines = []; $namespace = 'global'; foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $methodName => $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } $methodCount = 0; foreach (\range($method['startLine'], $method['endLine']) as $line) { if (isset($coverageData[$line]) && ($coverageData[$line] !== null)) { $methodCount = \max($methodCount, \count($coverageData[$line])); } } $lines[$method['startLine']] = [ 'ccn' => $method['ccn'], 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'visibility' => $method['visibility'], 'name' => $methodName, ]; } if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $xmlClass = $xmlDocument->createElement('class'); $xmlClass->setAttribute('name', $className); $xmlClass->setAttribute('namespace', $namespace); if (!empty($class['package']['fullPackage'])) { $xmlClass->setAttribute( 'fullPackage', $class['package']['fullPackage'] ); } if (!empty($class['package']['category'])) { $xmlClass->setAttribute( 'category', $class['package']['category'] ); } if (!empty($class['package']['package'])) { $xmlClass->setAttribute( 'package', $class['package']['package'] ); } if (!empty($class['package']['subpackage'])) { $xmlClass->setAttribute( 'subpackage', $class['package']['subpackage'] ); } $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); $xmlMetrics->setAttribute('methods', (string) $classMethods); $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements )); $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements )); $xmlClass->appendChild($xmlMetrics); } foreach ($coverageData as $line => $data) { if ($data === null || isset($lines[$line])) { continue; } $lines[$line] = [ 'count' => \count($data), 'type' => 'stmt', ]; } \ksort($lines); foreach ($lines as $line => $data) { $xmlLine = $xmlDocument->createElement('line'); $xmlLine->setAttribute('num', (string) $line); $xmlLine->setAttribute('type', $data['type']); if (isset($data['name'])) { $xmlLine->setAttribute('name', $data['name']); } if (isset($data['visibility'])) { $xmlLine->setAttribute('visibility', $data['visibility']); } if (isset($data['ccn'])) { $xmlLine->setAttribute('complexity', (string) $data['ccn']); } if (isset($data['crap'])) { $xmlLine->setAttribute('crap', (string) $data['crap']); } $xmlLine->setAttribute('count', (string) $data['count']); $xmlFile->appendChild($xmlLine); } $linesOfCode = $item->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('loc', (string) $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', (string) $item->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', (string) $item->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', (string) $item->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $item->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', (string) $item->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', (string) ($item->getNumMethods() + $item->getNumExecutableLines() )); $xmlMetrics->setAttribute('coveredelements', (string) ($item->getNumTestedMethods() + $item->getNumExecutedLines() )); $xmlFile->appendChild($xmlMetrics); if ($namespace === 'global') { $xmlProject->appendChild($xmlFile); } else { if (!isset($packages[$namespace])) { $packages[$namespace] = $xmlDocument->createElement( 'package' ); $packages[$namespace]->setAttribute('name', $namespace); $xmlProject->appendChild($packages[$namespace]); } $packages[$namespace]->appendChild($xmlFile); } } $linesOfCode = $report->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('files', (string) \count($report)); $xmlMetrics->setAttribute('loc', (string) $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', (string) $report->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', (string) $report->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', (string) $report->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $report->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', (string) $report->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', (string) ($report->getNumMethods() + $report->getNumExecutableLines() )); $xmlMetrics->setAttribute('coveredelements', (string) ($report->getNumTestedMethods() + $report->getNumExecutedLines() )); $xmlProject->appendChild($xmlMetrics); $buffer = $xmlDocument->saveXML(); if ($target !== null) { if (!$this->createDirectory(\dirname($target))) { throw new \RuntimeException(\sprintf('Directory "%s" was not created', \dirname($target))); } if (@\file_put_contents($target, $buffer) === false) { throw new RuntimeException( \sprintf( 'Could not write to "%s', $target ) ); } } return $buffer; } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } threshold = $threshold; } public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string { $document = new \DOMDocument('1.0', 'UTF-8'); $document->formatOutput = true; $root = $document->createElement('crap_result'); $document->appendChild($root); $project = $document->createElement('project', \is_string($name) ? $name : ''); $root->appendChild($project); $root->appendChild($document->createElement('timestamp', \date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']))); $stats = $document->createElement('stats'); $methodsNode = $document->createElement('methods'); $report = $coverage->getReport(); unset($coverage); $fullMethodCount = 0; $fullCrapMethodCount = 0; $fullCrapLoad = 0; $fullCrap = 0; foreach ($report as $item) { $namespace = 'global'; if (!$item instanceof File) { continue; } $file = $document->createElement('file'); $file->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']); $fullCrap += $method['crap']; $fullCrapLoad += $crapLoad; $fullMethodCount++; if ($method['crap'] >= $this->threshold) { $fullCrapMethodCount++; } $methodNode = $document->createElement('method'); if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); $methodNode->appendChild($document->createElement('methodSignature', \htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('fullMethod', \htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue($method['crap']))); $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); $methodNode->appendChild($document->createElement('crapLoad', (string) \round($crapLoad))); $methodsNode->appendChild($methodNode); } } } $stats->appendChild($document->createElement('name', 'Method Crap Stats')); $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount)); $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount)); $stats->appendChild($document->createElement('crapLoad', (string) \round($fullCrapLoad))); $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap)); $crapMethodPercent = 0; if ($fullMethodCount > 0) { $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); } $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent)); $root->appendChild($stats); $root->appendChild($methodsNode); $buffer = $document->saveXML(); if ($target !== null) { if (!$this->createDirectory(\dirname($target))) { throw new \RuntimeException(\sprintf('Directory "%s" was not created', \dirname($target))); } if (@\file_put_contents($target, $buffer) === false) { throw new RuntimeException( \sprintf( 'Could not write to "%s', $target ) ); } } return $buffer; } private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent): float { $crapLoad = 0; if ($crapValue >= $this->threshold) { $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); $crapLoad += $cyclomaticComplexity / $this->threshold; } return $crapLoad; } private function roundValue($value): float { return \round($value, 2); } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } unintentionallyCoveredUnits = $unintentionallyCoveredUnits; parent::__construct($this->toString()); } public function getUnintentionallyCoveredUnits(): array { return $this->unintentionallyCoveredUnits; } private function toString(): string { $message = ''; foreach ($this->unintentionallyCoveredUnits as $unit) { $message .= '- ' . $unit . "\n"; } return $message; } } tokens[] = $token; } public function current(): Token { return \current($this->tokens); } public function key(): int { return \key($this->tokens); } public function next(): void { \next($this->tokens); $this->pos++; } public function valid(): bool { return $this->count() > $this->pos; } public function rewind(): void { \reset($this->tokens); $this->pos = 0; } public function count(): int { return \count($this->tokens); } public function offsetExists($offset): bool { return isset($this->tokens[$offset]); } public function offsetGet($offset): Token { if (!$this->offsetExists($offset)) { throw new TokenCollectionException( \sprintf('No Token at offest %s', $offset) ); } return $this->tokens[$offset]; } public function offsetSet($offset, $value): void { if (!\is_int($offset)) { $type = \gettype($offset); throw new TokenCollectionException( \sprintf( 'Offset must be of type integer, %s given', $type === 'object' ? \get_class($value) : $type ) ); } if (!$value instanceof Token) { $type = \gettype($value); throw new TokenCollectionException( \sprintf( 'Value must be of type %s, %s given', Token::class, $type === 'object' ? \get_class($value) : $type ) ); } $this->tokens[$offset] = $value; } public function offsetUnset($offset): void { unset($this->tokens[$offset]); } } 'T_OPEN_BRACKET', ')' => 'T_CLOSE_BRACKET', '[' => 'T_OPEN_SQUARE', ']' => 'T_CLOSE_SQUARE', '{' => 'T_OPEN_CURLY', '}' => 'T_CLOSE_CURLY', ';' => 'T_SEMICOLON', '.' => 'T_DOT', ',' => 'T_COMMA', '=' => 'T_EQUAL', '<' => 'T_LT', '>' => 'T_GT', '+' => 'T_PLUS', '-' => 'T_MINUS', '*' => 'T_MULT', '/' => 'T_DIV', '?' => 'T_QUESTION_MARK', '!' => 'T_EXCLAMATION_MARK', ':' => 'T_COLON', '"' => 'T_DOUBLE_QUOTES', '@' => 'T_AT', '&' => 'T_AMPERSAND', '%' => 'T_PERCENT', '|' => 'T_PIPE', '$' => 'T_DOLLAR', '^' => 'T_CARET', '~' => 'T_TILDE', '`' => 'T_BACKTICK' ]; public function parse(string $source): TokenCollection { $result = new TokenCollection(); if ($source === '') { return $result; } $tokens = \token_get_all($source); $lastToken = new Token( $tokens[0][2], 'Placeholder', '' ); foreach ($tokens as $pos => $tok) { if (\is_string($tok)) { $token = new Token( $lastToken->getLine(), $this->map[$tok], $tok ); $result->addToken($token); $lastToken = $token; continue; } $line = $tok[2]; $values = \preg_split('/\R+/Uu', $tok[1]); foreach ($values as $v) { $token = new Token( $line, \token_name($tok[0]), $v ); $lastToken = $token; $line++; if ($v === '') { continue; } $result->addToken($token); } } return $this->fillBlanks($result, $lastToken->getLine()); } private function fillBlanks(TokenCollection $tokens, int $maxLine): TokenCollection { $prev = new Token( 0, 'Placeholder', '' ); $final = new TokenCollection(); foreach ($tokens as $token) { if ($prev === null) { $final->addToken($token); $prev = $token; continue; } $gap = $token->getLine() - $prev->getLine(); while ($gap > 1) { $linebreak = new Token( $prev->getLine() + 1, 'T_WHITESPACE', '' ); $final->addToken($linebreak); $prev = $linebreak; $gap--; } $final->addToken($token); $prev = $token; } $gap = $maxLine - $prev->getLine(); while ($gap > 0) { $linebreak = new Token( $prev->getLine() + 1, 'T_WHITESPACE', '' ); $final->addToken($linebreak); $prev = $linebreak; $gap--; } return $final; } } xmlns = $xmlns; } public function toDom(TokenCollection $tokens): DOMDocument { $dom = new DOMDocument(); $dom->preserveWhiteSpace = false; $dom->loadXML($this->toXML($tokens)); return $dom; } public function toXML(TokenCollection $tokens): string { $this->writer = new \XMLWriter(); $this->writer->openMemory(); $this->writer->setIndent(true); $this->writer->startDocument(); $this->writer->startElement('source'); $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); if (\count($tokens) > 0) { $this->writer->startElement('line'); $this->writer->writeAttribute('no', '1'); $this->previousToken = $tokens[0]; foreach ($tokens as $token) { $this->addToken($token); } } $this->writer->endElement(); $this->writer->endElement(); $this->writer->endDocument(); return $this->writer->outputMemory(); } private function addToken(Token $token): void { if ($this->previousToken->getLine() < $token->getLine()) { $this->writer->endElement(); $this->writer->startElement('line'); $this->writer->writeAttribute('no', (string)$token->getLine()); $this->previousToken = $token; } if ($token->getValue() !== '') { $this->writer->startElement('token'); $this->writer->writeAttribute('name', $token->getName()); $this->writer->writeRaw(\htmlspecialchars($token->getValue(), \ENT_NOQUOTES | \ENT_DISALLOWED | \ENT_XML1)); $this->writer->endElement(); } } } ensureValidUri($value); $this->value = $value; } public function asString(): string { return $this->value; } private function ensureValidUri($value): void { if (\strpos($value, ':') === false) { throw new NamespaceUriException( \sprintf("Namespace URI '%s' must contain at least one colon", $value) ); } } } line = $line; $this->name = $name; $this->value = $value; } public function getLine(): int { return $this->line; } public function getName(): string { return $this->name; } public function getValue(): string { return $this->value; } } _source = $source; $this->addIds($ids); return; } public function addIds(array $ids) { foreach ($ids as $id) { $this->_callables[$id] = []; } return; } public function attach($listenerId, $callable) { if (false === $this->listenerExists($listenerId)) { throw new Exception( 'Cannot listen %s because it is not defined.', 0, $listenerId ); } $callable = xcallable($callable); $this->_callables[$listenerId][$callable->getHash()] = $callable; return $this; } public function detach($listenerId, $callable) { unset($this->_callables[$listenerId][xcallable($callable)->getHash()]); return $this; } public function detachAll($listenerId) { unset($this->_callables[$listenerId]); return $this; } public function listenerExists($listenerId) { return array_key_exists($listenerId, $this->_callables); } public function fire($listenerId, Bucket $data) { if (false === $this->listenerExists($listenerId)) { throw new Exception( 'Cannot fire on %s because it is not defined.', 1, $listenerId ); } $data->setSource($this->_source); $out = []; foreach ($this->_callables[$listenerId] as $callable) { $out[] = $callable($data); } return $out; } } when($result = new \Mock\Hoa\Event\Source()) ->then ->object($result) ->isInstanceOf('Hoa\Event\Source'); } } given( $source = new \Mock\Hoa\Event\Listenable(), $ids = ['foo', 'bar', 'baz'] ) ->when($result = new SUT($source, $ids)) ->then ->object($result) ->isInstanceOf('Hoa\Event\Listener') ->boolean($result->listenerExists('foo')) ->isTrue() ->boolean($result->listenerExists('bar')) ->isTrue() ->boolean($result->listenerExists('baz')) ->isTrue(); } public function case_attach() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']), $callable = function () { return 42; } ) ->when($result = $listener->attach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener) ->array($listener->fire($listenerId, new LUT\Bucket())) ->isEqualTo([42]); } public function case_attach_to_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']), $callable = function () { } ) ->exception(function () use ($listener, $listenerId, $callable) { $listener->attach($listenerId, $callable); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_detach() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']), $callable = function () { return 42; }, $listener->attach($listenerId, $callable) ) ->when($result = $listener->detach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener) ->array($listener->fire($listenerId, new LUT\Bucket())) ->isEmpty(); } public function case_detach_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']), $callable = function () { } ) ->when($result = $listener->detach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener); } public function case_detach_all() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']) ) ->when($result = $listener->detachAll($listenerId)) ->then ->object($result) ->isIdenticalTo($listener) ->boolean($listener->listenerExists($listenerId)) ->isFalse(); } public function case_detach_all_with_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']) ) ->when($result = $listener->detachAll($listenerId)) ->then ->object($result) ->isIdenticalTo($listener); } public function case_listener_exists() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = [], $listener = new SUT($source, $ids) ) ->when($listener->addIds(['foo'])) ->then ->boolean($listener->listenerExists('foo')) ->isTrue() ->boolean($listener->listenerExists('bar')) ->isFalse() ->when($listener->addIds(['bar'])) ->then ->boolean($listener->listenerExists('bar')) ->isTrue(); } public function case_fire() { $self = $this; $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = ['foo', 'bar'], $listener = new SUT($source, $ids), $listenerId = 'foo', $bucket = new LUT\Bucket(), $listener->attach( $listenerId, function (LUT\Bucket $receivedBucket) use ($self, $bucket, $source, &$called) { $called = true; $self ->object($receivedBucket) ->isIdenticalTo($bucket) ->object($receivedBucket->getSource()) ->isIdenticalTo($source); return 42; } ) ) ->when($result = $listener->fire($listenerId, $bucket)) ->then ->array($result) ->isEqualTo([42]) ->boolean($called) ->isTrue(); } public function case_fire_an_undefined_listenerId() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = [], $listener = new SUT($source, $ids) ) ->exception(function () use ($listener) { $listener->fire('foo', new LUT\Bucket()); }) ->isInstanceOf('Hoa\Event\Exception'); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Event\Bucket') ->string($result->getData()) ->isEqualTo('foo'); } public function case_send() { $self = $this; $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), LUT::register($eventId, $source), $bucket = new SUT('foo'), LUT::getEvent($eventId)->attach( function (SUT $receivedBucket) use ($self, $bucket, &$called) { $called = true; $self ->object($receivedBucket) ->isIdenticalTo($bucket); } ) ) ->when($result = $bucket->send($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_set_source() { $this ->given( $bucket = new SUT(), $sourceA = new \Mock\Hoa\Event\Source() ) ->when($result = $bucket->setSource($sourceA)) ->then ->variable($result) ->isNull() ->object($bucket->getSource()) ->isIdenticalTo($sourceA) ->given($sourceB = new \Mock\Hoa\Event\Source()) ->when($result = $bucket->setSource($sourceB)) ->then ->object($result) ->isIdenticalTo($sourceA) ->object($bucket->getSource()) ->isIdenticalTo($sourceB); } public function case_set_data() { $this ->given( $bucket = new SUT(), $datumA = 'foo' ) ->when($result = $bucket->setData($datumA)) ->then ->variable($result) ->isNull() ->string($bucket->getData()) ->isEqualTo($datumA) ->given($datumB = 'bar') ->when($result = $bucket->setData($datumB)) ->then ->string($result) ->isEqualTo($datumA) ->string($bucket->getData()) ->isEqualTo($datumB); } } given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']) ) ->when($result = $listenable->_setListener($listener)) ->then ->variable($result) ->isNull(); } public function case_get_listener() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener) ) ->when($result = $listenable->_getListener()) ->then ->object($result) ->isIdenticalTo($listener); } public function case_on() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener), $callable = function () use (&$called) { $called = true; return 42; } ) ->when($result = $listenable->on('foo', $callable)) ->then ->object($result) ->isIdenticalTo($listenable) ->when($listenable->doSomethingThatFires()) ->then ->boolean($called) ->isTrue(); } public function case_on_unregistered_listener() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener) ) ->exception(function () use ($listenable) { $listenable->on('bar', null); }) ->isInstanceOf('Hoa\Event\Exception'); } } class _Listenable implements LUT\Listenable { use SUT; public function _setListener(LUT\Listener $listener) { return $this->setListener($listener); } public function _getListener() { return $this->getListener(); } public function doSomethingThatFires() { $this->getListener()->fire('foo', new LUT\Bucket('bar')); return; } } when($result = new \Mock\Hoa\Event\Listenable()) ->then ->object($result) ->isInstanceOf('Hoa\Event\Listenable'); } } given($eventId = 'hoa://Event/Test') ->when($result = SUT::getEvent($eventId)) ->then ->object($result) ->isInstanceOf('Hoa\Event\Event') ->object(SUT::getEvent($eventId)) ->isIdenticalTo($result); } public function case_register_source_instance() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source() ) ->when($result = SUT::register($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean(SUT::eventExists($eventId)) ->isTrue(); } public function case_register_source_name() { $this ->given( $eventId = 'hoa://Event/Test', $source = 'Mock\Hoa\Event\Source' ) ->when($result = SUT::register($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean(SUT::eventExists($eventId)) ->isTrue(); } public function case_register_redeclare() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->exception(function () use ($eventId, $source) { SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_register_not_a_source_instance() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \StdClass() ) ->exception(function () use ($eventId, $source) { $result = SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_register_not_a_source_name() { $this ->given( $eventId = 'hoa://Event/Test', $source = 'StdClass' ) ->exception(function () use ($eventId, $source) { $result = SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_unregister() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->when($result = SUT::unregister($eventId)) ->then ->boolean(SUT::eventExists($eventId)) ->isFalse(); } public function case_unregister_hard() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source), $event = SUT::getEvent($eventId) ) ->when($result = SUT::unregister($eventId, true)) ->then ->boolean(SUT::eventExists($eventId)) ->isFalse() ->object(SUT::getEvent($eventId)) ->isNotIdenticalTo($event); } public function case_unregister_not_registered() { $this ->given($eventId = 'hoa://Event/Test') ->when($result = SUT::unregister($eventId)) ->then ->variable($result) ->isNull(); } public function case_attach() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { } ) ->when($result = $event->attach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isTrue(); } public function case_detach() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { }, $event->attach($callable) ) ->when($result = $event->detach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isFalse(); } public function case_detach_unattached() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { } ) ->when($result = $event->detach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isFalse(); } public function case_is_listened() { $this ->given($event = SUT::getEvent('hoa://Event/Test')) ->when($result = $event->isListened()) ->then ->boolean($event->isListened()) ->isFalse(); } public function case_notify() { $self = $this; $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), $bucket = new LUT\Bucket(), SUT::register($eventId, $source), SUT::getEvent($eventId)->attach( function (LUT\Bucket $receivedBucket) use ($self, $source, $bucket, &$called) { $called = true; $this ->object($receivedBucket) ->isIdenticalTo($bucket) ->object($receivedBucket->getSource()) ->isIdenticalTo($source); } ) ) ->when($result = SUT::notify($eventId, $source, $bucket)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_notify_unregistered_event_id() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), $data = new LUT\Bucket() ) ->exception(function () use ($eventId, $source, $data) { SUT::notify($eventId, $source, $data); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_event_exists() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->when($result = SUT::eventExists($eventId)) ->then ->boolean($result) ->isTrue(); } public function case_event_not_exists() { $this ->given($eventId = 'hoa://Event/Test') ->when($result = SUT::eventExists($eventId)) ->then ->boolean($result) ->isFalse(); } } setData($data); return; } public function send($eventId, Source $source) { return Event::notify($eventId, $source, $this); } public function setSource(Source $source) { $old = $this->_source; $this->_source = $source; return $old; } public function getSource() { return $this->_source; } public function setData($data) { $old = $this->_data; $this->_data = $data; return $old; } public function getData() { return $this->_data; } } getListener(); if (null === $listener) { throw new Exception( 'Cannot attach a callable to the listener %s because ' . 'it has not been initialized yet.', 0, get_class($this) ); } $listener->attach($listenerId, $callable); return $this; } protected function setListener(Listener $listener) { $old = $this->_listener; $this->_listener = $listener; return $old; } protected function getListener() { return $this->_listener; } } new self(), self::KEY_SOURCE => null ]; } return self::$_register[$eventId][self::KEY_EVENT]; } public static function register($eventId, $source) { if (true === self::eventExists($eventId)) { throw new Exception( 'Cannot redeclare an event with the same ID, i.e. the event ' . 'ID %s already exists.', 0, $eventId ); } if (is_object($source) && !($source instanceof Source)) { throw new Exception( 'The source must implement \Hoa\Event\Source ' . 'interface; given %s.', 1, get_class($source) ); } else { $reflection = new \ReflectionClass($source); if (false === $reflection->implementsInterface('\Hoa\Event\Source')) { throw new Exception( 'The source must implement \Hoa\Event\Source ' . 'interface; given %s.', 2, $source ); } } if (!isset(self::$_register[$eventId][self::KEY_EVENT])) { self::$_register[$eventId][self::KEY_EVENT] = new self(); } self::$_register[$eventId][self::KEY_SOURCE] = $source; return; } public static function unregister($eventId, $hard = false) { if (false !== $hard) { unset(self::$_register[$eventId]); } else { self::$_register[$eventId][self::KEY_SOURCE] = null; } return; } public function attach($callable) { $callable = xcallable($callable); $this->_callable[$callable->getHash()] = $callable; return $this; } public function detach($callable) { unset($this->_callable[xcallable($callable)->getHash()]); return $this; } public function isListened() { return !empty($this->_callable); } public static function notify($eventId, Source $source, Bucket $data) { if (false === self::eventExists($eventId)) { throw new Exception( 'Event ID %s does not exist, cannot send notification.', 3, $eventId ); } $data->setSource($source); $event = self::getEvent($eventId); foreach ($event->_callable as $callable) { $callable($data); } return; } public static function eventExists($eventId) { return array_key_exists($eventId, self::$_register) && self::$_register[$eventId][self::KEY_SOURCE] !== null; } } Consistency::flexEntity('Hoa\Event\Event'); array_fill(-1, $n - $m + $k + 3, -2)]; for ($q = 0, $max = $k - 1; $q <= $max; ++$q) { $L[$q][-$q - 1] = $L[$q][-$q - 2] = $q - 1; } for ($q = 0; $q <= $k; ++$q) { for ($d = -$q, $max = $n - $m + $k - $q; $d <= $max; ++$d) { $l = min( max( $L[$q - 1][$d - 1], $L[$q - 1][$d ] + 1, $L[$q - 1][$d + 1] + 1 ), $m - 1 ); $a = substr($x, $l + 1, $m - $l); $b = substr($y, $l + 1 + $d, $n - $l - $d); $L[$q][$d] = $l + static::lcp($a, $b); if ($L[$q][$d] == $m - 1 || $d + $L[$q][$d] == $n - 1) { $j = $m + $d; $i = max(0, $j - $m); $offset[$q][] = ['i' => $i, 'j' => $j, 'l' => $j - $i]; } } } return empty($offset) ? $offset : $offset[$k]; } public static function lcp($x, $y) { $max = min(strlen($x), strlen($y)); $i = 0; while ($i < $max && $x[$i] == $y[$i]) { ++$i; } return $i; } } when($result = LUT::toCode(chr(160))) ->then ->integer($result) ->isEqualTo(0xa0); } } given( $x = 'GATAA', $y = 'CAGATAAGAGAA', $k = 1 ) ->when($result = LUT\Search::approximated($y, $x, $k)) ->then ->array($result) ->isEqualTo([ 0 => [ 'i' => 1, 'j' => 6, 'l' => 5 ], 1 => [ 'i' => 2, 'j' => 7, 'l' => 5 ], 2 => [ 'i' => 3, 'j' => 8, 'l' => 5 ], 3 => [ 'i' => 7, 'j' => 12, 'l' => 5 ] ]); } } given($this->function->function_exists = true) ->then ->boolean(LUT::checkMbString()) ->isTrue(); } public function case_check_no_mbstring() { $this ->given( $this->function->function_exists = function ($name) { return 'mb_substr' !== $name; } ) ->exception(function () { new LUT(); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_append_ltr() { $this ->given($string = new LUT('je')) ->when($result = $string->append(' t\'aime')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_append_rtl() { $this ->given($string = new LUT('أ')) ->when($result = $string->append('حبك')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك'); } public function case_prepend_ltr() { $this ->given($string = new LUT(' t\'aime')) ->when($result = $string->prepend('je')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_prepend_rtl() { $this ->given($string = new LUT('ك')) ->when($result = $string->prepend('أحب')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك'); } public function case_pad_beginning_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('👍 💩 😄 ❤️ 👍 je t\'aime'); } public function case_pad_beginning_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('👍 💩 😄 ❤️ 👍 💩 😄 ❤أحبك'); } public function case_pad_end_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime👍 💩 😄 ❤️ 👍 '); } public function case_pad_end_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك👍 💩 😄 ❤️ 👍 💩 😄 ❤'); } public function case_compare_no_collator() { $this ->given( $this->function->class_exists = function ($name) { return 'Collator' !== $name; }, $string = new LUT('b') ) ->case_compare(); } public function case_compare() { $this ->given($string = new LUT('b')) ->when($result = $string->compare('a')) ->then ->integer($result) ->isEqualTo(1) ->when($result = $string->compare('b')) ->then ->integer($result) ->isEqualTo(0) ->when($result = $string->compare('c')) ->then ->integer($result) ->isEqualTo(-1); } public function case_collator() { $this ->given( $this->function->setlocale = 'fr_FR', $collator = LUT::getCollator() ) ->when($result = $collator->getLocale(\Locale::VALID_LOCALE)) ->then ->string($result) ->isEqualTo('fr'); } public function case_safe_unsafe_pattern() { $this ->given($pattern = '/foo/i') ->when($result = LUT::safePattern($pattern)) ->then ->string($result) ->isEqualto('/foo/iu'); } public function case_safe_safe_pattern() { $this ->given($pattern = '/foo/ui') ->when($result = LUT::safePattern($pattern)) ->then ->string($result) ->isEqualto('/foo/ui'); } public function case_match_default() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([ 0 => '💩' ]); } public function case_match_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches, 0, 0)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([0 => '💩']) ->when($result = $string->match($pattern, $matches, 0, 4)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([0 => '💩']) ->when($result = $string->match($pattern, $matches, 0, 5)) ->then ->integer($result) ->isEqualTo(0) ->array($matches) ->isEmpty(); } public function case_match_with_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches, $string::WITH_OFFSET)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => 4 ] ]); } public function case_match_all_default() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, 0, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_match_all_with_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::WITH_OFFSET, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => [ 0 => '💩', 1 => 4 ], 1 => [ 0 => '💩', 1 => 13 ] ] ]); } public function case_match_all_grouped_by_pattern() { $this ->given( $pattern = '/(💩)/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::GROUP_BY_PATTERN, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ], 1 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_match_all_grouped_by_tuple() { $this ->given( $pattern = '/(💩)/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::GROUP_BY_TUPLE, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ], 1 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_replace() { $this ->given($string = new LUT('❤️ 💩 💩')) ->when($result = $string->replace('/💩/u', '😄')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️ 😄 😄'); } public function case_replace_limited() { $this ->given($string = new LUT('❤️ 💩 💩')) ->when($result = $string->replace('/💩/u', '😄', 1)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️ 😄 💩'); } public function case_split_default() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/')) ->then ->array($result) ->isEqualTo([ 0 => '❤️', 1 => '❤️', 2 => '❤️' ]); } public function case_split_default_limited() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', 1)) ->then ->array($result) ->isEqualTo([ 0 => '❤️💩❤️💩❤️' ]); } public function case_split_with_delimiters() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', -1, $string::WITH_DELIMITERS)) ->then ->array($result) ->isEqualTo([ 0 => '❤️', 1 => '❤️', 2 => '❤️' ]); } public function case_split_with_offset() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', -1, $string::WITH_OFFSET)) ->then ->array($result) ->isEqualTo([ 0 => [ 0 => '❤️', 1 => 0 ], 1 => [ 0 => '❤️', 1 => 10 ], 2 => [ 0 => '❤️', 1 => 20 ] ]); } public function case_iterator_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = iterator_to_array($string)) ->then ->array($result) ->isEqualTo([ 'j', 'e', ' ', 't', '\'', 'a', 'i', 'm', 'e' ]); } public function case_iterator_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = iterator_to_array($string)) ->then ->array($result) ->isEqualTo([ 'أ', 'ح', 'ب', 'ك' ]); } public function case_to_lower() { $this ->given($string = new LUT('Σ \'ΑΓΑΠΏ')) ->when($result = $string->toLowerCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('σ \'αγαπώ') ->given($string = new LUT('JE T\'AIME')) ->when($result = $string->toLowerCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_to_upper() { $this ->given($string = new LUT('σ \'αγαπώ')) ->when($result = $string->toUpperCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Σ \'ΑΓΑΠΏ') ->given($string = new LUT('je t\'aime')) ->when($result = $string->toUpperCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('JE T\'AIME'); } public function case_trim_default() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️'); } public function case_trim_beginning() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩', $string::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️💩💩'); } public function case_trim_end() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩', $string::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('💩💩❤️'); } public function case_offset_get_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string[0]) ->then ->string($result) ->isEqualTo('j') ->when($result = $string[-1]) ->then ->string($result) ->isEqualTo('e'); } public function case_offset_get_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string[0]) ->then ->string($result) ->isEqualTo('أ') ->when($result = $string[-1]) ->then ->string($result) ->isEqualTo('ك'); } public function case_offset_set() { $this ->given($string = new LUT('أحبﻙ')) ->when($string[-1] = 'ك') ->then ->string((string) $string) ->isEqualTo('أحبك'); } public function case_offset_unset() { $this ->given($string = new LUT('أحبك😄')) ->when(function () use ($string) { unset($string[-1]); }) ->then ->string((string) $string) ->isEqualTo('أحبك'); } public function case_reduce() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->reduce(0, 1)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أ'); } public function case_count() { $this ->given($string = new LUT('je t\'aime')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(9) ->given($string = new LUT('أحبك')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(4) ->given($string = new LUT('💩')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(1); } public function case_byte_at() { $this ->given($string = new LUT('💩')) ->when($result = $string->getByteAt(0)) ->then ->integer(ord($result)) ->isEqualTo(0xf0) ->when($result = $string->getByteAt(1)) ->then ->integer(ord($result)) ->isEqualTo(0x9f) ->when($result = $string->getByteAt(2)) ->then ->integer(ord($result)) ->isEqualTo(0x92) ->when($result = $string->getByteAt(3)) ->then ->integer(ord($result)) ->isEqualTo(0xa9) ->when($result = $string->getByteAt(-1)) ->then ->integer(ord($result)) ->isEqualTo(0xa9); } public function case_bytes_length() { $this ->given($string = new LUT('💩')) ->when($result = $string->getBytesLength()) ->then ->integer($result) ->isEqualTo(4); } public function case_get_width() { $this ->given($string = new LUT('💩')) ->when($result = $string->getWidth()) ->then ->integer($result) ->isEqualTo(1) ->given($string = new LUT('習')) ->when($result = $string->getWidth()) ->then ->integer($result) ->isEqualTo(2); } public function case_get_char_direction() { $this ->when($result = LUT::getCharDirection('A')) ->then ->integer($result) ->isEqualTo(LUT::LTR) ->when($result = LUT::getCharDirection('ا')) ->then ->integer($result) ->isEqualTo(LUT::RTL); } public function case_get_char_width() { $this ->given( $data = [ [0x0, 0], [0x19, -1], [0x7f, -1], [0x9f, -1], [0xa0, 1], [0x300, 0], [0x488, 0], [0x600, 0], [0xad, 1], [0x1160, 0], [0x11ff, 0], [0x200b, 0], [0x1100, 2], [0x2160, 1], [0x3f60, 2], [0x303f, 1], [0x2329, 2], [0xaed0, 2], [0x232a, 2], [0xffa4, 1], [0xfe10, 2], [0xfe30, 2], [0xff00, 2], [0xf900, 2] ] ) ->when(function () use ($data) { foreach ($data as $datum) { list($code, $width) = $datum; $this ->when($result = LUT::getCharWidth(LUT::fromCode($code))) ->then ->integer($result) ->isEqualTo($width); } }); } public function case_is_char_printable() { $this ->when($result = LUT::isCharPrintable(LUT::fromCode(0x7f))) ->then ->boolean($result) ->isFalse() ->when($result = LUT::isCharPrintable(LUT::fromCode(0xa0))) ->then ->boolean($result) ->isTrue() ->when($result = LUT::isCharPrintable(LUT::fromCode(0x1100))) ->then ->boolean($result) ->isTrue(); } public function case_from_code() { $this ->when($result = LUT::fromCode(0x7e)) ->then ->string($result) ->isEqualTo('~') ->when($result = LUT::fromCode(0xa7)) ->then ->string($result) ->isEqualTo('§') ->when($result = LUT::fromCode(0x1207)) ->then ->string($result) ->isEqualTo('ሇ') ->when($result = LUT::fromCode(0x1f4a9)) ->then ->string($result) ->isEqualTo('💩'); } public function case_to_code() { $this ->when($result = LUT::toCode('~')) ->then ->integer($result) ->isEqualTo(0x7e) ->when($result = LUT::toCode('§')) ->then ->integer($result) ->isEqualTo(0xa7) ->when($result = LUT::toCode('ሇ')) ->then ->integer($result) ->isEqualTo(0x1207) ->when($result = LUT::toCode('💩')) ->then ->integer($result) ->isEqualTo(0x1f4a9); } public function case_to_binary_code() { $this ->when($result = LUT::toBinaryCode('~')) ->then ->string($result) ->isEqualTo('01111110') ->when($result = LUT::toBinaryCode('§')) ->then ->string($result) ->isEqualTo('1100001010100111') ->when($result = LUT::toBinaryCode('ሇ')) ->then ->string($result) ->isEqualTo('111000011000100010000111') ->when($result = LUT::toBinaryCode('💩')) ->then ->string($result) ->isEqualTo('11110000100111111001001010101001'); } public function case_transcode_no_iconv() { $this ->given( $this->function->function_exists = function ($name) { return 'iconv' !== $name; } ) ->exception(function () { LUT::transcode('foo', 'UTF-8'); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_transcode_and_isUtf8() { $this ->given($uΣ = 'Σ') ->when($Σ = LUT::transcode($uΣ, 'UTF-8', 'UTF-16')) ->then ->string($Σ) ->isNotEqualTo($uΣ) ->boolean(LUT::isUtf8($Σ)) ->isFalse() ->when($Σ = LUT::transcode($Σ, 'UTF-16', 'UTF-8')) ->string($Σ) ->isEqualTo($uΣ) ->boolean(LUT::isUtf8($Σ)) ->isTrue() ->boolean(LUT::isUtf8($uΣ)) ->isTrue(); } public function case_to_ascii_no_transliterator_no_normalizer() { $this ->given( $this->function->class_exists = function ($name) { return false === in_array($name, ['Transliterator', 'Normalizer']); }, $string = new LUT('Un été brûlant sur la côte') ) ->exception(function () use ($string) { $string->toAscii(); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_to_ascii_no_transliterator_no_normalizer_try() { $this ->given( $this->function->class_exists = function ($name) { return false === in_array($name, ['Transliterator', 'Normalizer']); }, $string = new LUT('Un été brûlant sur la côte') ) ->when($result = $string->toAscii(true)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Un ete brulant sur la cote'); } public function case_to_ascii_no_transliterator() { $this ->given( $this->function->class_exists = function ($name) { return 'Transliterator' !== $name; }, $string = new LUT('Un été brûlant sur la côte') ) ->when($result = $string->toAscii()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Un ete brulant sur la cote'); } public function case_to_ascii() { $this ->given( $strings = [ 'Un été brûlant sur la côte' => 'Un ete brulant sur la cote', 'Αυτή είναι μια δοκιμή' => 'Aute einai mia dokime', 'أحبك' => 'ahbk', 'キャンパス' => 'kyanpasu', 'биологическом' => 'biologiceskom', '정, 병호' => 'jeong, byeongho', 'ますだ, よしひこ' => 'masuda, yoshihiko', 'मोनिच' => 'monica', 'क्ष' => 'ksa', 'أحبك 😀' => 'ahbk (grinning face)', '∀ i ∈ ℕ' => '(for all) i (element of) N' ] ) ->when(function () use ($strings) { foreach ($strings as $original => $asciied) { $this ->given($string = new LUT($original)) ->when($result = $string->toAscii()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo($asciied); } }); } public function case_copy() { $this ->given($string = new LUT('foo')) ->when($result = $string->copy()) ->then ->object($result) ->isEqualTo($string); } public function case_toString() { $this ->given($datum = $this->sample($this->realdom->regex('/\w{7,42}/'))) ->when($result = new LUT($datum)) ->then ->castToString($result) ->isEqualTo($datum); } } getOption($v)) { switch ($c) { case 'b': $base = intval($v); break; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } $this->parser->listInputs($char); $code = base_convert((string) Ustring::toCode($char), 10, $base); echo $code, "\n"; return; } public function usage() { echo 'Usage : ustring:tocode ', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'b' => 'Get the code in a specific base (16 by default).', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Transform a character into its code. getOption($v)) { switch ($c) { case 'b': $base = intval($v); break; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } $this->parser->listInputs($code); $char = Ustring::fromCode(base_convert($code, $base, 10)); echo $char; return; } public function usage() { echo 'Usage : ustring:fromcode ', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'b' => 'Specify the base of the code (16 by default).', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Get a character from its code. append($string); } return; } public static function checkMbString() { return function_exists('mb_substr'); } public static function checkIconv() { return function_exists('iconv'); } public function append($substring) { $this->_string .= $substring; return $this; } public function prepend($substring) { $this->_string = $substring . $this->_string; return $this; } public function pad($length, $piece, $side = self::END) { $difference = $length - $this->count(); if (0 >= $difference) { return $this; } $handle = null; for ($i = $difference / mb_strlen($piece) - 1; $i >= 0; --$i) { $handle .= $piece; } $handle .= mb_substr($piece, 0, $difference - mb_strlen($handle)); return static::END === $side ? $this->append($handle) : $this->prepend($handle); } public function compare($string) { if (null === $collator = static::getCollator()) { return strcmp($this->_string, (string) $string); } return $collator->compare($this->_string, $string); } public static function getCollator() { if (false === class_exists('Collator')) { return null; } if (null === static::$_collator) { static::$_collator = new \Collator(setlocale(LC_COLLATE, null)); } return static::$_collator; } public static function safePattern($pattern) { $delimiter = mb_substr($pattern, 0, 1); $options = mb_substr( mb_strrchr($pattern, $delimiter, false), mb_strlen($delimiter) ); if (false === strpos($options, 'u')) { $pattern .= 'u'; } return $pattern; } public function match( $pattern, &$matches = null, $flags = 0, $offset = 0, $global = false ) { $pattern = static::safePattern($pattern); if (0 === $flags) { if (true === $global) { $flags = static::GROUP_BY_PATTERN; } } else { $flags &= ~PREG_SPLIT_OFFSET_CAPTURE; } $offset = strlen(mb_substr($this->_string, 0, $offset)); if (true === $global) { return preg_match_all( $pattern, $this->_string, $matches, $flags, $offset ); } return preg_match($pattern, $this->_string, $matches, $flags, $offset); } public function replace($pattern, $replacement, $limit = -1) { $pattern = static::safePattern($pattern); if (false === is_callable($replacement)) { $this->_string = preg_replace( $pattern, $replacement, $this->_string, $limit ); } else { $this->_string = preg_replace_callback( $pattern, $replacement, $this->_string, $limit ); } return $this; } public function split( $pattern, $limit = -1, $flags = self::WITHOUT_EMPTY ) { return preg_split( static::safePattern($pattern), $this->_string, $limit, $flags ); } public function getIterator() { return new \ArrayIterator(preg_split('#(?_string)); } public function toLowerCase() { $this->_string = mb_strtolower($this->_string); return $this; } public function toUpperCase() { $this->_string = mb_strtoupper($this->_string); return $this; } public function toAscii($try = false) { if (0 === preg_match('#[\x80-\xff]#', $this->_string)) { return $this; } $string = $this->_string; $transId = 'Any-Latin; ' . '[\p{S}] Name; ' . 'Latin-ASCII'; if (null !== $transliterator = static::getTransliterator($transId)) { $this->_string = preg_replace_callback( '#\\\N\{([A-Z ]+)\}#u', function (array $matches) { return '(' . strtolower($matches[1]) . ')'; }, $transliterator->transliterate($string) ); return $this; } if (false === class_exists('Normalizer')) { if (false === $try) { throw new Exception( '%s needs the class Normalizer to work properly, ' . 'or you can force a try by using %1$s(true).', 0, __METHOD__ ); } $string = static::transcode($string, 'UTF-8', 'ASCII//IGNORE//TRANSLIT'); $this->_string = preg_replace('#(?:[\'"`^](\w))#u', '\1', $string); return $this; } $string = \Normalizer::normalize($string, \Normalizer::NFKD); $string = preg_replace('#\p{Mn}+#u', '', $string); $this->_string = static::transcode($string, 'UTF-8', 'ASCII//IGNORE//TRANSLIT'); return $this; } public function transliterate($identifier, $start = 0, $end = null) { if (null === $transliterator = static::getTransliterator($identifier)) { throw new Exception( '%s needs the class Transliterator to work properly.', 1, __METHOD__ ); } $this->_string = $transliterator->transliterate($this->_string, $start, $end); return $this; } public static function getTransliterator($identifier) { if (false === class_exists('Transliterator')) { return null; } return \Transliterator::create($identifier); } public function trim($regex = '\s', $side = 3 ) { $regex = '(?:' . $regex . ')+'; $handle = null; if (0 !== ($side & static::BEGINNING)) { $handle .= '(^' . $regex . ')'; } if (0 !== ($side & static::END)) { if (null !== $handle) { $handle .= '|'; } $handle .= '(' . $regex . '$)'; } $this->_string = preg_replace('#' . $handle . '#u', '', $this->_string); $this->_direction = null; return $this; } protected function computeOffset($offset) { $length = mb_strlen($this->_string); if (0 > $offset) { $offset = -$offset % $length; if (0 !== $offset) { $offset = $length - $offset; } } elseif ($offset >= $length) { $offset %= $length; } return $offset; } public function offsetGet($offset) { return mb_substr($this->_string, $this->computeOffset($offset), 1); } public function offsetSet($offset, $value) { $head = null; $offset = $this->computeOffset($offset); if (0 < $offset) { $head = mb_substr($this->_string, 0, $offset); } $tail = mb_substr($this->_string, $offset + 1); $this->_string = $head . $value . $tail; $this->_direction = null; return $this; } public function offsetUnset($offset) { return $this->offsetSet($offset, null); } public function offsetExists($offset) { return true; } public function reduce($start, $length = null) { $this->_string = mb_substr($this->_string, $start, $length); return $this; } public function count() { return mb_strlen($this->_string); } public function getByteAt($offset) { $length = strlen($this->_string); if (0 > $offset) { $offset = -$offset % $length; if (0 !== $offset) { $offset = $length - $offset; } } elseif ($offset >= $length) { $offset %= $length; } return $this->_string[$offset]; } public function getBytesLength() { return strlen($this->_string); } public function getWidth() { return mb_strwidth($this->_string); } public function getDirection() { if (null === $this->_direction) { if (null === $this->_string) { $this->_direction = static::LTR; } else { $this->_direction = static::getCharDirection( mb_substr($this->_string, 0, 1) ); } } return $this->_direction; } public static function getCharDirection($char) { $c = static::toCode($char); if (!(0x5be <= $c && 0x10b7f >= $c)) { return static::LTR; } if (0x85e >= $c) { if (0x5be === $c || 0x5c0 === $c || 0x5c3 === $c || 0x5c6 === $c || (0x5d0 <= $c && 0x5ea >= $c) || (0x5f0 <= $c && 0x5f4 >= $c) || 0x608 === $c || 0x60b === $c || 0x60d === $c || 0x61b === $c || (0x61e <= $c && 0x64a >= $c) || (0x66d <= $c && 0x66f >= $c) || (0x671 <= $c && 0x6d5 >= $c) || (0x6e5 <= $c && 0x6e6 >= $c) || (0x6ee <= $c && 0x6ef >= $c) || (0x6fa <= $c && 0x70d >= $c) || 0x710 === $c || (0x712 <= $c && 0x72f >= $c) || (0x74d <= $c && 0x7a5 >= $c) || 0x7b1 === $c || (0x7c0 <= $c && 0x7ea >= $c) || (0x7f4 <= $c && 0x7f5 >= $c) || 0x7fa === $c || (0x800 <= $c && 0x815 >= $c) || 0x81a === $c || 0x824 === $c || 0x828 === $c || (0x830 <= $c && 0x83e >= $c) || (0x840 <= $c && 0x858 >= $c) || 0x85e === $c) { return static::RTL; } } elseif (0x200f === $c) { return static::RTL; } elseif (0xfb1d <= $c) { if (0xfb1d === $c || (0xfb1f <= $c && 0xfb28 >= $c) || (0xfb2a <= $c && 0xfb36 >= $c) || (0xfb38 <= $c && 0xfb3c >= $c) || 0xfb3e === $c || (0xfb40 <= $c && 0xfb41 >= $c) || (0xfb43 <= $c && 0xfb44 >= $c) || (0xfb46 <= $c && 0xfbc1 >= $c) || (0xfbd3 <= $c && 0xfd3d >= $c) || (0xfd50 <= $c && 0xfd8f >= $c) || (0xfd92 <= $c && 0xfdc7 >= $c) || (0xfdf0 <= $c && 0xfdfc >= $c) || (0xfe70 <= $c && 0xfe74 >= $c) || (0xfe76 <= $c && 0xfefc >= $c) || (0x10800 <= $c && 0x10805 >= $c) || 0x10808 === $c || (0x1080a <= $c && 0x10835 >= $c) || (0x10837 <= $c && 0x10838 >= $c) || 0x1083c === $c || (0x1083f <= $c && 0x10855 >= $c) || (0x10857 <= $c && 0x1085f >= $c) || (0x10900 <= $c && 0x1091b >= $c) || (0x10920 <= $c && 0x10939 >= $c) || 0x1093f === $c || 0x10a00 === $c || (0x10a10 <= $c && 0x10a13 >= $c) || (0x10a15 <= $c && 0x10a17 >= $c) || (0x10a19 <= $c && 0x10a33 >= $c) || (0x10a40 <= $c && 0x10a47 >= $c) || (0x10a50 <= $c && 0x10a58 >= $c) || (0x10a60 <= $c && 0x10a7f >= $c) || (0x10b00 <= $c && 0x10b35 >= $c) || (0x10b40 <= $c && 0x10b55 >= $c) || (0x10b58 <= $c && 0x10b72 >= $c) || (0x10b78 <= $c && 0x10b7f >= $c)) { return static::RTL; } } return static::LTR; } public static function getCharWidth($char) { $char = (string) $char; $c = static::toCode($char); if (0x0 === $c) { return 0; } if (0x20 > $c || (0x7f <= $c && $c < 0xa0)) { return -1; } if (0xad !== $c && 0 !== preg_match('#^[\p{Mn}\p{Me}\p{Cf}\x{1160}-\x{11ff}\x{200b}]#u', $char)) { return 0; } return 1 + (0x1100 <= $c && (0x115f >= $c || 0x2329 === $c || 0x232a === $c || (0x2e80 <= $c && 0xa4cf >= $c && 0x303f !== $c) || (0xac00 <= $c && 0xd7a3 >= $c) || (0xf900 <= $c && 0xfaff >= $c) || (0xfe10 <= $c && 0xfe19 >= $c) || (0xfe30 <= $c && 0xfe6f >= $c) || (0xff00 <= $c && 0xff60 >= $c) || (0xffe0 <= $c && 0xffe6 >= $c) || (0x20000 <= $c && 0x2fffd >= $c) || (0x30000 <= $c && 0x3fffd >= $c))); } public static function isCharPrintable($char) { return 1 <= static::getCharWidth($char); } public static function fromCode($code) { return mb_convert_encoding( '&#x' . dechex($code) . ';', 'UTF-8', 'HTML-ENTITIES' ); } public static function toCode($char) { $char = (string) $char; $code = ord($char[0]); $bytes = 1; if (!($code & 0x80)) { return $code; } if (($code & 0xe0) === 0xc0) { $bytes = 2; $code = $code & ~0xc0; } elseif (($code & 0xf0) == 0xe0) { $bytes = 3; $code = $code & ~0xe0; } elseif (($code & 0xf8) === 0xf0) { $bytes = 4; $code = $code & ~0xf0; } for ($i = 2; $i <= $bytes; $i++) { $code = ($code << 6) + (ord($char[$i - 1]) & ~0x80); } return $code; } public static function toBinaryCode($char) { $char = (string) $char; $out = null; for ($i = 0, $max = strlen($char); $i < $max; ++$i) { $out .= vsprintf('%08b', ord($char[$i])); } return $out; } public static function transcode($string, $from, $to = 'UTF-8') { if (false === static::checkIconv()) { throw new Exception( '%s needs the iconv extension.', 2, __CLASS__ ); } return iconv($from, $to, $string); } public static function isUtf8($string) { return (bool) preg_match('##u', $string); } public function copy() { return clone $this; } public function __toString() { return $this->_string; } } Consistency::flexEntity('Hoa\Ustring\Ustring'); if (false === Ustring::checkMbString()) { throw new Exception( '%s needs the mbstring extension.', 0, __NAMESPACE__ . '\Ustring' ); } initialize(); return; } public static function getInstance() { if (null === static::$_instance) { static::$_instance = new static(); } return static::$_instance; } protected function initialize() { $root = dirname(dirname(__DIR__)); $cwd = 'cli' === PHP_SAPI ? dirname(realpath($_SERVER['argv'][0])) : getcwd(); $this[] = new Node( 'Application', $cwd . DS, [ new Node('Public', 'Public' . DS) ] ); $this[] = new Node( 'Data', dirname($cwd) . DS, [ new Node( 'Etc', 'Etc' . DS, [ new Node('Configuration', 'Configuration' . DS), new Node('Locale', 'Locale' . DS) ] ), new Node('Lost+found', 'Lost+found' . DS), new Node('Temporary', 'Temporary' . DS), new Node( 'Variable', 'Variable' . DS, [ new Node('Cache', 'Cache' . DS), new Node('Database', 'Database' . DS), new Node('Log', 'Log' . DS), new Node('Private', 'Private' . DS), new Node('Run', 'Run' . DS), new Node('Test', 'Test' . DS) ] ) ] ); $this[] = new Node\Library( 'Library', $root . DS . 'Hoathis' . DS . RS . $root . DS . 'Hoa' . DS ); return; } public function resolve($path, $exists = true, $unfold = false) { if (substr($path, 0, 6) !== 'hoa://') { if (true === is_dir($path)) { $path = rtrim($path, '/\\'); if (0 === strlen($path)) { $path = '/'; } } return $path; } if (isset(self::$_cache[$path])) { $handle = self::$_cache[$path]; } else { $out = $this->_resolve($path, $handle); if (!is_array($handle)) { return $out; } $handle = array_values(array_unique($handle, SORT_REGULAR)); foreach ($handle as &$entry) { if (true === is_dir($entry)) { $entry = rtrim($entry, '/\\'); if (0 === strlen($entry)) { $entry = '/'; } } } self::$_cache[$path] = $handle; } if (true === $unfold) { if (true !== $exists) { return $handle; } $out = []; foreach ($handle as $solution) { if (file_exists($solution)) { $out[] = $solution; } } return $out; } if (true !== $exists) { return $handle[0]; } foreach ($handle as $solution) { if (file_exists($solution)) { return $solution; } } return static::NO_RESOLUTION; } public static function clearCache() { self::$_cache = []; return; } } Consistency::flexEntity('Hoa\Protocol\Protocol'); when($result = SUT::getInstance()) ->then ->object($result) ->isInstanceOf('Hoa\Protocol\Node'); } public function case_default_tree() { $this ->when($result = SUT::getInstance()) ->then ->object($result['Application'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Application']['Public'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc']['Configuration'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc']['Locale'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Lost+found'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Temporary'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Cache'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Database'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Log'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Private'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Run'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Test'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Library'])->isInstanceOf('Hoa\Protocol\Node\Library') ->string($result['Library']->reach()) ->isEqualTo( dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoathis' . DS . RS . dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoa' . DS ); } public function case_resolve_not_a_hoa_path() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('/foo/bar')) ->then ->string($result) ->isEqualTo('/foo/bar'); } public function case_resolve_to_non_existing_resource() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Application/Foo/Bar')) ->then ->string($result) ->isEqualTo(SUT::NO_RESOLUTION); } public function case_resolve_does_not_test_if_exists() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Application/Foo/Bar', false)) ->then ->string($result) ->isEqualTo('/Foo/Bar'); } public function case_resolve_unfold_to_existing_resources() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Library', true, true)) ->then ->array($result) ->contains( dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoa' ); } public function case_resolve_unfold_to_non_existing_resources() { $this ->given( $parentHoaDirectory = dirname(dirname(dirname(dirname(__DIR__)))), $protocol = SUT::getInstance() ) ->when($result = $protocol->resolve('hoa://Library', false, true)) ->then ->array($result) ->isEqualTo([ $parentHoaDirectory . DS . 'hoathis', $parentHoaDirectory . DS . 'hoa' ]); } } given($wrapper = new SUT()) ->when($result = $wrapper->stream_cast(STREAM_CAST_FOR_SELECT)) ->then ->boolean($result) ->isFalse(); } public function case_stream_cast_as_stream() { $this ->given($wrapper = new SUT()) ->when($result = $wrapper->stream_cast(STREAM_CAST_AS_STREAM)) ->then ->boolean($result) ->isFalse(); } public function case_stream_close() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_close()) ->then ->variable($result) ->isNull() ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_stream_not_eof() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper, 'foo'), fseek($wrapper->getStream(), 0, SEEK_SET) ) ->when($result = $wrapper->stream_eof()) ->then ->boolean($result) ->isFalse(); } public function case_stream_eof() { $this ->given( $this->function->feof = true, $wrapper = new SUT() ) ->when($result = $wrapper->stream_eof()) ->then ->boolean($result) ->isTrue(); } public function case_stream_flush() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_flush()) ->then ->boolean($result) ->isTrue(); } public function _case_stream_xxx_lock($operation) { $this ->given( $this->function->flock = function ($resource, $operation) use (&$_resource, &$_operation) { $_resource = $resource; $_operation = $operation; if ($operation === LOCK_NB) { return true; } return flock($resource, $operation); }, $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_lock($operation)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($_operation) ->isEqualTo($operation); } public function case_stream_shared_lock() { return $this->_case_stream_xxx_lock(LOCK_SH); } public function case_stream_exclusive_lock() { return $this->_case_stream_xxx_lock(LOCK_EX); } public function case_stream_release_lock() { return $this->_case_stream_xxx_lock(LOCK_UN); } public function case_stream_not_blocking_lock() { return $this->_case_stream_xxx_lock(LOCK_NB); } public function _case_metadata_touch_with_xxx_arguments($arguments, $path, $time, $atime) { $this ->given( $this->function->touch = function ($path, $time, $atime) use (&$_path, &$_time, &$_atime) { $_path = $path; $_time = $time; $_atime = $atime; return true; }, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata($path, STREAM_META_TOUCH, $arguments)) ->then ->boolean($result) ->isTrue() ->string($_path) ->isEqualTo($path) ->variable($_time) ->isEqualTo($time) ->variable($_atime) ->isEqualTo($atime); } public function case_metadata_touch_with_no_argument() { return $this->_case_metadata_touch_with_xxx_arguments([], 'foo', null, null); } public function case_metadata_touch_with_time() { return $this->_case_metadata_touch_with_xxx_arguments([42], 'foo', 42, null); } public function case_metadata_touch_with_time_and_atime() { return $this->_case_metadata_touch_with_xxx_arguments([42, 777], 'foo', 42, 777); } public function _case_metadata_owner_xxx($owner) { $this ->given( $this->function->chown = function ($path, $user) use (&$_path, &$_user) { $_path = $path; $_user = $user; return true; }, $path = 'foo', $user = 'gordon', $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $owner, $user)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->string($user) ->isEqualTo($_user); } public function case_metadata_owner() { return $this->_case_metadata_owner_xxx(STREAM_META_OWNER); } public function case_metadata_owner_name() { return $this->_case_metadata_owner_xxx(STREAM_META_OWNER_NAME); } public function _case_metadata_group_xxx($grp) { $this ->given( $this->function->chgrp = function ($path, $group) use (&$_path, &$_group) { $_path = $path; $_group = $group; return true; }, $path = 'foo', $group = 'root', $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $grp, $group)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->string($group) ->isEqualTo($_group); } public function case_metadata_group() { return $this->_case_metadata_group_xxx(STREAM_META_GROUP); } public function case_metadata_group_name() { return $this->_case_metadata_group_xxx(STREAM_META_GROUP_NAME); } public function case_metadata_access() { $this ->given( $this->function->chmod = function ($path, $mode) use (&$_path, &$_mode) { $_path = $path; $_mode = $mode; return true; }, $path = 'foo', $mode = 0755, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', STREAM_META_ACCESS, $mode)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->integer($mode) ->isEqualTo($_mode); } public function case_metadata_default() { $this ->given( $option = 0, $mode = 0, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $option, $mode)) ->then ->boolean($result) ->isFalse(); } public function case_stream_open() { $this ->given( $this->function->fopen = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options, &$_openedPath) { $_path = $path; $_mode = $mode; $_options = $options; return fopen($path, $mode, $options); }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Foo?type=file', $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isTrue() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->string($mode) ->isEqualTo($_mode) ->integer($options) ->isEqualTo($_options & STREAM_USE_PATH) ->resource($openedPath) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->string($wrapper->getStreamName()) ->isEqualTo('atoum://Foo'); } public function case_stream_open_not_hoa_protocol() { $this ->given( $wrapper = new SUT(), $path = LUT::NO_RESOLUTION, $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isFalse(); } public function case_stream_open_not_a_resource() { $this ->given( $this->function->fopen = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options, &$_openedPath) { $_path = $path; $_mode = $mode; $_options = $options; return fopen($path, $mode, $options); }, $this->function->is_resource = false, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Foo?type=file', $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isFalse() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->string($mode) ->isEqualTo($_mode) ->integer($options) ->isEqualTo($_options & STREAM_USE_PATH) ->resource($openedPath) ->isStream(); } public function case_stream_read() { $this ->given( $this->function->fread = function ($resource, $count) use (&$_resource, &$_count) { $_resource = $resource; $_count = $count; return fread($resource, $count); }, $wrapper = new SUT(), $count = 42, $this->openFile($wrapper, str_repeat('@', $count)) ) ->when($result = $wrapper->stream_read($count)) ->then ->string($result) ->hasLength($count) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($_count) ->isEqualTo($count); } public function _case_stream_seek_xxx($offset, $whence) { return $this ->given( $this->function->fseek = function ($resource, $offset, $whence) use (&$_resource, &$_offset, &$_whence) { $_resource = $resource; $_offset = $offset; $_whence = $whence; return fseek($resource, $offset, $whence); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foobar') ) ->when($result = $wrapper->stream_seek($offset, $whence)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($offset) ->isEqualTo($_offset) ->integer($whence) ->isEqualTo($_whence) ->integer(ftell($wrapper->getStream())); } public function case_stream_seek_set() { return $this ->_case_stream_seek_xxx(3, SEEK_SET) ->isEqualTo(3); } public function case_stream_seek_current() { return $this ->_case_stream_seek_xxx(4, SEEK_CUR) ->isEqualTo(4); } public function case_stream_seek_end() { return $this ->_case_stream_seek_xxx(-4, SEEK_END) ->isEqualTo(2); } public function case_stream_stat() { $this ->given( $this->function->fstat = function ($resource) use (&$_resource) { $_resource = $resource; return fstat($resource); }, $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_stat()) ->then ->array($result) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()); } public function case_stream_tell() { $this ->given( $this->function->ftell = function ($resource) use (&$_resource) { $_resource = $resource; return ftell($resource); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foo'), $wrapper->stream_seek(2) ) ->when($result = $wrapper->stream_tell()) ->then ->integer($result) ->isEqualTo(2) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()); } public function case_stream_truncate() { $this ->given( $this->function->ftruncate = function ($resource, $size) use (&$_resource, &$_size) { $_resource = $resource; $_size = $size; return ftruncate($resource, $size); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foobar'), $size = 3 ) ->when($result = $wrapper->stream_truncate($size)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($size) ->isEqualTo($_size) ->integer($wrapper->stream_tell()) ->isEqualTo(0) ->let($wrapper->stream_seek(0, SEEK_END)) ->integer($wrapper->stream_tell()) ->isEqualTo(3); } public function case_stream_write() { $this ->given( $this->function->fwrite = function ($resource, $data) use (&$_resource, &$_data) { $_resource = $resource; $_data = $data; return fwrite($resource, $data); }, $wrapper = new SUT(), $wrapper->stream_open('hoa://Test/Vfs/Foo?type=file', 'wb+', STREAM_USE_PATH, $openedPath), $data = 'foo' ) ->when($result = $wrapper->stream_write($data)) ->then ->integer($result) ->isEqualTo(strlen($data)) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->string($_data) ->isEqualTo($data) ->let($wrapper->stream_seek(0)) ->string($wrapper->stream_read(3)) ->isEqualTo($data); } public function case_dir_closedir() { $this ->given( $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->dir_closedir()) ->then ->variable($result) ->isNull() ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_dir_opendir() { $this ->given( $this->function->opendir = function ($path) use (&$_path) { $_path = $path; return opendir($path); }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Bar?type=directory', $options = 0 ) ->when($result = $wrapper->dir_opendir($path, $options)) ->then ->boolean($result) ->isTrue() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->resource($wrapper->getStream()) ->isStream() ->string($wrapper->getStreamName()) ->isEqualTo('atoum://Bar'); } public function case_dir_opendir_not_a_resource() { $this ->given( $this->function->opendir = function ($path) use (&$_path) { $_path = $path; return false; }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Bar?type=directory', $options = 0 ) ->when($result = $wrapper->dir_opendir($path, $options)) ->then ->boolean($result) ->isFalse() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_dir_readdir() { $this ->given( $this->function->readdir = function ($resource) use (&$_resource) { $_resource = $resource; return readdir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz', 'Qux']) ) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()); } public function case_dir_readdir_until_eod() { $this ->given( $this->function->readdir = function ($resource) use (&$_resource) { $_resource = $resource; return readdir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz', 'Qux']) ) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Qux') ->when($result = $wrapper->dir_readdir()) ->then ->boolean($result) ->isFalse(); } public function case_dir_rewinddir() { $this ->given( $this->function->rewinddir = function ($resource) use (&$_resource) { $_resource = $resource; return rewinddir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz']), $wrapper->dir_readdir() ) ->when($result = $wrapper->dir_rewinddir()) ->then ->variable($result) ->isNull() ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()); } public function case_dir_mkdir() { $this ->given( $this->function->mkdir = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options) { $_path = $path; $_mode = $mode; $_options = $options; return true; }, $wrapper = new SUT(), $this->openDirectory($wrapper), $path = 'Baz', $mode = 0755, $options = STREAM_MKDIR_RECURSIVE ) ->when($result = $wrapper->mkdir($path, $mode, $options)) ->then ->boolean($result) ->isTrue() ->string($_path) ->isEqualTo($path) ->integer($_mode) ->isEqualTo($_mode) ->integer($_options) ->isEqualTo($options | STREAM_MKDIR_RECURSIVE); } public function case_rename() { $this ->given( $this->function->rename = function ($from, $to) use (&$_from, &$_to) { $_to = $to; $_from = $from; return rename($from, $to); }, $wrapper = new SUT(), $this->openFile($wrapper), $from = 'hoa://Test/Vfs/Foo?type=file', $to = 'hoa://Test/Vfs/Oof?type=file' ) ->when($result = $wrapper->rename($from, $to)) ->then ->boolean($result) ->isTrue() ->string($_from) ->isEqualTo(SUT::realPath($from)) ->string($_to) ->isEqualTo(SUT::realPath($_to, false)); } public function case_rmdir() { $this ->given( $this->function->rmdir = function ($path) use (&$_path) { $_path = $path; return rmdir($path); }, $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->rmdir('hoa://Test/Vfs/Bar?type=directory', 0)) ->then ->boolean($result) ->isTrue(); } public function case_rmdir_a_file() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->rmdir('hoa://Test/Vfs/Foo?type=file', 0)) ->then ->boolean($result) ->isFalse(); } public function case_unlink() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->unlink('hoa://Test/Vfs/Foo?type=file')) ->then ->boolean($result) ->isTrue(); } public function case_rmdir_a_directory() { $this ->given( $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->unlink('hoa://Test/Vfs/Bar?type=directory')) ->then ->boolean($result) ->isFalse(); } public function case_url_stat() { $this ->given( $this->function->stat = function ($path) use (&$_path) { $_path = $path; return stat($path); }, $wrapper = new SUT(), $this->openFile($wrapper), $path = 'hoa://Test/Vfs/Foo?type=file' ) ->when($result = $wrapper->url_stat($path, 0)) ->then ->let( $keys = [ 'dev', 'ino', 'mode', 'nlink', 'uid', 'gid', 'rdev', 'size', 'atime', 'mtime', 'ctime', 'blksize', 'blocks' ] ) ->array($result) ->hasSize(26) ->hasKeys($keys) ->hasKeys(array_keys($keys)) ->string($_path) ->isEqualTo(SUT::realPath($path)); } public function case_url_stat_not_hoa_protocol() { $this ->given( $wrapper = new SUT(), $path = LUT::NO_RESOLUTION ) ->when(function () use ($wrapper, $path) { $wrapper->url_stat($path, 0); }) ->then ->error() ->exists(); } protected function openFile(SUT $wrapper, $content = '') { $wrapper->stream_open('hoa://Test/Vfs/Foo?type=file', 'wb+', STREAM_USE_PATH, $openedPath); fwrite($openedPath, $content, strlen($content)); fseek($openedPath, 0, SEEK_SET); return $wrapper; } protected function openDirectory(SUT $wrapper, array $children = []) { $wrapper->dir_opendir('hoa://Test/Vfs/Bar?type=directory', 0); foreach ($children as $child) { resolve('hoa://Test/Vfs/Bar/' . $child . '?type=file'); } return $wrapper; } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } given( $this->constant->WITH_COMPOSER = false, $node = new SUT('foo', 'bar') ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo('bar'); } public function case_reach_without_composer_with_a_queue() { $this ->given( $this->constant->WITH_COMPOSER = false, $node = new SUT('foo', 'bar') ) ->when($result = $node->reach('baz')) ->then ->string($result) ->isEqualTo('baz'); } public function case_reach_with_composer_without_a_queue_and_a_single_reach() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo('Bar' . DS . 'Baz' . DS . 'qux' . DS); } public function case_reach_with_composer_without_a_queue_and_a_multiple_reaches() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT( 'foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS . RS . 'Hello' . DS . 'Mister' . DS . 'Anderson' . DS ) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo( 'Bar' . DS . 'Baz' . DS . 'qux' . DS . RS . 'Hello' . DS . 'Mister' . DS . 'anderson' . DS ); } public function case_reach_with_composer_with_a_simple_queue() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS) ) ->when($result = $node->reach('Hello')) ->then ->string($result) ->isEqualTo( "\r" . 'Bar' . DS . 'Baz' . DS . 'Qux' . DS . 'hello' . RS . "\r" . dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))) ); } public function case_reach_with_composer_with_a_queue() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS) ) ->when($result = $node->reach('Hello/Mister/Anderson')) ->then ->string($result) ->isEqualTo( "\r" . 'Bar' . DS . 'hello' . DS . 'Mister' . DS . 'Anderson' . RS . "\r" . dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))) . DS . 'Mister' . DS . 'Anderson' ); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('ArrayAccess') ->isInstanceOf('IteratorAggregate'); } public function case_empty_constructor() { $this ->when($result = new SUT()) ->then ->variable($result->getName()) ->isNull() ->array(iterator_to_array($result->getIterator())) ->isEmpty(); } public function case_constructor_with_a_name() { $this ->given($name = 'foo') ->when($result = new SUT($name)) ->then ->string($result->getName()) ->isEqualTo($name) ->array(iterator_to_array($result->getIterator())) ->isEmpty(); } public function case_constructor_with_a_name_and_children() { $this ->given( $name = 'foo', $children = [new SUT('bar'), new SUT('baz')] ) ->when($result = new SUT($name, '', $children)) ->then ->string($result->getName()) ->isEqualTo($name) ->array(iterator_to_array($result->getIterator())) ->hasSize(2); } public function case_offset_set() { $this ->given( $root = new SUT(), $name = 'foo', $node = new SUT(), $oldCountChildren = count(iterator_to_array($root->getIterator())) ) ->when($result = $root->offsetSet($name, $node)) ->then ->integer(count(iterator_to_array($root->getIterator()))) ->isEqualTo($oldCountChildren + 1) ->object($root[$name]) ->isIdenticalTo($node); } public function case_offset_set_not_a_node() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetSet('foo', null); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_set_no_name() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetSet(null, new SUT()); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_get() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetGet('foo')) ->then ->object($result) ->isIdenticalTo($child); } public function case_offset_get_an_unknown_name() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetGet('foo'); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_exists() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetExists('foo')) ->then ->boolean($result) ->isTrue(); } public function case_offset_not_exists() { $this ->given($root = new SUT()) ->when($result = $root->offsetExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_offset_unset() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetUnset('foo')) ->then ->boolean($root->offsetExists('foo')) ->isFalse(); } public function case_reach() { $this ->given( $reach = 'bar', $node = new SUT('foo', $reach) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo($reach); } public function case_reach_with_a_queue() { $this ->given( $queue = 'baz', $node = new SUT('foo', 'bar') ) ->when($result = $node->reach('baz')) ->then ->string($result) ->isEqualTo($queue); } public function case_reach_id() { $this ->given($node = new SUT()) ->exception(function () use ($node) { $node->reachId('foo'); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_set_reach() { $this ->given( $reach = 'bar', $node = new SUT('foo', $reach) ) ->when($result = $node->setReach('baz')) ->then ->string($result) ->isEqualTo($reach) ->string($node->reach()) ->isEqualTo('baz'); } public function case_get_name() { $this ->given( $name = 'foo', $node = new SUT($name) ) ->when($result = $node->getName()) ->then ->string($result) ->isEqualTo($name); } public function case_get_iterator() { $this ->given( $childA = new SUT('bar'), $childB = new SUT('baz'), $children = [$childA, $childB] ) ->when($result = new SUT('foo', '', $children)) ->then ->object($result->getIterator()) ->isInstanceOf('ArrayIterator') ->array(iterator_to_array($result->getIterator())) ->isEqualTo([ 'bar' => $childA, 'baz' => $childB ]); } public function case_get_root() { $this ->when($result = SUT::getRoot()) ->then ->object($result) ->isIdenticalTo(LUT::getInstance()); } public function case_to_string_as_leaf() { $this ->given($node = new SUT('foo')) ->when($result = $node->__toString()) ->then ->string($result) ->isEqualTo('foo' . "\n"); } public function case_to_string_as_node() { $this ->given( $node = new SUT('foo'), $node[] = new SUT('bar'), $node[] = new SUT('baz') ) ->when($result = $node->__toString()) ->then ->string($result) ->isEqualTo( 'foo' . "\n" . ' bar' . "\n" . ' baz' . "\n" ); } } resolve($path, $exists); } public function stream_cast($castAs) { return false; } public function stream_close() { if (true === @fclose($this->getStream())) { $this->_stream = null; $this->_streamName = null; } return; } public function stream_eof() { return feof($this->getStream()); } public function stream_flush() { return fflush($this->getStream()); } public function stream_lock($operation) { return flock($this->getStream(), $operation); } public function stream_metadata($path, $option, $values) { $path = static::realPath($path, false); switch ($option) { case STREAM_META_TOUCH: $arity = count($values); if (0 === $arity) { $out = touch($path); } elseif (1 === $arity) { $out = touch($path, $values[0]); } else { $out = touch($path, $values[0], $values[1]); } break; case STREAM_META_OWNER_NAME: case STREAM_META_OWNER: $out = chown($path, $values); break; case STREAM_META_GROUP_NAME: case STREAM_META_GROUP: $out = chgrp($path, $values); break; case STREAM_META_ACCESS: $out = chmod($path, $values); break; default: $out = false; } return $out; } public function stream_open($path, $mode, $options, &$openedPath) { $path = static::realPath($path, 'r' === $mode[0]); if (Protocol::NO_RESOLUTION === $path) { return false; } if (null === $this->context) { $openedPath = fopen($path, $mode, $options & STREAM_USE_PATH); } else { $openedPath = fopen( $path, $mode, $options & STREAM_USE_PATH, $this->context ); } if (false === is_resource($openedPath)) { return false; } $this->_stream = $openedPath; $this->_streamName = $path; return true; } public function stream_read($count) { return fread($this->getStream(), $count); } public function stream_seek($offset, $whence = SEEK_SET) { return 0 === fseek($this->getStream(), $offset, $whence); } public function stream_stat() { return fstat($this->getStream()); } public function stream_tell() { return ftell($this->getStream()); } public function stream_truncate($size) { return ftruncate($this->getStream(), $size); } public function stream_write($data) { return fwrite($this->getStream(), $data); } public function dir_closedir() { closedir($this->getStream()); $this->_stream = null; $this->_streamName = null; return; } public function dir_opendir($path, $options) { $path = static::realPath($path); $handle = null; if (null === $this->context) { $handle = @opendir($path); } else { $handle = @opendir($path, $this->context); } if (false === $handle) { return false; } $this->_stream = $handle; $this->_streamName = $path; return true; } public function dir_readdir() { return readdir($this->getStream()); } public function dir_rewinddir() { return rewinddir($this->getStream()); } public function mkdir($path, $mode, $options) { if (null === $this->context) { return mkdir( static::realPath($path, false), $mode, $options | STREAM_MKDIR_RECURSIVE ); } return mkdir( static::realPath($path, false), $mode, $options | STREAM_MKDIR_RECURSIVE, $this->context ); } public function rename($from, $to) { if (null === $this->context) { return rename(static::realPath($from), static::realPath($to, false)); } return rename( static::realPath($from), static::realPath($to, false), $this->context ); } public function rmdir($path, $options) { if (null === $this->context) { return rmdir(static::realPath($path)); } return rmdir(static::realPath($path), $this->context); } public function unlink($path) { if (null === $this->context) { return unlink(static::realPath($path)); } return unlink(static::realPath($path), $this->context); } public function url_stat($path, $flags) { $path = static::realPath($path); if (Protocol::NO_RESOLUTION === $path) { if ($flags & STREAM_URL_STAT_QUIET) { return 0; } else { return trigger_error( 'Path ' . $path . ' cannot be resolved.', E_WARNING ); } } if ($flags & STREAM_URL_STAT_LINK) { return @lstat($path); } return @stat($path); } public function getStream() { return $this->_stream; } public function getStreamName() { return $this->_streamName; } } stream_wrapper_register('hoa', Wrapper::class); } namespace { if (!function_exists('resolve')) { function resolve($path, $exists = true, $unfold = false) { return Hoa\Protocol::getInstance()->resolve($path, $exists, $unfold); } } } getOption($v)) { switch ($c) { case 'E': $exists = false; break; case 'u': $unfold = true; break; case 't': $tree = true; break; case 'V': $verbose = false; break; case 'h': case '?': return $this->usage(); case '__ambiguous': $this->resolveOptionAmbiguity($v); break; } } $this->parser->listInputs($path); if (null === $path) { return $this->usage(); } if (true === $tree) { $protocol = Protocol::getInstance(); $foo = substr($path, 0, 6); if ('hoa://' !== $foo) { return; } $path = substr($path, 6); $current = $protocol; foreach (explode('/', $path) as $component) { if (!isset($current[$component])) { break; } $current = $current[$component]; } echo $current; return; } if (true === $verbose) { echo Console\Cursor::colorize('foreground(yellow)'), $path, Console\Cursor::colorize('normal'), ' is equivalent to:', "\n"; } $resolved = resolve($path, $exists, $unfold); foreach ((array) $resolved as $r) { echo $r, "\n"; } return; } public function usage() { echo 'Usage : protocol:resolve path', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'E' => 'Do not check if the resolution result exists.', 'u' => 'Unfold all possible results.', 't' => 'Print the tree from the path.', 'V' => 'No-verbose, i.e. be as quiet as possible, just print ' . 'essential information.', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Resolve `hoa://` paths. _reach) as $part) { $out[] = "\r" . $part . strtolower($head) . $queue; } $out[] = "\r" . dirname(dirname(dirname(dirname(__DIR__)))) . $queue; return implode(RS, $out); } $out = []; foreach (explode(RS, $this->_reach) as $part) { $pos = strrpos(rtrim($part, DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR) + 1; $head = substr($part, 0, $pos); $tail = substr($part, $pos); $out[] = $head . strtolower($tail); } $this->_reach = implode(RS, $out); return parent::reach($queue); } } _name = $name; } if (null !== $reach) { $this->_reach = $reach; } foreach ($children as $child) { $this[] = $child; } return; } public function offsetSet($name, $node) { if (!($node instanceof self)) { throw new Protocol\Exception( 'Protocol node must extend %s.', 0, __CLASS__ ); } if (empty($name)) { $name = $node->getName(); } if (empty($name)) { throw new Protocol\Exception( 'Cannot add a node to the `hoa://` protocol without a name.', 1 ); } $this->_children[$name] = $node; return; } public function offsetGet($name) { if (!isset($this[$name])) { throw new Protocol\Exception( 'Node %s does not exist.', 2, $name ); } return $this->_children[$name]; } public function offsetExists($name) { return true === array_key_exists($name, $this->_children); } public function offsetUnset($name) { unset($this->_children[$name]); return; } protected function _resolve($path, &$accumulator, $id = null) { if (substr($path, 0, 6) == 'hoa://') { $path = substr($path, 6); } if (empty($path)) { return null; } if (null === $accumulator) { $accumulator = []; $posId = strpos($path, '#'); if (false !== $posId) { $id = substr($path, $posId + 1); $path = substr($path, 0, $posId); } else { $id = null; } } $path = trim($path, '/'); $pos = strpos($path, '/'); if (false !== $pos) { $next = substr($path, 0, $pos); } else { $next = $path; } if (isset($this[$next])) { if (false === $pos) { if (null === $id) { $this->_resolveChoice($this[$next]->reach(), $accumulator); return true; } $accumulator = null; return $this[$next]->reachId($id); } $tnext = $this[$next]; $this->_resolveChoice($tnext->reach(), $accumulator); return $tnext->_resolve(substr($path, $pos + 1), $accumulator, $id); } $this->_resolveChoice($this->reach($path), $accumulator); return true; } protected function _resolveChoice($reach, array &$accumulator) { if (empty($accumulator)) { $accumulator = explode(RS, $reach); return; } if (false === strpos($reach, RS)) { if (false !== $pos = strrpos($reach, "\r")) { $reach = substr($reach, $pos + 1); foreach ($accumulator as &$entry) { $entry = null; } } foreach ($accumulator as &$entry) { $entry .= $reach; } return; } $choices = explode(RS, $reach); $ref = $accumulator; $accumulator = []; foreach ($choices as $choice) { if (false !== $pos = strrpos($choice, "\r")) { $choice = substr($choice, $pos + 1); foreach ($ref as $entry) { $accumulator[] = $choice; } } else { foreach ($ref as $entry) { $accumulator[] = $entry . $choice; } } } unset($ref); return; } public function reach($queue = null) { return empty($queue) ? $this->_reach : $queue; } public function reachId($id) { throw new Protocol\Exception( 'The node %s has no ID support (tried to reach #%s).', 4, [$this->getName(), $id] ); } public function setReach($reach) { $old = $this->_reach; $this->_reach = $reach; return $old; } public function getName() { return $this->_name; } protected function getReach() { return $this->_reach; } public function getIterator() { return new \ArrayIterator($this->_children); } public static function getRoot() { return Protocol::getInstance(); } public function __toString() { static $i = 0; $out = str_repeat(' ', $i) . $this->getName() . "\n"; foreach ($this as $node) { ++$i; $out .= $node; --$i; } return $out; } } Consistency::flexEntity('Hoa\Protocol\Node\Node'); given( $name = 'custom', SUT::register($name, CustomFilter::class), $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r') ) ->when( SUT::append($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo( strtolower($content) . ' ' . strlen($content) ); } } class CustomFilter extends LUT\Filter\LateComputed { protected function compute() { $this->_buffer = strtolower($this->_buffer) . ' ' . strlen($this->_buffer); return; } } given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when( SUT::append($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when( SUT::prepend($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_append_append() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::append($stream, $name1), SUT::append($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtolower($content)); } public function case_append_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::append($stream, $name1), SUT::prepend($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_prepend_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::prepend($stream, $name1), SUT::prepend($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_append_1000_filters() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when(function () use ($stream, $name) { for ($i = 1000; $i >= 0; --$i) { $this->resource(SUT::prepend($stream, $name)); } }) ->when($result = stream_get_contents($stream)) ->then ->string($result) ->isEqualTo(strtoupper($content)); } } given( $port = mt_rand(10000, 12000), exec( sprintf( 'php -S 127.0.0.1:%d -t %s > /dev/null 2>&1 & echo $! && sleep 0.2', $port, dirname(__DIR__) . DS . 'Fixtures' ), $outputs ), $pid = $outputs[0], $stream = new SUT('http://127.0.0.1:' . $port, null, true), $stream->on( 'connect', function (Event\Bucket $bucket) use ($self, &$connectCalled) { $connectCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => null, 'transferred' => 0, 'max' => 0 ]); } ), $stream->on( 'mimetype', function (Event\Bucket $bucket) use ($self, &$mimetypeCalled) { $mimetypeCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => 'text/html; charset=UTF-8', 'transferred' => 0, 'max' => 0 ]); } ), $stream->on( 'size', function (Event\Bucket $bucket) use ($self, &$sizeCalled) { $sizeCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => 'Content-Length: 14', 'transferred' => 0, 'max' => 14 ]); } ), $stream->on( 'progress', function (Event\Bucket $bucket) use ($self, &$progressCalled) { $progressCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => null, 'transferred' => 0, 'max' => 14 ]); } ) ) ->when($stream->open()) ->then ->boolean($connectCalled) ->isTrue() ->boolean($mimetypeCalled) ->isTrue() ->boolean($sizeCalled) ->isTrue() ->boolean($progressCalled) ->isTrue() ->let(!empty($pid) && exec('kill ' . $pid)); } } class SUT extends LUT\Stream { protected function &_open($streamName, LUT\Context $context = null) { if (null === $context) { $out = fopen($streamName, 'rb'); } else { $out = fopen($streamName, 'rb', false, $context->getContext()); } return $out; } protected function _close() { return fclose($this->getStream()); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(HoaException::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\IWrapper()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\Wrapper\IWrapper\File::class) ->isInstanceOf(LUT\Wrapper\IWrapper\Stream::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\Stream()) ->then ->object($result) ->isInstanceOf(SUT::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\File()) ->then ->object($result) ->isInstanceOf(SUT::class); } } given($oldIsRegistered = SUT::isRegistered('foo')) ->when($result = SUT::register('foo', 'StdClass')) ->then ->boolean($result) ->isTrue() ->boolean($oldIsRegistered) ->isFalse() ->boolean(SUT::isRegistered('foo')) ->isTrue(); } public function case_register_already_registered() { $this ->exception(function () { SUT::register('php', 'ClassName'); }) ->isInstanceOf(LUT\Exception::class) ->hasMessage('The protocol php is already registered.'); } public function case_register_implementation_does_not_exist() { $this ->exception(function () { SUT::register('foo', 'ClassName'); }) ->isInstanceOf(LUT\Exception::class) ->hasMessage( 'Cannot use the ClassName class for the implementation ' . 'of the foo protocol because it is not found.' ); } public function case_unregister() { $this ->given( SUT::register('foo', 'StdClass'), $oldIsRegistered = SUT::isRegistered('foo') ) ->when($result = SUT::unregister('foo')) ->then ->boolean($result) ->isTrue() ->boolean($oldIsRegistered) ->isTrue() ->boolean(SUT::isRegistered('foo')) ->isFalse(); } public function case_unregister_unregistered_protocol() { $this ->when($result = SUT::unregister('foo')) ->then ->boolean($result) ->isFalse(); } public function case_restore_registered_protocol() { $this ->when($result = SUT::restore('php')) ->then ->boolean($result) ->isTrue(); } public function case_restore_unregistered_protocol() { $this ->when($result = SUT::restore('foo')) ->then ->boolean($result) ->isFalse(); } public function case_is_registered() { $this ->when($result = SUT::isRegistered('php')) ->then ->boolean($result) ->isTrue(); } public function case_is_not_registered() { $this ->when($result = SUT::isRegistered('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_registered() { $this ->when($result = SUT::getRegistered()) ->then ->array($result) ->containsValues([ 'https', 'php', 'file', 'glob', 'data', 'http', 'hoa' ]); } public function case_get_registered_dynamically() { $this ->given($oldCount = count(SUT::getRegistered())) ->when( SUT::register('foo', \StdClass::class), $result = SUT::getRegistered() ) ->then ->integer(count($result)) ->isEqualTo($oldCount + 1) ->when( SUT::unregister('foo'), $result = SUT::getRegistered() ) ->then ->integer(count($result)) ->isEqualTo($oldCount); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(LUT\Exception::class); } } when($result = new \Mock\Hoa\Stream\IStream\Statable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::SIZE_UNDEFINED) ->then ->integer($result) ->isEqualTo(-1); } } when($result = new \Mock\Hoa\Stream\IStream\Touchable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::OVERWRITE) ->then ->boolean($result) ->isEqualTo(true) ->when($result = SUT::DO_NOT_OVERWRITE) ->then ->boolean($result) ->isEqualTo(false) ->when($result = SUT::MAKE_DIRECTORY) ->then ->boolean($result) ->isEqualTo(true) ->when($result = SUT::DO_NOT_MAKE_DIRECTORY) ->then ->boolean($result) ->isEqualTo(false); } } when($result = new \Mock\Hoa\Stream\IStream\In()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Structural()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Out()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Pointable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::SEEK_SET) ->then ->integer($result) ->isEqualTo(SEEK_SET) ->when($result = SUT::SEEK_CURRENT) ->then ->integer($result) ->isEqualTo(SEEK_CUR) ->when($result = SUT::SEEK_END) ->then ->integer($result) ->isEqualTo(SEEK_END); } } when($result = new \Mock\Hoa\Stream\IStream\Stream()) ->then ->object($result) ->isInstanceOf(SUT::class); } } when($result = new \Mock\Hoa\Stream\IStream\Bufferable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Lockable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::LOCK_SHARED) ->then ->integer($result) ->isEqualTo(LOCK_SH) ->when($result = SUT::LOCK_EXCLUSIVE) ->then ->integer($result) ->isEqualTo(LOCK_EX) ->when($result = SUT::LOCK_RELEASE) ->then ->integer($result) ->isEqualTo(LOCK_UN) ->when($result = SUT::LOCK_NO_BLOCK) ->then ->integer($result) ->isEqualTo(LOCK_NB); } } when($result = new \Mock\Hoa\Stream\IStream\Pathable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } boolean(SUT::IS_A_BRIGADE) ->isTrue() ->boolean(SUT::IS_A_STREAM) ->isFalse(); } public function case_construct_a_brigade() { $this ->given($brigade = 'foo') ->when($result = new SUT($brigade, SUT::IS_A_BRIGADE)) ->then ->boolean($result->getType()) ->isEqualTo(SUT::IS_A_BRIGADE) ->variable($result->getBrigade()) ->isIdenticalTo($brigade) ->isIdenticalTo('foo'); } public function case_construct_a_stream() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar' ) ->when($result = new SUT($stream, SUT::IS_A_STREAM, $buffer)) ->then ->boolean($result->getType()) ->isEqualTo(SUT::IS_A_STREAM) ->let($bucket = $this->invoke($result)->getBucket()) ->object($bucket) ->isInstanceOf(\StdClass::class) ->resource($bucket->bucket) ->string($bucket->data) ->isEqualTo($buffer) ->integer($bucket->datalen) ->isEqualTo(strlen($buffer)) ->object($result->getBrigade()) ->isIdenticalTo($bucket); } public function case_eob() { $this ->given( $stream = fopen(__FILE__, 'r'), $bucket = new SUT($stream, SUT::IS_A_STREAM) ) ->when($result = $bucket->eob()) ->then ->boolean($result) ->isTrue(); } public function case_set_data() { $this ->given( $stream = fopen(__FILE__, 'r'), $oldBuffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $oldBuffer), $buffer = 'bazqux' ) ->when($result = $bucket->setData('bazqux')) ->then ->string($result) ->isEqualTo($oldBuffer) ->let($_bucket = $this->invoke($bucket)->getBucket()) ->object($_bucket) ->isInstanceOf(\StdClass::class) ->resource($_bucket->bucket) ->string($_bucket->data) ->isEqualTo($buffer) ->integer($_bucket->datalen) ->isEqualTo(strlen($buffer)) ->object($bucket->getBrigade()) ->isIdenticalTo($_bucket); } public function case_get_data() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $buffer) ) ->when($result = $bucket->getData()) ->then ->string($result) ->isEqualTo($buffer) ->isEqualTo($this->invoke($bucket)->getBucket()->data); } public function case_get_length() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $buffer) ) ->when($result = $bucket->getLength()) ->then ->integer($result) ->isEqualTo(strlen($buffer)) ->isEqualTo($this->invoke($bucket)->getBucket()->datalen); } } exception(function () { SUT::getInstance(null); }) ->isInstanceOf(LUT\Exception::class); } public function case_get_new_instance() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isInstanceOf(SUT::class); } public function case_get_new_instances() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isNotIdenticalTo(SUT::getInstance('bar')); } public function case_get_same_instance() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isIdenticalTo(SUT::getInstance('foo')); } public function case_get_id() { $this ->given( $id = 'foo', $context = SUT::getInstance($id) ) ->when($result = $context->getId()) ->then ->string($result) ->isEqualTo($id); } public function case_context_exists() { $this ->given( $id = 'foo', SUT::getInstance($id) ) ->when($result = SUT::contextExists($id)) ->then ->boolean($result) ->isTrue(); } public function case_context_does_not_exist() { $this ->when($result = SUT::contextExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_set_options() { $this ->given( $context = SUT::getInstance('foo'), $options = ['bar' => ['baz' => 'qux']] ) ->when($result = $context->setOptions($options)) ->then ->boolean($result) ->isTrue(); } public function case_get_options() { $this ->given( $context = SUT::getInstance('foo'), $options = ['bar' => ['baz' => 'qux']], $context->setOptions($options) ) ->when($result = $context->getOptions()) ->then ->array($result) ->isEqualTo($options); } public function case_set_parameters() { $this ->given( $context = SUT::getInstance('foo'), $parameters = [ 'notificaion' => 'callback', 'options' => ['bar' => ['baz' => 'qux']] ] ) ->when($result = $context->setParameters($parameters)) ->then ->boolean($result) ->isTrue(); } public function case_get_parameters() { $this ->given( $context = SUT::getInstance('foo'), $parameters = [ 'notification' => 'callback', 'options' => ['bar' => ['baz' => 'qux']] ], $context->setParameters($parameters) ) ->when($result = $context->getParameters()) ->then ->array($result) ->isEqualTo($parameters); } public function case_get_context() { $this ->given($context = SUT::getInstance('foo')) ->when($result = $context->getContext()) ->then ->resource($result) ->isStreamContext(); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(LUT\Exception::class); } } boolean(SUT::OVERWRITE) ->isTrue() ->boolean(SUT::DO_NOT_OVERWRITE) ->isFalse() ->integer(SUT::READ) ->isEqualTo(STREAM_FILTER_READ) ->integer(SUT::WRITE) ->isEqualTo(STREAM_FILTER_WRITE) ->integer(SUT::READ_AND_WRITE) ->isEqualTo(STREAM_FILTER_ALL); } public function case_register() { $this ->when($result = SUT::register('foo', \StdClass::class)) ->then ->boolean($result) ->isTrue(); } public function case_register_already_registered_do_not_overwrite() { $this ->given( $name = 'foo', $class = \StdClass::class, SUT::register($name, $class) ) ->exception(function () use ($name, $class) { SUT::register($name, $class); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage('Filter foo is already registered.'); } public function case_register_already_registered_do_overwrite() { $this ->given( $name = 'foo', SUT::register($name, \StdClass::class), new \Mock\StdClass() ) ->when($result = SUT::register($name, \Mock\StdClass::class, SUT::OVERWRITE)) ->then ->boolean($result) ->isFalse(); } public function case_register_empty_name() { $this ->exception(function () { SUT::register('', \StdClass::class); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Filter name cannot be empty ' . '(implementation class is StdClass).' ); } public function case_register_unknown_class() { $this ->exception(function () { SUT::register('foo', '42Foo'); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Cannot register the 42Foo class for the filter foo ' . 'because it does not exist.' ); } public function case_append() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper' ) ->when($result = SUT::append($stream, $name)) ->then ->resource($result) ->isStreamFilter(); } public function case_prepend() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper' ) ->when($result = SUT::prepend($stream, $name)) ->then ->resource($result) ->isStreamFilter(); } public function case_remove() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper', $filter = SUT::append($stream, $name) ) ->when($result = SUT::remove($filter)) ->then ->boolean($result) ->isTrue(); } public function case_remove_by_name() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper', $filter = SUT::append($stream, $name) ) ->when($result = SUT::remove($name)) ->then ->boolean($result) ->isTrue(); } public function case_remove_unknown() { $this ->exception(function () { SUT::remove('foo'); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Cannot remove the stream filter foo ' . 'because no resource was found with this name.' ); } public function case_is_registered() { $this ->when($result = SUT::isRegistered('string.toupper')) ->then ->boolean($result) ->isTrue(); } public function case_is_not_registered() { $this ->when($result = SUT::isRegistered('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_registered() { $this ->when($result = SUT::getRegistered()) ->then ->array($result) ->containsValues([ 'string.rot13', 'string.toupper', 'string.tolower', 'string.strip_tags', 'consumed', 'dechunk' ]); } } integer(SUT::PASS_ON) ->isEqualTo(PSFS_PASS_ON) ->integer(SUT::FEED_ME) ->isEqualTo(PSFS_FEED_ME) ->integer(SUT::FATAL_ERROR) ->isEqualTo(PSFS_ERR_FATAL) ->integer(SUT::FLAG_NORMAL) ->isEqualTo(PSFS_FLAG_NORMAL) ->integer(SUT::FLAG_FLUSH_INC) ->isEqualTo(PSFS_FLAG_FLUSH_INC) ->integer(SUT::FLAG_FLUSH_CLOSE) ->isEqualTo(PSFS_FLAG_FLUSH_CLOSE); } public function case_is_a_php_filter() { $this ->when($result = new SUT()) ->then ->object($result) ->isInstanceOf(\php_user_filter::class); } public function case_interfaces() { $this ->when($result = new SUT()) ->then ->object($result) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_set_name() { $this ->given($filter = new SUT()) ->when($result = $filter->setName('foo')) ->then ->string($result) ->isEqualTo(''); } public function case_get_name() { $this ->given( $filter = new SUT(), $name = 'foo', $filter->setName($name) ) ->when($result = $filter->getName()) ->then ->string($result) ->isEqualTo($name); } public function case_set_parameters() { $this ->given($filter = new SUT()) ->when($result = $filter->setParameters(['foo', 'bar', 'baz'])) ->then ->string($result) ->isEqualTo(''); } public function case_get_parameters() { $this ->given( $filter = new SUT(), $parameters = ['foo', 'bar', 'baz'], $filter->setParameters($parameters) ) ->when($result = $filter->getParameters()) ->then ->array($result) ->isEqualTo($parameters); } public function case_get_stream() { $this ->given($filter = new SUT()) ->when($result = $filter->getStream()) ->then ->variable($result) ->isNull(); } } when($result = new SUT(__FILE__)) ->then ->object($result) ->isInstanceOf(LUT\IStream\Stream::class) ->isInstanceOf(Event\Listenable::class); } public function case_constants() { $this ->integer(SUT::NAME) ->isEqualTo(0) ->integer(SUT::HANDLER) ->isEqualTo(1) ->integer(SUT::RESOURCE) ->isEqualTo(2) ->integer(SUT::CONTEXT) ->isEqualTo(3); } public function case_construct() { $this ->given($name = __FILE__) ->when($result = new SUT($name)) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isFalse() ->let($listener = $this->invoke($result)->getListener()) ->object($listener) ->isInstanceOf(Event\Listener::class) ->boolean($listener->listenerExists('authrequire')) ->isTrue() ->boolean($listener->listenerExists('authresult')) ->isTrue() ->boolean($listener->listenerExists('complete')) ->isTrue() ->boolean($listener->listenerExists('connect')) ->isTrue() ->boolean($listener->listenerExists('failure')) ->isTrue() ->boolean($listener->listenerExists('mimetype')) ->isTrue() ->boolean($listener->listenerExists('progress')) ->isTrue() ->boolean($listener->listenerExists('redirect')) ->isTrue() ->boolean($listener->listenerExists('resolve')) ->isTrue() ->boolean($listener->listenerExists('size')) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isTrue(); } public function case_construct_with_a_context() { $this ->given( $name = __FILE__, $contextName = 'foo', LUT\Context::getInstance($contextName) ) ->when($result = new SUT($name, $contextName)) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isFalse() ->object($this->invoke($result)->getListener()) ->isInstanceOf(Event\Listener::class); } public function case_construct_with_deferred_opening() { $this ->given($name = __FILE__) ->when($result = new SUT($name, null, true)) ->then ->boolean($this->invoke($result)->hasBeenDeferred()) ->isTrue() ->boolean($result->isOpened()) ->isFalse() ->variable($result->getStreamName()) ->isNull(); } public function case_open() { $this ->given( $name = __FILE__, $stream = new SUT($name, null, true) ) ->when($result = $stream->open()) ->then ->object($result) ->isIdenticalTo($stream) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isTrue() ->boolean($result->isOpened()) ->isTrue() ->string($result->getStreamName()) ->isEqualTo($name) ->integer($result->getStreamBufferSize()) ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE); } public function case_close() { $this ->given( $name = __FILE__, $stream = new SUT($name), $resource = $stream->getStream(), $context = $stream->getStreamContext() ) ->when($result = $stream->close()) ->then ->variable($result) ->isNull() ->boolean($stream->isOpened()) ->isFalse() ->variable(SUT::getStreamHandler($stream)) ->isNull() ->variable($stream->getStreamName()) ->isEqualTo($name) ->variable($stream->getStream()) ->isEqualTo($resource) ->variable($stream->getStreamContext()) ->isEqualTo($context) ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isFalse() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isFalse(); } public function case_close_more_than_once() { $this ->given( $name = __FILE__, $stream = new SUT($name), $close1 = $stream->close() ) ->when($result = $stream->close()) ->then ->variable($result) ->isIdenticalTo($close1); } public function case_open_close_open() { $this ->given( $name = __FILE__, $stream = new SUT($name, null, true), $stream->open(), $resource = $stream->getStream(), $context = $stream->getStreamContext(), $handler = SUT::getStreamHandler($stream), $this->function->stream_set_write_buffer = 0, $stream->setStreamBuffer(42), $stream->close() ) ->when($result = $stream->open()) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->resource($result->getStream()) ->isNotEqualTo($resource) ->object($handler) ->isIdenticalTo($result) ->object($this->invoke($stream)->getListener()) ->isInstanceOf(Event\Listener::class) ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE); } public function case_close_event_close_before() { $self = $this; $this ->given( $name = 'hoa://Test/Vfs/Foo?type=file', $stream = new SUT($name), Event::getEvent('hoa://Event/Stream/' . $name . ':close-before')->attach( function (Event\Bucket $bucket) use ($self, &$called) { $called = true; $self ->variable($bucket->getData()) ->isNull() ->boolean($bucket->getSource()->isOpened()) ->isTrue(); } ) ) ->when($result = $stream->close()) ->then ->boolean($called) ->isTrue(); } public function case_get_stream_name() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStreamName()) ->then ->string($result) ->isEqualTo($name); } public function case_get_stream() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStream()) ->then ->resource($result) ->isStream($name); } public function case_get_stream_context() { $this ->given( $name = __FILE__, $contextName = 'foo', $context = LUT\Context::getInstance($contextName), $stream = new SUT($name, $contextName) ) ->when($result = $stream->getStreamContext()) ->then ->object($result) ->isIdenticalTo($context); } public function case_get_stream_context_with_no_context_given() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStreamContext()) ->then ->variable($result) ->isNull(); } public function case_get_stream_handler() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = SUT::getStreamHandler($name)) ->then ->object($result) ->isIdenticalTo($result); } public function case_get_stream_handler_of_unknown_stream() { $this ->when($result = SUT::getStreamHandler('foo')) ->then ->variable($result) ->isNull(); } public function case__set_stream() { $this ->given( $stream = new SUT(__FILE__), $oldStream = $stream->getStream(), $newStream = fopen('php://memory', 'rb') ) ->when($result = $stream->_setStream($newStream)) ->then ->resource($result) ->isIdenticalTo($oldStream) ->isStream() ->resource($stream->getStream()) ->isStream() ->isIdenticalTo($newStream); } public function case__set_stream_invalid_resource() { $this ->given($stream = new SUT(__FILE__)) ->exception(function () use ($stream) { $stream->_setStream(true); }) ->isInstanceOf(LUT\Exception::class); } public function case__set_stream_unknown_resource() { $this ->given( $stream = new SUT(__FILE__), $oldStream = $stream->getStream(), $newStream = fopen('php://memory', 'rb'), $this->function->is_resource = false, $this->function->gettype = 'resource', $this->function->get_resource_type = 'Unknown' ) ->when($result = $stream->_setStream($newStream)) ->then ->resource($result) ->isIdenticalTo($oldStream) ->isStream() ->resource($stream->getStream()) ->isStream() ->isIdenticalTo($newStream); } public function case_is_opened() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->isOpened()) ->then ->boolean($result) ->isTrue(); } public function case_is_not_opened() { $this ->given($stream = new SUT(__FILE__, null, true)) ->when($result = $stream->isOpened()) ->then ->boolean($result) ->isFalse() ->when( $stream->open(), $result = $stream->isOpened() ) ->then ->boolean($result) ->isTrue(); } public function case_set_stream_timeout() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_timeout = function ($_stream, $_seconds, $_microseconds) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_seconds) ->isEqualTo(7) ->integer($_microseconds) ->isEqualTo(42); return true; } ) ->when($result = $stream->setStreamTimeout(7, 42)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue(); } public function case_has_been_deferred() { $this ->given($stream = new SUT(__FILE__, null, true)) ->when($result = $this->invoke($stream)->hasBeenDeferred()) ->then ->boolean($result) ->isTrue(); } public function case_has_not_been_deferred() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $this->invoke($stream)->hasBeenDeferred()) ->then ->boolean($result) ->isFalse(); } public function case_has_timed_out() { $this ->given( $stream = new SUT(__FILE__), $this->function->stream_get_meta_data = [ 'timed_out' => true ] ) ->when($result = $stream->hasTimedOut()) ->then ->boolean($result) ->isTrue(); } public function case_has_not_timed_out() { $this ->given( $stream = new SUT(__FILE__), $this->function->stream_get_meta_data = [ 'timed_out' => false ] ) ->when($result = $stream->hasTimedOut()) ->then ->boolean($result) ->isFalse(); } public function case_set_stream_blocking() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_blocking = function ($_stream, $_mode) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_mode) ->isEqualTo(1); return true; } ) ->when($result = $stream->setStreamBlocking(true)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue(); } public function case_get_default_stream_buffer_size() { $self = $this; $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamBufferSize()) ->then ->integer($result) ->isEqualTo(8192); } public function case_set_stream_buffer() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(42); return 0; } ) ->when($result = $stream->setStreamBuffer(42)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(42); } public function case_set_stream_buffer_fail() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $oldStreamBufferSize = $stream->getStreamBufferSize(), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(42); return 1; } ) ->when($result = $stream->setStreamBuffer(42)) ->then ->boolean($result) ->isFalse() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo($oldStreamBufferSize); } public function case_disable_stream_buffer() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(0); return 0; } ) ->when($result = $stream->disableStreamBuffer()) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(0); } public function case_get_stream_wrapper_name_with_no_wrapper() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamWrapperName()) ->then ->string($result) ->isEqualTo('file'); } public function case_get_stream_wrapper_name() { $this ->given($stream = new SUT('hoa://Test/Vfs/Foo?type=file')) ->when($result = $stream->getStreamWrapperName()) ->then ->string($result) ->isEqualTo('hoa'); } public function case_get_stream_meta_data() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamMetaData()) ->then ->array($result) ->isEqualTo([ 'timed_out' => false, 'blocked' => true, 'eof' => false, 'wrapper_type' => 'plainfile', 'stream_type' => 'STDIO', 'mode' => 'rb', 'unread_bytes' => 0, 'seekable' => true, 'uri' => __FILE__ ]); } public function case_is_borrowing() { $this ->given( $streamA1 = new SUT(__FILE__), $streamA2 = new SUT(__FILE__) ) ->when($result = $streamA2->isBorrowing()) ->then ->boolean($result) ->isTrue() ->boolean($streamA1->isBorrowing()) ->isFalse(); } public function case_is_not_borrowing() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->isBorrowing()) ->then ->boolean($result) ->isFalse(); } public function case_shutdown_destructor() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = SUT::_Hoa_Stream()) ->then ->boolean($called) ->isTrue(); } public function case_destruct_an_opened_stream() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = $stream->__destruct()) ->then ->boolean($called) ->isTrue(); } public function case_destruct_a_deferred_stream() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__, null, true), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = $stream->__destruct()) ->then ->variable($called) ->isNull(); } public function case_protocol_reach_id() { $this ->given( $name = 'hoa://Test/Vfs/Foo?type=file', $stream = new SUT($name) ) ->when($result = resolve('hoa://Library/Stream#' . $name)) ->then ->object($result) ->isIdenticalTo($stream); } public function case_protocol_reach_unknown_id() { $this ->given($name = 'hoa://Test/Vfs/Foo?type=file') ->when($result = resolve('hoa://Library/Stream#' . $name)) ->then ->variable($result) ->isNull(); } } class SUT extends LUT\Stream { protected function &_open($streamName, LUT\Context $context = null) { if (null === $context) { $out = fopen($streamName, 'rb'); } else { $out = fopen($streamName, 'rb', false, $context->getContext()); } return $out; } protected function _close() { return fclose($this->getStream()); } } class SUTWithPublicClose extends SUT { public function _close() { return parent::_close(); } } given( $stream = new \StdClass(), $composite = new SUT() ) ->when($result = $this->invoke($composite)->setStream($stream)) ->then ->variable($result) ->isNull(); } public function case_get_stream() { $this ->given( $stream = new \StdClass(), $composite = new SUT(), $this->invoke($composite)->setStream($stream) ) ->when($result = $composite->getStream()) ->then ->object($result) ->isIdenticalTo($stream); } public function case_set_inner_stream() { $this ->given( $innerStream = new \Mock\Hoa\Stream(__FILE__), $composite = new SUT() ) ->when($result = $this->invoke($composite)->setInnerStream($innerStream)) ->then ->variable($result) ->isNull(); } public function case_get_inner_stream() { $this ->given( $innerStream = new \Mock\Hoa\Stream(__FILE__), $composite = new SUT(), $this->invoke($composite)->setInnerStream($innerStream) ) ->when($result = $composite->getInnerStream()) ->then ->object($result) ->isIdenticalTo($innerStream); } } setType($is); if (self::IS_A_BRIGADE === $this->getType()) { $this->setBrigade($brigade); } else { $this->setBucket(stream_bucket_new($brigade, $buffer)); $bucket = $this->getBucket(); $this->setBrigade($bucket); } return; } public function eob() { $this->_bucket = null; return false == $this->getBucket(); } public function append(Bucket $bucket) { stream_bucket_append($this->getBrigade(), $bucket->getBucket()); return; } public function prepend(Bucket $bucket) { stream_bucket_prepend($this->getBrigade(), $bucket->getBucket()); return; } protected function setType($type) { $old = $this->_type; $this->_type = $type; return $old; } public function getType() { return $this->_type; } public function setData($data) { $old = $this->getBucket()->data; $this->getBucket()->data = $data; $this->getBucket()->datalen = strlen($this->getBucket()->data); return $old; } public function getData() { if (null === $this->getBucket()) { return null; } return $this->getBucket()->data; } public function getLength() { if (null === $this->getBucket()) { return 0; } return $this->getBucket()->datalen; } protected function setBrigade(&$brigade) { $old = $this->_brigade; $this->_brigade = $brigade; return $old; } public function getBrigade() { return $this->_brigade; } protected function setBucket($bucket) { $old = $this->_bucket; $this->_bucket = $bucket; return $old; } protected function getBucket() { if (null === $this->_bucket && self::IS_A_BRIGADE === $this->getType()) { $this->_bucket = stream_bucket_make_writeable($this->getBrigade()); } return $this->_bucket; } } _id = $id; $this->_context = stream_context_create(); return; } public static function getInstance($id) { if (empty($id)) { throw new Exception('Context ID must not be null.', 0); } if (false === static::contextExists($id)) { static::$_instances[$id] = new static($id); } return static::$_instances[$id]; } public function getId() { return $this->_id; } public static function contextExists($id) { return array_key_exists($id, static::$_instances); } public function setOptions(array $options) { return stream_context_set_option($this->getContext(), $options); } public function setParameters(array $parameters) { return stream_context_set_params($this->getContext(), $parameters); } public function getOptions() { return stream_context_get_options($this->getContext()); } public function getParameters() { return stream_context_get_params($this->getContext()); } public function getContext() { return $this->_context; } } eob()) { $this->_buffer .= $iBucket->getData(); $consumed += $iBucket->getLength(); } if (null !== $consumed) { $return = self::PASS_ON; } if (true === $closing) { $stream = $this->getStream(); $this->compute(); $bucket = new Stream\Bucket( $stream, Stream\Bucket::IS_A_STREAM, $this->_buffer ); $oBucket = new Stream\Bucket($out); $oBucket->append($bucket); $return = self::PASS_ON; $this->_buffer = null; } return $return; } abstract protected function compute(); } getStream(); } if (null === $parameters) { return self::$_resources[$name] = stream_filter_append( $stream, $name, $mode ); } return self::$_resources[$name] = stream_filter_append( $stream, $name, $mode, $parameters ); } public static function prepend( $stream, $name, $mode = self::READ, $parameters = null ) { if ($stream instanceof Stream) { $stream = $stream->getStream(); } if (null === $parameters) { return self::$_resources[$name] = stream_filter_prepend( $stream, $name, $mode ); } return self::$_resources[$name] = stream_filter_prepend( $stream, $name, $mode, $parameters ); } public static function remove($streamFilter) { if (!is_resource($streamFilter)) { if (isset(self::$_resources[$streamFilter])) { $streamFilter = self::$_resources[$streamFilter]; } else { throw new Exception( 'Cannot remove the stream filter %s because no resource was ' . 'found with this name.', 3, $streamFilter ); } } return stream_filter_remove($streamFilter); } public static function isRegistered($name) { return in_array($name, self::getRegistered()); } public static function getRegistered() { return stream_get_filters(); } } Consistency::flexEntity('Hoa\Stream\Filter\Filter'); eob()) { $consumed += $iBucket->getLength(); $oBucket->append($iBucket); } unset($iBucket); unset($oBucket); return self::PASS_ON; } public function onCreate() { return true; } public function onClose() { return; } public function setName($name) { $old = $this->filtername; $this->filtername = $name; return $old; } public function setParameters($parameters) { $old = $this->params; $this->params = $parameters; return $old; } public function getName() { return $this->filtername; } public function getParameters() { return $this->params; } public function getStream() { return isset($this->stream) ? $this->stream : null; } } _streamName = $streamName; $this->_context = $context; $this->_hasBeenDeferred = $wait; $this->setListener( new Event\Listener( $this, [ 'authrequire', 'authresult', 'complete', 'connect', 'failure', 'mimetype', 'progress', 'redirect', 'resolve', 'size' ] ) ); if (true === $wait) { return; } $this->open(); return; } final private static function &_getStream( $streamName, Stream $handler, $context = null ) { $name = md5($streamName); if (null !== $context) { if (false === Context::contextExists($context)) { throw new Exception( 'Context %s was not previously declared, cannot retrieve ' . 'this context.', 0, $context ); } $context = Context::getInstance($context); } if (!isset(self::$_register[$name])) { self::$_register[$name] = [ self::NAME => $streamName, self::HANDLER => $handler, self::RESOURCE => $handler->_open($streamName, $context), self::CONTEXT => $context ]; Event::register( 'hoa://Event/Stream/' . $streamName, $handler ); Event::register( 'hoa://Event/Stream/' . $streamName . ':close-before', $handler ); } else { $handler->_borrowing = true; } if (null === self::$_register[$name][self::RESOURCE]) { self::$_register[$name][self::RESOURCE] = $handler->_open($streamName, $context); } return self::$_register[$name]; } abstract protected function &_open($streamName, Context $context = null); abstract protected function _close(); final public function open() { $context = $this->_context; if (true === $this->hasBeenDeferred()) { if (null === $context) { $handle = Context::getInstance(uniqid()); $handle->setParameters([ 'notification' => [$this, '_notify'] ]); $context = $handle->getId(); } elseif (true === Context::contextExists($context)) { $handle = Context::getInstance($context); $parameters = $handle->getParameters(); if (!isset($parameters['notification'])) { $handle->setParameters([ 'notification' => [$this, '_notify'] ]); } } } $this->_bufferSize = self::DEFAULT_BUFFER_SIZE; $this->_bucket = self::_getStream( $this->_streamName, $this, $context ); return $this; } final public function close() { $streamName = $this->getStreamName(); $name = md5($streamName); if (!isset(self::$_register[$name])) { return; } Event::notify( 'hoa://Event/Stream/' . $streamName . ':close-before', $this, new Event\Bucket() ); if (false === $this->_close()) { return; } unset(self::$_register[$name]); $this->_bucket[self::HANDLER] = null; Event::unregister( 'hoa://Event/Stream/' . $streamName ); Event::unregister( 'hoa://Event/Stream/' . $streamName . ':close-before' ); return; } public function getStreamName() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::NAME]; } public function getStream() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::RESOURCE]; } public function getStreamContext() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::CONTEXT]; } public static function getStreamHandler($streamName) { $name = md5($streamName); if (!isset(self::$_register[$name])) { return null; } return self::$_register[$name][self::HANDLER]; } public function _setStream($stream) { if (false === is_resource($stream) && ('resource' !== gettype($stream) || 'Unknown' !== get_resource_type($stream))) { throw new Exception( 'Try to change the stream resource with an invalid one; ' . 'given %s.', 1, gettype($stream) ); } $old = $this->_bucket[self::RESOURCE]; $this->_bucket[self::RESOURCE] = $stream; return $old; } public function isOpened() { return is_resource($this->getStream()); } public function setStreamTimeout($seconds, $microseconds = 0) { return stream_set_timeout($this->getStream(), $seconds, $microseconds); } protected function hasBeenDeferred() { return $this->_hasBeenDeferred; } public function hasTimedOut() { $metaData = $this->getStreamMetaData(); return true === $metaData['timed_out']; } public function setStreamBlocking($mode) { return stream_set_blocking($this->getStream(), (int) $mode); } public function setStreamBuffer($buffer) { $out = 0 === stream_set_write_buffer($this->getStream(), $buffer); if (true === $out) { $this->_bufferSize = $buffer; } return $out; } public function disableStreamBuffer() { return $this->setStreamBuffer(0); } public function getStreamBufferSize() { return $this->_bufferSize; } public function getStreamWrapperName() { if (false === $pos = strpos($this->getStreamName(), '://')) { return 'file'; } return substr($this->getStreamName(), 0, $pos); } public function getStreamMetaData() { return stream_get_meta_data($this->getStream()); } public function isBorrowing() { return $this->_borrowing; } public function _notify( $ncode, $severity, $message, $code, $transferred, $max ) { static $_map = [ STREAM_NOTIFY_AUTH_REQUIRED => 'authrequire', STREAM_NOTIFY_AUTH_RESULT => 'authresult', STREAM_NOTIFY_COMPLETED => 'complete', STREAM_NOTIFY_CONNECT => 'connect', STREAM_NOTIFY_FAILURE => 'failure', STREAM_NOTIFY_MIME_TYPE_IS => 'mimetype', STREAM_NOTIFY_PROGRESS => 'progress', STREAM_NOTIFY_REDIRECTED => 'redirect', STREAM_NOTIFY_RESOLVE => 'resolve', STREAM_NOTIFY_FILE_SIZE_IS => 'size' ]; $this->getListener()->fire($_map[$ncode], new Event\Bucket([ 'code' => $code, 'severity' => $severity, 'message' => $message, 'transferred' => $transferred, 'max' => $max ])); return; } final public static function _Hoa_Stream() { foreach (self::$_register as $entry) { $entry[self::HANDLER]->close(); } return; } public function __toString() { return $this->getStreamName(); } public function __destruct() { if (false === $this->isOpened()) { return; } $this->close(); return; } } class _Protocol extends Protocol\Node { protected $_name = 'Stream'; public function reachId($id) { return Stream::getStreamHandler($id); } } Consistency::flexEntity('Hoa\Stream\Stream'); Consistency::registerShutdownFunction(xcallable('Hoa\Stream\Stream::_Hoa_Stream')); $protocol = Protocol::getInstance(); $protocol['Library'][] = new _Protocol(); _stream; $this->_stream = $stream; return $old; } public function getStream() { return $this->_stream; } protected function setInnerStream(Stream $innerStream) { $old = $this->_innerStream; $this->_innerStream = $innerStream; return $old; } public function getInnerStream() { return $this->_innerStream; } } _tmpArguments = $arguments; parent::__construct($message, $code, $previous); $this->_rawMessage = $message; $this->message = @vsprintf($message, $this->getArguments()); return; } public function getBacktrace() { if (null === $this->_trace) { $this->_trace = $this->getTrace(); } return $this->_trace; } public function getPreviousThrow() { if (null === $this->_previous) { $this->_previous = $this->getPrevious(); } return $this->_previous; } public function getArguments() { if (null === $this->_arguments) { $arguments = $this->_tmpArguments; if (!is_array($arguments)) { $arguments = [$arguments]; } foreach ($arguments as &$value) { if (null === $value) { $value = '(null)'; } } $this->_arguments = $arguments; unset($this->_tmpArguments); } return $this->_arguments; } public function getRawMessage() { return $this->_rawMessage; } public function getFormattedMessage() { return $this->getMessage(); } public function getFrom() { $trace = $this->getBacktrace(); $from = '{main}'; if (!empty($trace)) { $t = $trace[0]; $from = ''; if (isset($t['class'])) { $from .= $t['class'] . '::'; } if (isset($t['function'])) { $from .= $t['function'] . '()'; } } return $from; } public function raise($previous = false) { $message = $this->getFormattedMessage(); $trace = $this->getBacktrace(); $file = '/dev/null'; $line = -1; $pre = $this->getFrom(); if (!empty($trace)) { $file = isset($trace['file']) ? $trace['file'] : null; $line = isset($trace['line']) ? $trace['line'] : null; } $pre .= ': '; try { $out = $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 'in ' . $this->getFile() . ' at line ' . $this->getLine() . '.'; } catch (\Exception $e) { $out = $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 'in ' . $file . ' around line ' . $line . '.'; } if (true === $previous && null !== $previous = $this->getPreviousThrow()) { $out .= "\n\n" . ' ⬇' . "\n\n" . 'Nested exception (' . get_class($previous) . '):' . "\n" . ($previous instanceof self ? $previous->raise(true) : $previous->getMessage()); } return $out; } public static function uncaught($exception) { if (!($exception instanceof self)) { throw $exception; } while (0 < ob_get_level()) { ob_end_flush(); } echo 'Uncaught exception (' . get_class($exception) . '):' . "\n" . $exception->raise(true); return; } public function __toString() { return $this->raise(); } public static function enableUncaughtHandler($enable = true) { if (false === $enable) { return restore_exception_handler(); } return set_exception_handler(function ($exception) { return self::uncaught($exception); }); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Exception'); } public function case_get_backtrace() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->getBacktrace()) ->then ->array($result) ->hasKey(0) ->array($result[0]) ->hasKey('file') ->hasKey('line') ->hasKey('function') ->hasKey('class') ->hasKey('type') ->hasKey('args'); } public function case_get_previous_throw() { $this ->given( $previous = new SUT('previous'), $exception = new SUT('foo', 0, [], $previous) ) ->when($result = $exception->getPreviousThrow()) ->then ->object($result) ->isIdenticalTo($previous); } public function case_get_arguments() { $this ->given($exception = new SUT('foo', 0, ['arg', 42, null])) ->when($result = $exception->getArguments()) ->then ->array($result) ->isEqualTo(['arg', 42, '(null)']); } public function case_get_arguments_from_a_string() { $this ->given($exception = new SUT('foo', 0, 'arg')) ->when($result = $exception->getArguments()) ->then ->array($result) ->isEqualTo(['arg']); } public function case_get_raw_message() { $this ->given( $message = 'foo %s', $exception = new SUT($message) ) ->when($result = $exception->getRawMessage()) ->then ->string($result) ->isEqualTo($message); } public function case_get_formatted_message() { $this ->given( $message = 'foo %s', $exception = new SUT($message, 0, 'bar') ) ->when($result = $exception->getFormattedMessage()) ->then ->string($result) ->isEqualTo($exception->getMessage()) ->isEqualTo('foo bar'); } public function case_get_from_object() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->getFrom()) ->then ->string($result) ->isEqualTo(__METHOD__ . '()'); } public function case_raise() { $this ->given($exception = new SUT('foo'), $line = __LINE__) ->when($result = $exception->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_raise_with_previous() { $this ->given( $previous = new SUT('previous'), $previousLine = __LINE__, $exception = new SUT('foo', 0, [], $previous), $line = __LINE__ ) ->when($result = $exception->raise(true)) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' . "\n\n" . ' ⬇' . "\n\n" . 'Nested exception (' . get_class($previous) . '):' . "\n" . __METHOD__ . '(): (0) previous' . "\n" . 'in ' . __FILE__ . ' at line ' . $previousLine . '.' ); } public function case_uncaught() { $this ->given( $this->function->ob_get_level = 0, $exception = new SUT('foo'), $line = __LINE__ ) ->when($result = SUT::uncaught($exception)) ->then ->variable($result) ->isNull() ->output ->isEqualTo( 'Uncaught exception (' . get_class($exception) . '):' . "\n" . __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_uncaught_not_Hoa() { $this ->exception(function () { SUT::uncaught(new \Exception('foo')); }) ->isInstanceOf('Exception') ->output ->isEmpty(); } public function case_to_string() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->__toString()) ->then ->string($result) ->isEqualTo($exception->raise()); } public function case_disable_uncaught_handler() { $this ->given( $this->function->restore_exception_handler = function () use (&$called) { $called = true; return null; } ) ->when($result = SUT::enableUncaughtHandler(false)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_enable_uncaught_handler() { $self = $this; $this ->given( $this->function->set_exception_handler = function ($handler) use ($self, &$called) { $called = true; $self ->object($handler) ->isInstanceOf('Closure') ->let($reflection = new \ReflectionObject($handler)) ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters()) ->hasSize(1) ->string($invokeParameters[0]->getName()) ->isEqualTo('exception'); return null; } ) ->when($result = SUT::enableUncaughtHandler()) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Idle'); } public function case_event_is_registered() { $this ->given(new SUT('foo')) ->when($result = Event::eventExists('hoa://Event/Exception')) ->then ->boolean($result) ->isTrue(); } public function case_event_is_sent() { $self = $this; $this ->given( Event::getEvent('hoa://Event/Exception')->attach( function (Event\Bucket $bucket) use ($self, &$called) { $called = true; $self ->object($bucket->getSource()) ->isInstanceOf('Hoa\Exception\Exception') ->string($bucket->getSource()->getMessage()) ->isEqualTo('foo') ->object($bucket->getData()) ->isIdenticalTo($bucket->getSource()); } ) ) ->when(new SUT('foo')) ->then ->boolean($called) ->isTrue(); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception') ->isInstanceOf('ArrayAccess') ->isInstanceOf('IteratorAggregate') ->isInstanceOf('Countable'); } public function case_constructor() { $this ->given( $message = 'foo %s %d %s', $code = 7, $arguments = ['arg', 42, null], $previous = new SUT('previous') ) ->when($result = new SUT($message, $code, $arguments, $previous), $line = __LINE__) ->then ->string($result->getMessage()) ->isEqualTo('foo arg 42 (null)') ->integer($result->getCode()) ->isEqualTo(7) ->array($result->getArguments()) ->isEqualTo(['arg', 42, '(null)']) ->object($result->getPreviousThrow()) ->isIdenticalTo($previous) ->boolean($result->hasUncommittedExceptions()) ->isFalse(); } public function case_raise_zero_exception() { $this ->given($group = new SUT('foo'), $line = __LINE__) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_raise_one_exception() { $this ->given( $exception1 = new SUT('bar'), $barLine = __LINE__, $group = new SUT('foo'), $fooLine = __LINE__, $group[] = $exception1 ) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" . 'Contains the following exceptions:' . "\n\n" . ' • ' . __METHOD__ . '(): (0) bar' . "\n" . ' in ' . __FILE__ . ' at line ' . $barLine . '.' ); } public function case_raise_more_exceptions() { $this ->given( $exception1 = new SUT('bar'), $barLine = __LINE__, $exception2 = new SUT('baz'), $bazLine = __LINE__, $group = new SUT('foo'), $fooLine = __LINE__, $group[] = $exception1, $group[] = $exception2 ) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" . 'Contains the following exceptions:' . "\n\n" . ' • ' . __METHOD__ . '(): (0) bar' . "\n" . ' in ' . __FILE__ . ' at line ' . $barLine . '.' . "\n\n" . ' • ' . __METHOD__ . '(): (0) baz' . "\n" . ' in ' . __FILE__ . ' at line ' . $bazLine . '.' ); } public function case_begin_transaction() { $this ->given( $group = new SUT('foo'), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->beginTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(1) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize + 1); } public function case_rollback_transaction_with_an_empty_stack() { $this ->given( $group = new SUT('foo'), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->rollbackTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(1) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize); } public function case_rollback_transaction() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $oldStackSize = $group->getStackSize(), $group->rollbackTransaction() ) ->when( $result = $group->rollbackTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(3) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize - 2); } public function case_commit_transaction_with_an_empty_stack() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $oldCount = count($group), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->commitTransaction(), $count = count($group), $stackSize = $group->getStackSize() ) ->then ->integer($oldCount) ->isEqualTo(0) ->integer($oldStackSize) ->isEqualTo(2) ->object($result) ->isIdenticalTo($group) ->integer($count) ->isEqualTo($oldCount) ->integer($stackSize) ->isEqualTo($oldStackSize - 1); } public function case_commit_transaction() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group[] = $exception1, $group[] = $exception2, $oldCount = count($group), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->commitTransaction(), $count = count($group), $stackSize = $group->getStackSize() ) ->then ->integer($oldCount) ->isEqualTo(0) ->integer($oldStackSize) ->isEqualTo(2) ->object($result) ->isIdenticalTo($group) ->integer($count) ->isEqualTo($oldCount + 2) ->integer($stackSize) ->isEqualTo($oldStackSize - 1) ->array(iterator_to_array($group->getIterator())) ->isEqualTo([ 0 => $exception1, 1 => $exception2 ]); } public function case_has_uncommitted_exceptions() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group[] = new SUT('bar') ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isTrue(); } public function case_has_no_uncommitted_exceptions() { $this ->given( $group = new SUT('foo'), $group->beginTransaction() ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isFalse(); } public function case_has_no_uncommitted_exceptions_with_empty_stack() { $this ->given( $group = new SUT('foo'), $group[] = new SUT('bar') ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isFalse(); } public function case_offset_exists_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isTrue(); } public function case_offset_does_not_exist_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('baz')) ->then ->boolean($result) ->isFalse(); } public function case_offset_exists() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isTrue(); } public function case_offset_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('baz')) ->then ->boolean($result) ->isFalse(); } public function case_offset_get_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('bar')) ->then ->object($result) ->isIdenticalTo($exception1); } public function case_offset_get_does_not_exist_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('baz')) ->then ->variable($result) ->isNull(); } public function case_offset_get() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('bar')) ->then ->object($result) ->isIdenticalTo($exception1); } public function case_offset_get_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('baz')) ->then ->variable($result) ->isNull(); } public function case_offset_set_not_an_exception() { $this ->given($group = new SUT('foo')) ->when($group->offsetSet('bar', new \StdClass())) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_set() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isFalse() ->when($group->offsetSet('bar', $exception1)) ->then ->boolean($group->offsetExists('bar')) ->isTrue() ->object($group->offsetGet('bar')) ->isIdenticalTo($exception1); } public function case_offset_set_with_a_null_index() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($group->offsetSet(null, $exception1)) ->then ->boolean($group->offsetExists(0)) ->isTrue() ->object($group->offsetGet(0)) ->isIdenticalTo($exception1); } public function case_offset_set_with_an_integer_index() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($group->offsetSet(42, $exception1)) ->then ->boolean($group->offsetExists(42)) ->isFalse() ->boolean($group->offsetExists(0)) ->isTrue() ->object($group->offsetGet(0)) ->isIdenticalTo($exception1); } public function case_offset_unset_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset_does_not_exist_with_no_uncommited_exceptions() { $this ->given($group = new SUT('foo')) ->when($group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction() ) ->when($result = $group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_get_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = $group->getExceptions()) ->then ->object($result) ->isInstanceOf('ArrayObject') ->object($result['bar']) ->isIdenticalTo($exception1); } public function case_get_iterator() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->getIterator()) ->then ->object($result) ->isInstanceOf('ArrayIterator') ->array(iterator_to_array($result)) ->isEqualTo([ 'bar' => $exception1 ]); } public function case_count() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = count($group)) ->then ->integer($result) ->isEqualTo(1); } public function get_get_stack_size() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = $group->getStackSize()) ->then ->integer($result) ->isEqualTo(2); } } when($result = new SUT('foo', 42, '/hoa/flatland', 153)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } public function case_get_message() { $this ->given($exception = new SUT('foo', 42, '/hoa/flatland', 153)) ->when($result = $exception->raise()) ->then ->string($result) ->isEqualTo( '{main}: (42) foo' . "\n" . 'in /hoa/flatland at line 153.' ); } public function case_disable_error_handler() { $this ->given( $this->function->restore_error_handler = function () use (&$called) { $called = true; return null; } ) ->when($result = SUT::enableErrorHandler(false)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_enable_error_handler() { $self = $this; $this ->given( $this->function->set_error_handler = function ($handler) use ($self, &$called) { $called = true; $self ->object($handler) ->isInstanceOf('Closure') ->let($reflection = new \ReflectionObject($handler)) ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters()) ->hasSize(5) ->string($invokeParameters[0]->getName()) ->isEqualTo('no') ->string($invokeParameters[1]->getName()) ->isEqualTo('str') ->string($invokeParameters[2]->getName()) ->isEqualTo('file') ->boolean($invokeParameters[2]->isOptional()) ->isTrue() ->string($invokeParameters[3]->getName()) ->isEqualTo('line') ->boolean($invokeParameters[3]->isOptional()) ->isTrue() ->string($invokeParameters[4]->getName()) ->isEqualTo('ctx') ->boolean($invokeParameters[4]->isOptional()) ->isTrue(); return null; } ) ->when($result = SUT::enableErrorHandler()) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_error_handler() { $this ->given(SUT::enableErrorHandler()) ->exception(function () { ++$i; }) ->isInstanceOf('Hoa\Exception\Error') ->hasMessage('Undefined variable: i'); } } send(); return; } public function send() { Event::notify( 'hoa://Event/Exception', $this, new Event\Bucket($this) ); return; } } Consistency::flexEntity('Hoa\Exception\Exception'); _group = new \SplStack(); $this->beginTransaction(); return; } public function raise($previous = false) { $out = parent::raise($previous); if (0 >= count($this)) { return $out; } $out .= "\n\n" . 'Contains the following exceptions:'; foreach ($this as $exception) { $out .= "\n\n" . ' • ' . str_replace( "\n", "\n" . ' ', $exception->raise($previous) ); } return $out; } public function beginTransaction() { $this->_group->push(new \ArrayObject()); return $this; } public function rollbackTransaction() { if (1 >= count($this->_group)) { return $this; } $this->_group->pop(); return $this; } public function commitTransaction() { if (false === $this->hasUncommittedExceptions()) { $this->_group->pop(); return $this; } foreach ($this->_group->pop() as $index => $exception) { $this[$index] = $exception; } return $this; } public function hasUncommittedExceptions() { return 1 < count($this->_group) && 0 < count($this->_group->top()); } public function offsetExists($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { return true; } } return false; } public function offsetGet($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { return $group[$index]; } } return null; } public function offsetSet($index, $exception) { if (!($exception instanceof \Exception)) { return null; } $group = $this->_group->top(); if (null === $index || true === is_int($index)) { $group[] = $exception; } else { $group[$index] = $exception; } return; } public function offsetUnset($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { unset($group[$index]); } } return; } public function getExceptions() { return $this->_group->bottom(); } public function getIterator() { return $this->getExceptions()->getIterator(); } public function count() { return count($this->getExceptions()); } public function getStackSize() { return count($this->_group); } } file = $file; $this->line = $line; $this->_trace = $trace; parent::__construct($message, $code); return; } public static function enableErrorHandler($enable = true) { if (false === $enable) { return restore_error_handler(); } return set_error_handler( function ($no, $str, $file = null, $line = null, $ctx = null) { if (0 === ($no & error_reporting())) { return; } $trace = debug_backtrace(); array_shift($trace); array_shift($trace); throw new Error($str, $no, $file, $line, $trace); } ); } } getStreamName()); } public function getDirname() { return dirname($this->getStreamName()); } public function getSize() { if (false === $this->getStatistic()) { return false; } return filesize($this->getStreamName()); } public function getStatistic() { return fstat($this->getStream()); } public function getATime() { return fileatime($this->getStreamName()); } public function getCTime() { return filectime($this->getStreamName()); } public function getMTime() { return filemtime($this->getStreamName()); } public function getGroup() { return filegroup($this->getStreamName()); } public function getOwner() { return fileowner($this->getStreamName()); } public function getPermissions() { return fileperms($this->getStreamName()); } public function getReadablePermissions() { $p = $this->getPermissions(); if (($p & 0xC000) == 0xC000) { $out = 's'; } elseif (($p & 0xA000) == 0xA000) { $out = 'l'; } elseif (($p & 0x8000) == 0x8000) { $out = '-'; } elseif (($p & 0x6000) == 0x6000) { $out = 'b'; } elseif (($p & 0x4000) == 0x4000) { $out = 'd'; } elseif (($p & 0x2000) == 0x2000) { $out = 'c'; } elseif (($p & 0x1000) == 0x1000) { $out = 'p'; } else { $out = 'u'; } $out .= (($p & 0x0100) ? 'r' : '-') . (($p & 0x0080) ? 'w' : '-') . (($p & 0x0040) ? (($p & 0x0800) ? 's' : 'x') : (($p & 0x0800) ? 'S' : '-')) . (($p & 0x0020) ? 'r' : '-') . (($p & 0x0010) ? 'w' : '-') . (($p & 0x0008) ? (($p & 0x0400) ? 's' : 'x') : (($p & 0x0400) ? 'S' : '-')) . (($p & 0x0004) ? 'r' : '-') . (($p & 0x0002) ? 'w' : '-') . (($p & 0x0001) ? (($p & 0x0200) ? 't' : 'x') : (($p & 0x0200) ? 'T' : '-')); return $out; } public function isReadable() { return is_readable($this->getStreamName()); } public function isWritable() { return is_writable($this->getStreamName()); } public function isExecutable() { return is_executable($this->getStreamName()); } public function clearStatisticCache() { clearstatcache(true, $this->getStreamName()); return; } public static function clearAllStatisticCaches() { clearstatcache(); return; } public function touch($time = -1, $atime = -1) { if ($time == -1) { $time = time(); } if ($atime == -1) { $atime = $time; } return touch($this->getStreamName(), $time, $atime); } public function copy($to, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE) { $from = $this->getStreamName(); if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && true === file_exists($to)) { return true; } if (null === $this->getStreamContext()) { return @copy($from, $to); } return @copy($from, $to, $this->getStreamContext()->getContext()); } public function move( $name, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE, $mkdir = Stream\IStream\Touchable::DO_NOT_MAKE_DIRECTORY ) { $from = $this->getStreamName(); if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && true === file_exists($name)) { return false; } if (Stream\IStream\Touchable::MAKE_DIRECTORY === $mkdir) { Directory::create( dirname($name), Directory::MODE_CREATE_RECURSIVE ); } if (null === $this->getStreamContext()) { return @rename($from, $name); } return @rename($from, $name, $this->getStreamContext()->getContext()); } public function delete() { if (null === $this->getStreamContext()) { return @unlink($this->getStreamName()); } return @unlink( $this->getStreamName(), $this->getStreamContext()->getContext() ); } public function changeGroup($group) { return chgrp($this->getStreamName(), $group); } public function changeMode($mode) { return chmod($this->getStreamName(), $mode); } public function changeOwner($user) { return chown($this->getStreamName(), $user); } public static function umask($umask = null) { if (null === $umask) { return umask(); } return umask($umask); } public function isFile() { return is_file($this->getStreamName()); } public function isLink() { return is_link($this->getStreamName()); } public function isDirectory() { return is_dir($this->getStreamName()); } public function isSocket() { return filetype($this->getStreamName()) == 'socket'; } public function isFIFOPipe() { return filetype($this->getStreamName()) == 'fifo'; } public function isCharacterSpecial() { return filetype($this->getStreamName()) == 'char'; } public function isBlockSpecial() { return filetype($this->getStreamName()) == 'block'; } public function isUnknown() { return filetype($this->getStreamName()) == 'unknown'; } protected function setMode($mode) { $old = $this->_mode; $this->_mode = $mode; return $old; } public function getMode() { return $this->_mode; } public function getINode() { return fileinode($this->getStreamName()); } public static function isCaseSensitive() { return !( file_exists(mb_strtolower(__FILE__)) && file_exists(mb_strtoupper(__FILE__)) ); } public function getRealPath() { if (false === $out = realpath($this->getStreamName())) { return $this->getStreamName(); } return $out; } public function getExtension() { return pathinfo( $this->getStreamName(), PATHINFO_EXTENSION ); } public function getFilename() { $file = basename($this->getStreamName()); if (defined('PATHINFO_FILENAME')) { return pathinfo($file, PATHINFO_FILENAME); } if (strstr($file, '.')) { return substr($file, 0, strrpos($file, '.')); } return $file; } } setMode($mode); parent::__construct($streamName, $context, $wait); return; } protected function &_open($streamName, Stream\Context $context = null) { if (false === is_dir($streamName)) { if ($this->getMode() == self::MODE_READ) { throw new Exception\FileDoesNotExist( 'Directory %s does not exist.', 0, $streamName ); } else { self::create( $streamName, $this->getMode(), null !== $context ? $context->getContext() : null ); } } $out = null; return $out; } protected function _close() { return true; } public function copy($to, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE) { if (empty($to)) { throw new Exception( 'The destination path (to copy) is empty.', 1 ); } $from = $this->getStreamName(); $fromLength = strlen($from) + 1; $finder = new Finder(); $finder->in($from); self::create($to, self::MODE_CREATE_RECURSIVE); foreach ($finder as $file) { $relative = substr($file->getPathname(), $fromLength); $_to = $to . DS . $relative; if (true === $file->isDir()) { self::create($_to, self::MODE_CREATE); continue; } $handle = null; if (true === $file->isFile()) { $handle = new Read($file->getPathname()); } elseif (true === $file->isDir()) { $handle = new Directory($file->getPathName()); } elseif (true === $file->isLink()) { $handle = new Link\Read($file->getPathName()); } if (null !== $handle) { $handle->copy($_to, $force); $handle->close(); } } return true; } public function delete() { $from = $this->getStreamName(); $finder = new Finder(); $finder->in($from) ->childFirst(); foreach ($finder as $file) { $file->open()->delete(); $file->close(); } if (null === $this->getStreamContext()) { return @rmdir($from); } return @rmdir($from, $this->getStreamContext()->getContext()); } public static function create( $name, $mode = self::MODE_CREATE_RECURSIVE, $context = null ) { if (true === is_dir($name)) { return true; } if (empty($name)) { return false; } if (null !== $context) { if (false === Stream\Context::contextExists($context)) { throw new Exception( 'Context %s was not previously declared, cannot retrieve ' . 'this context.', 2, $context ); } else { $context = Stream\Context::getInstance($context); } } if (null === $context) { return @mkdir( $name, 0755, self::MODE_CREATE_RECURSIVE === $mode ); } return @mkdir( $name, 0755, self::MODE_CREATE_RECURSIVE === $mode, $context->getContext() ); } } isFile()) { return $this->_stream = new ReadWrite($this->getPathname()); } elseif (true === $this->isDir()) { return $this->_stream = new Directory($this->getPathname()); } elseif (true === $this->isLink()) { return $this->_stream = new Link\ReadWrite($this->getPathname()); } throw new Exception('%s has an unknown type.', 0, $this->getPathname()); } public function close() { if (null === $this->_stream) { return; } return $this->_stream->close(); } public function __destruct() { $this->close(); return; } } _flags = Iterator\FileSystem::KEY_AS_PATHNAME | Iterator\FileSystem::CURRENT_AS_FILEINFO | Iterator\FileSystem::SKIP_DOTS; $this->_first = Iterator\Recursive\Iterator::SELF_FIRST; return; } public function in($paths) { if (!is_array($paths)) { $paths = [$paths]; } foreach ($paths as $path) { if (1 === preg_match('/[\*\?\[\]]/', $path)) { $iterator = new Iterator\CallbackFilter( new Iterator\Glob(rtrim($path, DS)), function ($current) { return $current->isDir(); } ); foreach ($iterator as $fileInfo) { $this->_paths[] = $fileInfo->getPathname(); } } else { $this->_paths[] = $path; } } return $this; } public function maxDepth($depth) { $this->_maxDepth = $depth; return $this; } public function files() { $this->_types[] = 'file'; return $this; } public function directories() { $this->_types[] = 'dir'; return $this; } public function links() { $this->_types[] = 'link'; return $this; } public function followSymlinks($flag = true) { if (true === $flag) { $this->_flags ^= Iterator\FileSystem::FOLLOW_SYMLINKS; } else { $this->_flags |= Iterator\FileSystem::FOLLOW_SYMLINKS; } return $this; } public function name($regex) { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { return 0 !== preg_match($regex, $current->getBasename()); }; return $this; } public function notIn($regex) { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { foreach (explode(DS, $current->getPathname()) as $part) { if (0 !== preg_match($regex, $part)) { return false; } } return true; }; return $this; } public function size($size) { if (0 === preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) { return $this; } $number = floatval($matches[2]); $unit = isset($matches[3]) ? $matches[3] : 'b'; $operator = $matches[1]; switch ($unit) { case 'b': break; case 'Kb': $number <<= 10; break; case 'Mb': $number <<= 20; break; case 'Gb': $number <<= 30; break; case 'Tb': $number *= 1099511627776; break; case 'Pb': $number *= pow(1024, 5); break; case 'Eb': $number *= pow(1024, 6); break; case 'Zb': $number *= pow(1024, 7); break; case 'Yb': $number *= pow(1024, 8); break; } $filter = null; switch ($operator) { case '<': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() < $number; }; break; case '<=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() <= $number; }; break; case '>': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() > $number; }; break; case '>=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() >= $number; }; break; case '=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() === $number; }; break; } $this->_filters[] = $filter; return $this; } public function dots($flag = true) { if (true === $flag) { $this->_flags ^= Iterator\FileSystem::SKIP_DOTS; } else { $this->_flags |= Iterator\FileSystem::SKIP_DOTS; } return $this; } public function owner($owner) { $this->_filters[] = function (\SplFileInfo $current) use ($owner) { return $current->getOwner() === $owner; }; return $this; } protected function formatDate($date, &$operator) { $operator = -1; if (0 === preg_match('#\bago\b#', $date)) { $date .= ' ago'; } if (0 !== preg_match('#^(since|until)\b(.+)$#', $date, $matches)) { $time = strtotime($matches[2]); if ('until' === $matches[1]) { $operator = 1; } } else { $time = strtotime($date); } return $time; } public function changed($date) { $time = $this->formatDate($date, $operator); if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() < $time; }; } return $this; } public function modified($date) { $time = $this->formatDate($date, $operator); if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() < $time; }; } return $this; } public function filter($callback) { $this->_filters[] = $callback; return $this; } public function sortByName($locale = 'root') { if (true === class_exists('Collator', false)) { $collator = new \Collator($locale); $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) { return $collator->compare($a->getPathname(), $b->getPathname()); }; } else { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getPathname(), $b->getPathname()); }; } return $this; } public function sortBySize() { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return $a->getSize() < $b->getSize(); }; return $this; } public function sort($callable) { $this->_sorts[] = $callable; return $this; } public function childFirst() { $this->_first = Iterator\Recursive\Iterator::CHILD_FIRST; return $this; } public function getIterator() { $_iterator = new Iterator\Append(); $types = $this->getTypes(); if (!empty($types)) { $this->_filters[] = function (\SplFileInfo $current) use ($types) { return in_array($current->getType(), $types); }; } $maxDepth = $this->getMaxDepth(); $splFileInfo = $this->getSplFileInfo(); foreach ($this->getPaths() as $path) { if (1 == $maxDepth) { $iterator = new Iterator\IteratorIterator( new Iterator\Recursive\Directory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() ); } else { $iterator = new Iterator\Recursive\Iterator( new Iterator\Recursive\Directory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() ); if (1 < $maxDepth) { $iterator->setMaxDepth($maxDepth - 1); } } $_iterator->append($iterator); } foreach ($this->getFilters() as $filter) { $_iterator = new Iterator\CallbackFilter( $_iterator, $filter ); } $sorts = $this->getSorts(); if (empty($sorts)) { return $_iterator; } $array = iterator_to_array($_iterator); foreach ($sorts as $sort) { uasort($array, $sort); } return new Iterator\Map($array); } public function setSplFileInfo($splFileInfo) { $old = $this->_splFileInfo; $this->_splFileInfo = $splFileInfo; return $old; } public function getSplFileInfo() { return $this->_splFileInfo; } protected function getPaths() { return $this->_paths; } public function getMaxDepth() { return $this->_maxDepth; } public function getTypes() { return $this->_types; } protected function getFilters() { return $this->_filters; } protected function getSorts() { return $this->_sorts; } public function getFlags() { return $this->_flags; } public function getFirst() { return $this->_first; } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } setListener( new Event\Listener( $this, [ 'new', 'modify', 'move' ] ) ); if (null !== $latency) { $this->setLatency($latency); } return; } public function run() { $iterator = $this->getIterator(); $previous = iterator_to_array($iterator); $current = $previous; while (true) { foreach ($current as $name => $c) { if (!isset($previous[$name])) { $this->getListener()->fire( 'new', new Event\Bucket([ 'file' => $c ]) ); continue; } if (null === $c->getHash()) { unset($current[$name]); continue; } if ($previous[$name]->getHash() != $c->getHash()) { $this->getListener()->fire( 'modify', new Event\Bucket([ 'file' => $c ]) ); } unset($previous[$name]); } foreach ($previous as $p) { $this->getListener()->fire( 'move', new Event\Bucket([ 'file' => $p ]) ); } usleep($this->getLatency() * 1000000); $previous = $current; $current = iterator_to_array($iterator); } return; } public function setLatency($latency) { $old = $this->_latency; $this->_latency = $latency; return $old; } public function getLatency() { return $this->_latency; } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_TRUNCATE_WRITE == $this->getMode()) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getStreamName()); } public function changeGroup($group) { return lchgrp($this->getStreamName(), $group); } public function changeOwner($user) { return lchown($this->getStreamName(), $user); } public function getPermissions() { return 41453; } public function getTarget() { $target = dirname($this->getStreamName()) . DS . $this->getTargetName(); $context = null !== $this->getStreamContext() ? $this->getStreamContext()->getCurrentId() : null; if (true === is_link($target)) { return new ReadWrite( $target, File::MODE_APPEND_READ_WRITE, $context ); } elseif (true === is_file($target)) { return new File\ReadWrite( $target, File::MODE_APPEND_READ_WRITE, $context ); } elseif (true === is_dir($target)) { return new File\Directory( $target, File::MODE_READ, $context ); } throw new File\Exception( 'Cannot find an appropriated object that matches with ' . 'path %s when defining it.', 1, $target ); } public function getTargetName() { return readlink($this->getStreamName()); } public static function create($name, $target) { if (false != linkinfo($name)) { return true; } return symlink($target, $name); } } Consistency::flexEntity('Hoa\File\Link\Link'); setMode($mode); switch ($streamName) { case '0': $streamName = 'php://stdin'; break; case '1': $streamName = 'php://stdout'; break; case '2': $streamName = 'php://stderr'; break; default: if (true === ctype_digit($streamName)) { if (PHP_VERSION_ID >= 50306) { $streamName = 'php://fd/' . $streamName; } else { throw new Exception( 'You need PHP5.3.6 to use a file descriptor ' . 'other than 0, 1 or 2 (tried %d with PHP%s).', 0, [$streamName, PHP_VERSION] ); } } } parent::__construct($streamName, $context, $wait); return; } protected function &_open($streamName, Stream\Context $context = null) { if (substr($streamName, 0, 4) == 'file' && false === is_dir(dirname($streamName))) { throw new Exception( 'Directory %s does not exist. Could not open file %s.', 1, [dirname($streamName), basename($streamName)] ); } if (null === $context) { if (false === $out = @fopen($streamName, $this->getMode(), true)) { throw new Exception( 'Failed to open stream %s.', 2, $streamName ); } return $out; } $out = @fopen( $streamName, $this->getMode(), true, $context->getContext() ); if (false === $out) { throw new Exception( 'Failed to open stream %s.', 3, $streamName ); } return $out; } protected function _close() { return @fclose($this->getStream()); } public function newBuffer($callable = null, $size = null) { $this->setStreamBuffer($size); return 1; } public function flush() { return fflush($this->getStream()); } public function deleteBuffer() { return $this->disableStreamBuffer(); } public function getBufferLevel() { return 1; } public function getBufferSize() { return $this->getStreamBufferSize(); } public function lock($operation) { return flock($this->getStream(), $operation); } public function rewind() { return rewind($this->getStream()); } public function seek($offset, $whence = Stream\IStream\Pointable::SEEK_SET) { return fseek($this->getStream(), $offset, $whence); } public function tell() { $stream = $this->getStream(); if (null === $stream) { return 0; } return ftell($stream); } public static function create($name, $dummy) { if (file_exists($name)) { return true; } return touch($name); } } Consistency::flexEntity('Hoa\File\File'); _case_get_mode_xxx(0010000, SUT::IS_FIFO); } public function case_get_mode_character() { return $this->_case_get_mode_xxx(0020000, SUT::IS_CHARACTER); } public function case_get_mode_directory() { return $this->_case_get_mode_xxx(0040000, SUT::IS_DIRECTORY); } public function case_get_mode_block() { return $this->_case_get_mode_xxx(0060000, SUT::IS_BLOCK); } public function case_get_mode_regular() { return $this->_case_get_mode_xxx(0100000, SUT::IS_REGULAR); } public function case_get_mode_link() { return $this->_case_get_mode_xxx(0120000, SUT::IS_LINK); } public function case_get_mode_socket() { return $this->_case_get_mode_xxx(0140000, SUT::IS_SOCKET); } public function case_get_mode_whiteout() { return $this->_case_get_mode_xxx(0160000, SUT::IS_WHITEOUT); } public function case_get_mode_unknown() { return $this->_case_get_mode_xxx(0170000, -1); } protected function _case_get_mode_xxx($mask, $expect) { $this ->given($this->function->fstat = ['mode' => $mask & 0170000]) ->when($result = SUT::getMode(null)) ->then ->integer($result) ->isEqualTo($expect); } public function case_set_input() { $this ->given($input = new LUT\Input()) ->when($result = SUT::setInput($input)) ->then ->variable($result) ->isNull() ->object(SUT::getInput()) ->isIdenticalTo($input); } public function case_get_input() { $this ->when($result = SUT::getInput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Input') ->isIdenticalTo(SUT::getInput()); } public function case_set_output() { $this ->given($output = new LUT\Output()) ->when($result = SUT::setOutput($output)) ->then ->variable($result) ->isNull() ->object(SUT::getOutput()) ->isIdenticalTo($output); } public function case_get_output() { $this ->when($result = SUT::getOutput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Output') ->isIdenticalTo(SUT::getOutput()); } public function case_set_tput() { $this ->given($tput = new LUT\Tput('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = SUT::setTput($tput)) ->then ->variable($result) ->isNull() ->object(SUT::getTput()) ->isIdenticalTo($tput); } public function case_get_tput() { $this ->when($result = SUT::getTput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Tput') ->isIdenticalTo(SUT::getTput()); } public function case_is_tmux_running() { $this ->given($_SERVER['TMUX'] = 'foo') ->when($result = SUT::isTmuxRunning()) ->then ->boolean($result) ->isTrue(); } public function case_is_not_tmux_running() { unset($_SERVER['TMUX']); $this ->when($result = SUT::isTmuxRunning()) ->then ->boolean($result) ->isFalse(); } } when($result = SUT::getInstance()) ->then ->object($result) ->isIdenticalTo(SUT::getInstance()); } public function case_track_button_left() { return $this->_case_track( 7, 42, SUT::BUTTON_LEFT, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'left', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_middle() { return $this->_case_track( 7, 42, SUT::BUTTON_MIDDLE, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'middle', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_right() { return $this->_case_track( 7, 42, SUT::BUTTON_RIGHT, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'right', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_release() { return $this->_case_track( 7, 42, SUT::BUTTON_RELEASE, 'mouseup', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_wheelup() { return $this->_case_track( 7, 42, SUT::WHEEL_UP, 'wheelup', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_wheeldown() { return $this->_case_track( 7, 42, SUT::WHEEL_DOWN, 'wheeldown', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function _case_track($x, $y, $pointerActionCode, $listenerName, array $listenerData) { $this ->given( $self = $this, $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll( "\033[M" . chr(($pointerActionCode + 32) & ~28) . chr($x + 32) . chr($y + 32) ), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { static $i = 1; if (1 === $i) { return $i--; } return false; }, SUT::getInstance()->on( $listenerName, function (Event\Bucket $bucket) use (&$_listenerData) { $_listenerData = $bucket->getData(); return; } ) ) ->when(SUT::track()) ->then ->output ->isEqualTo( "\033[1;2'z" . "\033[?1000h" . "\033[?1003h" . "\033[?1003l" . "\033[?1000l" ) ->array($_listenerData) ->isEqualTo($listenerData); } public function case_untrack_when_not_tracked() { $this ->when($result = SUT::untrack()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('Hoa\Stream\IStream\In'); } public function case_eof() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $input = new SUT($file) ) ->when($result = $input->eof()) ->then ->boolean($result) ->isEqualTo($file->eof()); } public function case_read() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->read(3)) ->then ->string($result) ->isEqualTo('foo'); } public function case_read_string() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readString(3)) ->then ->string($result) ->isEqualTo('foo'); } public function case_read_character() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readCharacter(1)) ->then ->string($result) ->isEqualTo('f'); } public function case_read_boolean_true() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('1'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readBoolean()) ->then ->boolean($result) ->isTrue(); } public function case_read_boolean_false() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('0'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readBoolean()) ->then ->boolean($result) ->isFalse(); } public function case_read_integer() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('42'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readInteger(2)) ->then ->integer($result) ->isEqualTo(42); } public function case_read_float() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('4.2'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readFloat(3)) ->then ->float($result) ->isEqualTo(4.2); } public function case_read_array() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo bar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readArray('%s %s')) ->then ->array($result) ->isEqualTo([ 0 => 'foo', 1 => 'bar' ]); } public function case_read_line() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo' . "\n" . 'bar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readLine()) ->then ->string($result) ->isEqualTo('foo' . "\n"); } public function case_read_all() { $this ->given( $content = '4.2foo' . "\n" . 'bar', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll($content), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readAll()) ->then ->string($result) ->isEqualTo($content); } public function case_scanf() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo 42' . "\n" . 'bar 153'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->scanf('%s %d')) ->then ->array($result) ->isEqualTo([ 0 => 'foo', 1 => 42 ]) ->when($result = $input->scanf('%s %d')) ->then ->array($result) ->isEqualTo([ 0 => 'bar', 1 => 153 ]); } } given($_SERVER['TERM'] = 'foo') ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('foo'); } public function case_get_unknown_term_on_windows() { unset($_SERVER['TERM']); $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('windows-ansi'); } public function case_get_unknown_term() { unset($_SERVER['TERM']); $this ->given($this->constant->OS_WIN = false) ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('xterm'); } public function case_unknown_file_when_parsing() { $this ->exception(function () { new SUT('/hoa/flatland'); }) ->isInstanceOf('Hoa\Console\Exception'); } public function case_all_informations() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->getInformations()) ->then ->array($result) ->isIdenticalTo([ 'file' => 'hoa://Library/Console/Terminfo/78/xterm', 'headers' => [ 'data_size' => 3258, 'header_size' => 12, 'magic_number' => 282, 'names_size' => 48, 'bool_count' => 38, 'number_count' => 15, 'string_count' => 413, 'string_table_size' => 1388 ], 'name' => 'xterm', 'description' => 'xterm terminal emulator (X Window System)', 'booleans' => [ 'auto_left_margin' => false, 'auto_right_margin' => true, 'no_esc_ctlc' => false, 'ceol_standout_glitch' => false, 'eat_newline_glitch' => true, 'erase_overstrike' => false, 'generic_type' => false, 'hard_copy' => false, 'meta_key' => true, 'status_line' => false, 'insert_null_glitch' => false, 'memory_above' => false, 'memory_below' => false, 'move_insert_mode' => true, 'move_standout_mode' => true, 'over_strike' => false, 'status_line_esc_ok' => false, 'dest_tabs_magic_smso' => false, 'tilde_glitch' => false, 'transparent_underline' => false, 'xon_xoff' => false, 'needs_xon_xoff' => false, 'prtr_silent' => true, 'hard_cursor' => false, 'non_rev_rmcup' => false, 'no_pad_char' => true, 'non_dest_scroll_region' => false, 'can_change' => false, 'back_color_erase' => true, 'hue_lightness_saturation' => false, 'col_addr_glitch' => false, 'cr_cancels_micro_mode' => false, 'print_wheel' => false, 'row_addr_glitch' => false, 'semi_auto_right_margin' => false, 'cpi_changes_res' => false, 'lpi_changes_res' => false, 'backspaces_with_bs' => true ], 'numbers' => [ 'columns' => 80, 'init_tabs' => 8, 'lines' => 24, 'lines_of_memory' => -1, 'magic_cookie_glitch' => -1, 'padding_baud_rate' => -1, 'virtual_terminal' => -1, 'width_status_line' => -1, 'num_labels' => -1, 'label_height' => -1, 'label_width' => -1, 'max_attributes' => -1, 'maximum_windows' => -1, 'max_colors' => 8, 'max_pairs' => 64 ], 'strings' => [ 'back_tab' => '', 'bell' => '', 'carriage_return' => ' ', 'change_scroll_region' => '[%i%p1%d;%p2%dr', 'clear_all_tabs' => '', 'clear_screen' => '', 'clr_eol' => '', 'clr_eos' => '', 'column_address' => '[%i%p1%dG', 'cursor_address' => '[%i%p1%d;%p2%dH', 'cursor_down' => "\n", 'cursor_home' => '', 'cursor_invisible' => '[?25l', 'cursor_left' => '', 'cursor_normal' => '[?12l[?25h', 'cursor_right' => '', 'cursor_up' => '', 'cursor_visible' => '[?12;25h', 'delete_character' => '', 'delete_line' => '', 'enter_alt_charset_mode' => '(0', 'enter_blink_mode' => '', 'enter_bold_mode' => '', 'enter_ca_mode' => '[?1049h', 'enter_insert_mode' => '', 'enter_secure_mode' => '', 'enter_reverse_mode' => '', 'enter_standout_mode' => '', 'enter_underline_mode' => '', 'erase_chars' => '[%p1%dX', 'exit_alt_charset_mode' => '(B', 'exit_attribute_mode' => '(B', 'exit_ca_mode' => '[?1049l', 'exit_insert_mode' => '', 'exit_standout_mode' => '', 'exit_underline_mode' => '', 'flash_screen' => '[?5h$<100/>[?5l', 'init_2string' => '[!p[?3;4l>', 'insert_line' => '', 'key_backspace' => '', 'key_dc' => '[3~', 'key_down' => 'OB', 'key_f1' => 'OP', 'key_f10' => '[21~', 'key_f2' => 'OQ', 'key_f3' => 'OR', 'key_f4' => 'OS', 'key_f5' => '[15~', 'key_f6' => '[17~', 'key_f7' => '[18~', 'key_f8' => '[19~', 'key_f9' => '[20~', 'key_home' => 'OH', 'key_ic' => '[2~', 'key_left' => 'OD', 'key_npage' => '[6~', 'key_ppage' => '[5~', 'key_right' => 'OC', 'key_sf' => '', 'key_sr' => '', 'key_up' => 'OA', 'keypad_local' => '[?1l>', 'keypad_xmit' => '[?1h=', 'meta_off' => '[?1034l', 'meta_on' => '[?1034h', 'parm_dch' => '[%p1%dP', 'parm_delete_line' => '[%p1%dM', 'parm_down_cursor' => '[%p1%dB', 'parm_ich' => '[%p1%d@', 'parm_index' => '[%p1%dS', 'parm_insert_line' => '[%p1%dL', 'parm_left_cursor' => '[%p1%dD', 'parm_right_cursor' => '[%p1%dC', 'parm_rindex' => '[%p1%dT', 'parm_up_cursor' => '[%p1%dA', 'print_screen' => '', 'prtr_off' => '', 'prtr_on' => '', 'reset_1string' => 'c', 'reset_2string' => '[!p[?3;4l>', 'restore_cursor' => '8', 'row_address' => '[%i%p1%dd', 'save_cursor' => '7', 'scroll_forward' => "\n", 'scroll_reverse' => 'M', 'set_attributes' => '%?%p9%t(0%e(B%;[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m', 'set_tab' => 'H', 'tab' => ' ', 'key_b2' => 'OE', 'acs_chars' => '``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~', 'key_btab' => '', 'enter_am_mode' => '[?7h', 'exit_am_mode' => '[?7l', 'key_end' => 'OF', 'key_enter' => 'OM', 'key_sdc' => '[3;2~', 'key_send' => '', 'key_shome' => '', 'key_sic' => '[2;2~', 'key_sleft' => '', 'key_snext' => '[6;2~', 'key_sprevious' => '[5;2~', 'key_sright' => '', 'key_f11' => '[23~', 'key_f12' => '[24~', 'key_f13' => '', 'key_f14' => '', 'key_f15' => '', 'key_f16' => '', 'key_f17' => '[15;2~', 'key_f18' => '[17;2~', 'key_f19' => '[18;2~', 'key_f20' => '[19;2~', 'key_f21' => '[20;2~', 'key_f22' => '[21;2~', 'key_f23' => '[23;2~', 'key_f24' => '[24;2~', 'key_f25' => '', 'key_f26' => '', 'key_f27' => '', 'key_f28' => '', 'key_f29' => '[15;5~', 'key_f30' => '[17;5~', 'key_f31' => '[18;5~', 'key_f32' => '[19;5~', 'key_f33' => '[20;5~', 'key_f34' => '[21;5~', 'key_f35' => '[23;5~', 'key_f36' => '[24;5~', 'key_f37' => '', 'key_f38' => '', 'key_f39' => '', 'key_f40' => '', 'key_f41' => '[15;6~', 'key_f42' => '[17;6~', 'key_f43' => '[18;6~', 'key_f44' => '[19;6~', 'key_f45' => '[20;6~', 'key_f46' => '[21;6~', 'key_f47' => '[23;6~', 'key_f48' => '[24;6~', 'key_f49' => '', 'key_f50' => '', 'key_f51' => '', 'key_f52' => '', 'key_f53' => '[15;3~', 'key_f54' => '[17;3~', 'key_f55' => '[18;3~', 'key_f56' => '[19;3~', 'key_f57' => '[20;3~', 'key_f58' => '[21;3~', 'key_f59' => '[23;3~', 'key_f60' => '[24;3~', 'key_f61' => '', 'key_f62' => '', 'key_f63' => '', 'clr_bol' => '', 'user6' => '[%i%d;%dR', 'user7' => '', 'user8' => '[?1;2c', 'user9' => '', 'orig_pair' => '', 'set_foreground' => '[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m', 'set_background' => '[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m', 'key_mouse' => '', 'set_a_foreground' => '[3%p1%dm', 'set_a_background' => '[4%p1%dm', 'memory_lock' => 'l', 'memory_unlock' => 'm' ] ]); } public function case_has() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->has('auto_left_margin')) ->then ->boolean($result) ->isFalse() ->when($result = $tput->has('auto_right_margin')) ->then ->boolean($result) ->isTrue(); } public function case_has_unknown_boolean() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->has('💩')) ->then ->boolean($result) ->isFalse(); } public function case_count() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->count('columns')) ->then ->integer($result) ->isEqualTo(80); } public function case_count_unknown_integer() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->count('💩')) ->then ->integer($result) ->isEqualTo(0); } public function case_get() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->get('cursor_down')) ->then ->string($result) ->isEqualTo("\n"); } public function case_get_unknown_string() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->get('💩')) ->then ->variable($result) ->isNull(); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('Hoa\Stream\IStream\Out'); } public function case_write() { $this ->given($output = new SUT()) ->when($output->write('foobar', 3)) ->then ->output ->isIdenticalTo('foo'); } public function case_write_string() { $this ->given($output = new SUT()) ->when($output->writeString(123)) ->then ->output ->isIdenticalTo('123'); } public function case_write_character() { $this ->given($output = new SUT()) ->when($output->writeCharacter('foo')) ->then ->output ->isIdenticalTo('f'); } public function case_write_boolean_true() { $this ->given($output = new SUT()) ->when($output->writeBoolean(true)) ->then ->output ->isIdenticalTo('1'); } public function case_write_boolean_false() { $this ->given($output = new SUT()) ->when($output->writeBoolean(false)) ->then ->output ->isIdenticalTo('0'); } public function case_write_integer() { $this ->given($output = new SUT()) ->when($output->writeInteger(-42)) ->then ->output ->isIdenticalTo('-42'); } public function case_write_float() { $this ->given($output = new SUT()) ->when($output->writeFloat(-4.2)) ->then ->output ->isIdenticalTo('-4.2'); } public function case_write_array() { $this ->given($output = new SUT()) ->when($output->writeArray(['foo' => 'bar'])) ->then ->output ->isIdenticalTo( 'array (' . "\n" . ' \'foo\' => \'bar\',' . "\n" . ')' ); } public function case_write_line_no_newline() { $this ->given($output = new SUT()) ->when($output->writeLine('foo')) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_line_with_newline() { $this ->given($output = new SUT()) ->when($output->writeLine('foo' . "\n")) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_line_with_newlines() { $this ->given($output = new SUT()) ->when($output->writeLine('foo' . "\n" . 'bar' . "\n")) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_all() { $this ->given($output = new SUT()) ->when($output->writeAll('foobar')) ->then ->output ->isIdenticalTo('foobar'); } public function case_truncate() { $this ->given($output = new SUT()) ->when($result = $output->truncate(42)) ->then ->boolean($result) ->isFalse(); } public function case_default_multiplexer_consideration() { $this ->given($output = new SUT()) ->when($result = $output->isMultiplexerConsidered()) ->then ->boolean($result) ->isFalse(); } public function case_consider_multiplexer() { $this ->given($output = new SUT()) ->when( $output->considerMultiplexer(true), $result = $output->isMultiplexerConsidered() ) ->then ->boolean($result) ->isTrue(); } } when($result = SUT::getInstance()) ->then ->object($result) ->isIdenticalTo(SUT::getInstance()); } public function case_set_size() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setSize(7, 42)) ->then ->output ->isEqualTo("\033[8;42;7t"); } public function case_set_size_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setSize(7, 42)) ->then ->output ->isEmpty(); } public function case_move_to() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::moveTo(7, 42)) ->then ->output ->isEqualTo("\033[3;7;42t"); } public function case_move_to_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::moveTo(7, 42)) ->then ->output ->isEmpty(); } public function case_get_position() { $this ->given( $this->constant->OS_WIN = false, $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[3;7;42t"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when($result = SUT::getPosition()) ->then ->output ->isEqualTo("\033[13t") ->array($result) ->isEqualTo([ 'x' => 7, 'y' => 42 ]); } public function case_get_position_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getPosition()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_scroll_u() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('u')) ->then ->output ->isEqualTo("\033[1S"); } public function case_scroll_up() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('up')) ->then ->output ->isEqualTo("\033[1S"); } public function case_scroll_d() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('d')) ->then ->output ->isEqualTo("\033[1T"); } public function case_scroll_down() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('d')) ->then ->output ->isEqualTo("\033[1T"); } public function case_scroll_u_d_up_down() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('u d up down')) ->then ->output ->isEqualTo("\033[2S\033[2T"); } public function case_scroll_up_repeated() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('up', 3)) ->then ->output ->isEqualTo("\033[3S"); } public function case_scroll_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::scroll('u')) ->then ->output ->isEmpty(); } public function case_minimize() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::minimize()) ->then ->output ->isEqualTo("\033[2t"); } public function case_minimize_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::minimize()) ->then ->output ->isEmpty(); } public function case_restore() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::restore()) ->then ->output ->isEqualTo("\033[1t"); } public function case_restore_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::restore()) ->then ->output ->isEmpty(); } public function case_raise() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::raise()) ->then ->output ->isEqualTo("\033[5t"); } public function case_raise_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::raise()) ->then ->output ->isEmpty(); } public function case_lower() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::lower()) ->then ->output ->isEqualTo("\033[6t"); } public function case_lower_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::lower()) ->then ->output ->isEmpty(); } public function case_set_title() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setTitle('foobar 😄')) ->then ->output ->isEqualTo("\033]0;foobar 😄\033\\"); } public function case_set_title_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setTitle('foobar 😄')) ->then ->output ->isEmpty(); } public function case_get_title() { $this ->given( $this->constant->OS_WIN = false, $title = 'hello 🌍', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033]l" . $title . "\033\\"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { return 1; } ) ->when($result = SUT::getTitle()) ->then ->output ->isEqualTo("\033[21t") ->string($result) ->isEqualTo($title); } public function case_get_title_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getTitle()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_get_title_timed_out() { $this ->given( $this->function->stream_select = function () { return 0; } ) ->when($result = SUT::getTitle()) ->then ->output ->isEqualTo("\033[21t") ->variable($result) ->isNull(); } public function case_get_label() { $this ->given( $this->constant->OS_WIN = false, $label = 'hello 🌍', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033]L" . $label . "\033\\"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { return 1; } ) ->when($result = SUT::getLabel()) ->then ->output ->isEqualTo("\033[20t") ->string($result) ->isEqualTo($label); } public function case_get_label_timed_out() { $this ->given( $this->function->stream_select = function () { return 0; } ) ->when($result = SUT::getLabel()) ->then ->output ->isEqualTo("\033[20t") ->variable($result) ->isNull(); } public function case_get_label_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getLabel()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_refresh() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::refresh()) ->then ->output ->isEqualTo("\033[7t"); } public function case_refresh_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::refresh()) ->then ->output ->isEmpty(); } public function case_copy() { unset($_SERVER['TMUX']); $this ->given($this->constant->OS_WIN = false) ->when(SUT::copy('bla')) ->then ->output ->isEqualTo("\033]52;;" . base64_encode('bla') . "\033\\"); } public function case_copy_on_tmux() { $this ->given( $_SERVER['TMUX'] = 'foo', $this->constant->OS_WIN = false ) ->when(SUT::copy('bla')) ->then ->output ->isEqualTo( "\033Ptmux;" . "\033\033]52;;" . base64_encode('bla') . "\033\033\\" . "\033\\" ); } public function case_copy_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::copy('bla')) ->then ->output ->isEmpty(); } } given( $parser = new LUT\Parser(), $parser->parse(''), $options = new SUT([], $parser) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isTrue() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_one_entry() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--foo'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('f') ->boolean($value) ->isTrue() ->when($result = $options->getOption($value)) ->boolean($options->isPipetteEmpty()) ->isFalse() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_more_entries() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--foo --bar baz'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'], ['bar', SUT::REQUIRED_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('f') ->boolean($value) ->isTrue() ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('b') ->string($value) ->isEqualTo('baz') ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_ambiguous() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--baz'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'], ['bar', SUT::REQUIRED_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('__ambiguous') ->array($value) ->isEqualTo([ 'solutions' => ['bar'], 'value' => true, 'option' => 'baz' ]); } public function case_resolve_option_ambiguity_no_solution() { $this ->given( $parser = new LUT\Parser(), $parser->parse(''), $options = new SUT([], $parser), $solutions = [ 'solutions' => [], 'value' => true, 'option' => 'baz' ] ) ->exception(function () use ($options, $solutions) { $options->resolveOptionAmbiguity($solutions); }) ->isInstanceOf('Hoa\Console\Exception'); } public function case_resolve_option_ambiguity() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--baz'), $options = new SUT( [ ['bar', SUT::NO_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->string($result) ->isEqualTo('__ambiguous') ->array($value) ->isEqualTo([ 'solutions' => ['bar'], 'value' => true, 'option' => 'baz' ]) ->when($result = $options->resolveOptionAmbiguity($value)) ->then ->variable($result) ->isNull() ->when($result = $options->getOption($value)) ->then ->string($result) ->isEqualTo('b') ->boolean($value) ->isEqualTo(true); } } _case( '-a -b -c', ['a' => true, 'b' => true, 'c' => true] ); } public function case_single_dashed_short_options() { return $this->_case( '-abc', ['a' => true, 'b' => true, 'c' => true] ); } public function case_long_options() { return $this->_case( '--foo --bar --b-a-z', ['foo' => true, 'bar' => true, 'b-a-z' => true] ); } public function case_boolean_switches() { return $this->_case( '-a -a --foo --foo --bar --bar --bar', ['a' => false, 'foo' => false, 'bar' => true] ); } public function case_valued_switches_equal_simple() { return $this->_case( '-a=foo --long=bar', ['a' => 'foo', 'long' => 'bar'] ); } public function case_valued_switches_equal_with_escaped_space() { return $this->_case( '-a=fo\ o --long=b\ a\ r', ['a' => 'fo o', 'long' => 'b a r'] ); } public function case_valued_switches_equal_double_quoted() { return $this->_case( '-a="fo\"o" --long="b\"a\'r"', ['a' => 'fo"o', 'long' => 'b"a\'r'] ); } public function case_valued_switches_equal_single_quoted() { return $this->_case( '-a=\'fo\\\'"o\' --long=\'b\\\'a"r\'', ['a' => 'fo\'"o', 'long' => 'b\'a"r'] ); } public function case_valued_switches_space_simple() { return $this->_case( '-a foo --long bar', ['a' => 'foo', 'long' => 'bar'] ); } public function case_valued_switches_space_with_escaped_space() { return $this->_case( '-a fo\ o --long b\ a\ r', ['a' => 'fo o', 'long' => 'b a r'] ); } public function case_valued_switches_space_double_quoted() { return $this->_case( '-a "fo\"o" --long "b\"a\'r"', ['a' => 'fo"o', 'long' => 'b"a\'r'] ); } public function case_valued_switches_space_single_quoted() { return $this->_case( '-a \'fo\\\'"o\' --long \'b\\\'a"r\'', ['a' => 'fo\'"o', 'long' => 'b\'a"r'] ); } public function case_valued_switch_equal_negative_value() { return $this->_case( '-a=-foo --long=-bar', ['a' => '-foo', 'long' => '-bar'] ); } public function case_special_valued_switch() { return $this->_case( '-a f,o,o --long b,a,r', ['a' => 'f,o,o', 'long' => 'b,a,r'] ); } public function case_input_associated_to_a_short_option() { return $this->_case( '-a input', ['a' => 'input'] ); } public function case_double_dashes_input() { return $this->_case( '-a --long bar -- inputA inputB', ['a' => true, 'long' => 'bar'], ['inputA', 'inputB'] ); } public function case_simple_input() { return $this->_case( 'inputA inputB', [], ['inputA', 'inputB'] ); } public function case_valued_switch_followed_by_an_input() { return $this->_case( '-a foo --long bar inputA inputB', ['a' => 'foo', 'long' => 'bar'], ['inputA', 'inputB'] ); } public function case_unordered() { return $this->_case( 'inputA -a foo inputB --long bar inputC', ['a' => 'foo', 'long' => 'bar'], ['inputA', 'inputB', 'inputC'] ); } protected function _case($command, array $switches, array $inputs = []) { $this ->given($parser = new SUT()) ->when($result = $parser->parse($command)) ->then ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo($switches) ->array($parser->getInputs()) ->isIdenticalTo($inputs); } public function case_state_is_reset() { $this ->given($parser = new SUT()) ->when($result = $parser->parse('--foo=bar baz')) ->then ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo(['foo' => 'bar']) ->array($parser->getInputs()) ->isIdenticalTo(['baz']) ->when($result = $parser->parse('--bar=baz qux')) ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo(['bar' => 'baz']) ->array($parser->getInputs()) ->isIdenticalTo(['qux']); } public function case_parse_special_value_list() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,baz')) ->then ->array($result) ->isIdenticalTo([ 'foo', 'bar', 'baz' ]); } public function case_parse_special_value_list_with_keywords() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,QUX', ['QUX' => 'baz'])) ->then ->array($result) ->isIdenticalTo([ 'foo', 'bar', 'baz' ]); } public function case_parse_special_value_list_with_range() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,1:3')) ->then ->array($result) ->isIdenticalTo([ 1, 2, 3, 'foo', 'bar' ]); } public function case_set_long_only() { $this ->given($parser = new SUT()) ->when( $parser->setLongOnly(true), $result = $parser->parse('-abc') ) ->then ->boolean($parser->getLongOnly()) ->isTrue() ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo([ 'abc' => true, ]) ->array($parser->getInputs()) ->isEmpty(); } } when($result = SUT::STATE_CONTINUE) ->then ->integer($result) ->isEqualTo( LUT\Readline::STATE_CONTINUE | LUT\Readline::STATE_NO_ECHO ); } } given($autocompleter = new SUT([])) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('.*'); } public function case_constructor() { $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter() ) ->when($result = new SUT([$autocompleterA, $autocompleterB])) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->let($autocompleters = $result->getAutocompleters()) ->object($autocompleters) ->isInstanceOf('ArrayObject') ->integer(count($autocompleters)) ->isEqualTo(2) ->object($autocompleters[0]) ->isIdenticalTo($autocompleterA) ->object($autocompleters[1]) ->isIdenticalTo($autocompleterB); } public function case_complete_no_solution() { $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterA->getWordDefinition = function () { return 'aaa'; }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterB->getWordDefinition = function () { return 'bbb'; }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'ccc' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('ccc'); } public function case_complete_one_solution_first_autocompleter() { $self = $this; $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterA)->getWordDefinition = function () { return 'aaa'; }, $this->calling($autocompleterA)->complete = function ($prefix) use ($self) { $self ->string($prefix) ->isEqualTo('aaa'); return 'AAA'; }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterB)->getWordDefinition = function () { return 'bbb'; }, $this->calling($autocompleterB)->complete = function ($prefix) use ($self) { $self->fail('Bad autocompleter called.'); }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'aaa' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('AAA') ->string($prefix) ->isEqualTo('aaa'); } public function case_complete_one_solution_second_autocompleter() { $self = $this; $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterA)->getWordDefinition = function () { return 'aaa'; }, $this->calling($autocompleterA)->complete = function ($prefix) use ($self) { $self->fail('Bad autocompleter called.'); }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterB)->getWordDefinition = function () { return 'bbb'; }, $this->calling($autocompleterB)->complete = function ($prefix) use ($self) { $self ->string($prefix) ->isEqualTo('bbb'); return 'BBB'; }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'bbb' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('BBB') ->string($prefix) ->isEqualTo('bbb'); } } given($words = ['foo', 'bar', 'baz', 'qux']) ->when($result = new SUT($words)) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->array($result->getWords()) ->isEqualTo($words); } public function case_complete_no_solution() { $this ->given( $autocompleter = new SUT(['foo', 'bar']), $prefix = 'q' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('q'); } public function case_complete_one_solution() { $this ->given( $autocompleter = new SUT(['foo', 'bar']), $prefix = 'f' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('foo') ->string($prefix) ->isEqualTo('f'); } public function case_complete_with_smallest_prefix() { $this ->given( $autocompleter = new SUT(['foo', 'bar', 'baz', 'qux']), $prefix = 'b' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['bar', 'baz']) ->string($prefix) ->isEqualTo('b'); } public function case_complete_with_longer_prefix() { $this ->given( $autocompleter = new SUT(['bara', 'barb', 'baza']), $prefix = 'bar' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['bara', 'barb']) ->string($prefix) ->isEqualTo('bar'); } public function case_get_word_definition() { $this ->given($autocompleter = new SUT([])) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('\b\w+'); } public function case_set_words() { $this ->given( $words = ['foo', 'bar', 'baz', 'qux'], $autocompleter = new SUT([]) ) ->when($result = $autocompleter->setWords($words)) ->then ->array($result) ->isEmpty(); } public function case_get_words() { $this ->given( $words = ['foo', 'bar', 'baz', 'qux'], $autocompleter = new SUT($words) ) ->when($result = $autocompleter->getWords()) ->then ->array($result) ->isEqualTo($words); } } given($autocompleter = new SUT()) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*'); } public function case_constructor() { $this ->given( $root = 'foo', $iteratorFactory = function () { return 42; } ) ->when($result = new SUT($root, $iteratorFactory)) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->string($result->getRoot()) ->isEqualTo($root) ->object($result->getIteratorFactory()) ->isIdenticalTo($iteratorFactory); } public function case_complete_no_solution() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'Q' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('Q'); } public function case_complete_one_solution() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'F' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('Foo') ->string($prefix) ->isEqualTo('F'); } public function case_complete_with_smallest_prefix() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), resolve('hoa://Test/Vfs/Root/Baz?type=file'), resolve('hoa://Test/Vfs/Root/Qux?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'B' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['Bar', 'Baz']) ->string($prefix) ->isEqualTo('B'); } public function case_complete_with_longer_prefix() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Bara?type=file'), resolve('hoa://Test/Vfs/Root/Barb?type=file'), resolve('hoa://Test/Vfs/Root/Baza?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'Bar' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['Bara', 'Barb']) ->string($prefix) ->isEqualTo('Bar'); } public function case_set_root() { $this ->given($autocompleter = new SUT()) ->when($result = $autocompleter->setRoot('foo')) ->then ->variable($result) ->isNull() ->when($result = $autocompleter->setRoot('bar')) ->then ->string($result) ->isEqualTo('foo'); } public function case_get_root() { $this ->given( $autocompleter = new SUT(), $autocompleter->setRoot('foo') ) ->when($result = $autocompleter->getRoot()) ->then ->string($result) ->isEqualTo('foo'); } public function case_set_iterator_factory() { $this ->given($autocompleter = new SUT()) ->when( $result = $autocompleter->setIteratorFactory(function () { return 42; }) ) ->then ->variable($result) ->isNull() ->when( $result = $autocompleter->setIteratorFactory(function () { return 43; }) ) ->then ->integer($result()) ->isEqualTo(42); } public function case_get_iterator_factory() { $this ->given( $autocompleter = new SUT(), $autocompleter->setIteratorFactory(function () { return 42; }) ) ->when(function () use (&$result, $autocompleter) { $result = $autocompleter->getIteratorFactory(); }) ->then ->integer($result()) ->isEqualTo(42); } public function case_get_default_iterator_factory() { $this ->when(function () use (&$result) { $result = SUT::getDefaultIteratorFactory(); }) ->then ->object($result) ->isInstanceOf('Closure') ->object($result(__DIR__)) ->isInstanceOf('DirectoryIterator'); } } when(SUT::move('u')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_up() { $this ->when(SUT::move('up')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_↑() { $this ->when(SUT::move('↑')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_↑_repeated() { $this ->when(SUT::move('↑', 42)) ->then ->output ->isEqualTo("\033[42A"); } public function case_move_r() { $this ->when(SUT::move('r')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_right() { $this ->when(SUT::move('right')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_→() { $this ->when(SUT::move('→')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_→_repeated() { $this ->when(SUT::move('→', 42)) ->then ->output ->isEqualTo("\033[42C"); } public function case_move_d() { $this ->when(SUT::move('d')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_down() { $this ->when(SUT::move('down')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_↓() { $this ->when(SUT::move('↓')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_↓_repeated() { $this ->when(SUT::move('↓', 42)) ->then ->output ->isEqualTo("\033[42B"); } public function case_move_l() { $this ->when(SUT::move('l')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_left() { $this ->when(SUT::move('left')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_←() { $this ->when(SUT::move('←')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_←_repeated() { $this ->when(SUT::move('←', 42)) ->then ->output ->isEqualTo("\033[42D"); } public function case_move_sequence() { $this ->when(SUT::move('↑ → ↓ ←')) ->then ->output ->isEqualTo("\033[1A\033[1C\033[1B\033[1D"); } public function case_move_to_x_y() { $this ->when(SUT::moveTo(7, 42)) ->then ->output ->isEqualTo("\033[42;7H"); } public function case_move_to_x() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when(SUT::moveTo(153)) ->then ->output ->isEqualTo("\033[6n\033[42;153H"); } public function case_move_to_y() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when(SUT::moveTo(null, 153)) ->then ->output ->isEqualTo("\033[6n\033[153;7H"); } public function case_get_position() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when($result = SUT::getPosition()) ->then ->output ->isEqualTo("\033[6n") ->array($result) ->isEqualTo([ 'x' => 7, 'y' => 42 ]); } public function case_save() { $this ->when(SUT::save()) ->then ->output ->isEqualTo("\0337"); } public function case_restore() { $this ->when(SUT::restore()) ->then ->output ->isEqualTo("\0338"); } public function case_clear_a() { $this ->when(SUT::clear('a')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_all() { $this ->when(SUT::clear('all')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_↕() { $this ->when(SUT::clear('↕')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_u() { $this ->when(SUT::clear('u')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_up() { $this ->when(SUT::clear('up')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_↑() { $this ->when(SUT::clear('↑')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_r() { $this ->when(SUT::clear('r')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_right() { $this ->when(SUT::clear('right')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_→() { $this ->when(SUT::clear('→')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_d() { $this ->when(SUT::clear('d')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_down() { $this ->when(SUT::clear('down')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_↓() { $this ->when(SUT::clear('↓')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_l() { $this ->when(SUT::clear('l')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_left() { $this ->when(SUT::clear('left')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_←() { $this ->when(SUT::clear('←')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_line() { $this ->when(SUT::clear('line')) ->then ->output ->isEqualTo("\r\033[K"); } public function case_clear_↔() { $this ->when(SUT::clear('↔')) ->then ->output ->isEqualTo("\r\033[K"); } public function case_hide() { $this ->when(SUT::hide()) ->then ->output ->isEqualTo("\033[?25l"); } public function case_show() { $this ->when(SUT::show()) ->then ->output ->isEqualTo("\033[?12;25h"); } public function case_colorize_n() { $this ->when(SUT::colorize('n')) ->then ->output ->isEqualTo("\033[0m"); } public function case_colorize_normal() { $this ->when(SUT::colorize('normal')) ->then ->output ->isEqualTo("\033[0m"); } public function case_colorize_normal_repeated() { $this ->when(SUT::colorize('n normal')) ->then ->output ->isEqualTo("\033[0;0m"); } public function case_colorize_b() { $this ->when(SUT::colorize('b')) ->then ->output ->isEqualTo("\033[1m"); } public function case_colorize_bold() { $this ->when(SUT::colorize('bold')) ->then ->output ->isEqualTo("\033[1m"); } public function case_colorize_u() { $this ->when(SUT::colorize('u')) ->then ->output ->isEqualTo("\033[4m"); } public function case_colorize_underlined() { $this ->when(SUT::colorize('underlined')) ->then ->output ->isEqualTo("\033[4m"); } public function case_colorize_bl() { $this ->when(SUT::colorize('bl')) ->then ->output ->isEqualTo("\033[5m"); } public function case_colorize_blink() { $this ->when(SUT::colorize('blink')) ->then ->output ->isEqualTo("\033[5m"); } public function case_colorize_i() { $this ->when(SUT::colorize('i')) ->then ->output ->isEqualTo("\033[7m"); } public function case_colorize_inverse() { $this ->when(SUT::colorize('inverse')) ->then ->output ->isEqualTo("\033[7m"); } public function case_colorize_not_b() { $this ->when(SUT::colorize('!b')) ->then ->output ->isEqualTo("\033[22m"); } public function case_colorize_not_bold() { $this ->when(SUT::colorize('!bold')) ->then ->output ->isEqualTo("\033[22m"); } public function case_colorize_not_u() { $this ->when(SUT::colorize('!u')) ->then ->output ->isEqualTo("\033[24m"); } public function case_colorize_not_underlined() { $this ->when(SUT::colorize('!underlined')) ->then ->output ->isEqualTo("\033[24m"); } public function case_colorize_not_bl() { $this ->when(SUT::colorize('!bl')) ->then ->output ->isEqualTo("\033[25m"); } public function case_colorize_not_blink() { $this ->when(SUT::colorize('!blink')) ->then ->output ->isEqualTo("\033[25m"); } public function case_colorize_not_i() { $this ->when(SUT::colorize('!i')) ->then ->output ->isEqualTo("\033[27m"); } public function case_colorize_not_inverse() { $this ->when(SUT::colorize('!inverse')) ->then ->output ->isEqualTo("\033[27m"); } public function case_colorize_fg_black() { $this ->when(SUT::colorize('fg(black)')) ->then ->output ->isEqualTo("\033[30m"); } public function case_colorize_foreground_black() { $this ->when(SUT::colorize('foreground(black)')) ->then ->output ->isEqualTo("\033[30m"); } public function case_colorize_fg_red() { $this ->when(SUT::colorize('fg(red)')) ->then ->output ->isEqualTo("\033[31m"); } public function case_colorize_fg_green() { $this ->when(SUT::colorize('fg(green)')) ->then ->output ->isEqualTo("\033[32m"); } public function case_colorize_fg_yellow() { $this ->when(SUT::colorize('fg(yellow)')) ->then ->output ->isEqualTo("\033[33m"); } public function case_colorize_fg_blue() { $this ->when(SUT::colorize('fg(blue)')) ->then ->output ->isEqualTo("\033[34m"); } public function case_colorize_fg_magenta() { $this ->when(SUT::colorize('fg(magenta)')) ->then ->output ->isEqualTo("\033[35m"); } public function case_colorize_fg_cyan() { $this ->when(SUT::colorize('fg(cyan)')) ->then ->output ->isEqualTo("\033[36m"); } public function case_colorize_fg_white() { $this ->when(SUT::colorize('fg(white)')) ->then ->output ->isEqualTo("\033[37m"); } public function case_colorize_fg_default() { $this ->when(SUT::colorize('fg(default)')) ->then ->output ->isEqualTo("\033[39m"); } public function case_colorize_bg_black() { $this ->when(SUT::colorize('bg(black)')) ->then ->output ->isEqualTo("\033[40m"); } public function case_colorize_background_black() { $this ->when(SUT::colorize('background(black)')) ->then ->output ->isEqualTo("\033[40m"); } public function case_colorize_bg_red() { $this ->when(SUT::colorize('bg(red)')) ->then ->output ->isEqualTo("\033[41m"); } public function case_colorize_bg_green() { $this ->when(SUT::colorize('bg(green)')) ->then ->output ->isEqualTo("\033[42m"); } public function case_colorize_bg_yellow() { $this ->when(SUT::colorize('bg(yellow)')) ->then ->output ->isEqualTo("\033[43m"); } public function case_colorize_bg_blue() { $this ->when(SUT::colorize('bg(blue)')) ->then ->output ->isEqualTo("\033[44m"); } public function case_colorize_bg_magenta() { $this ->when(SUT::colorize('bg(magenta)')) ->then ->output ->isEqualTo("\033[45m"); } public function case_colorize_bg_cyan() { $this ->when(SUT::colorize('bg(cyan)')) ->then ->output ->isEqualTo("\033[46m"); } public function case_colorize_bg_white() { $this ->when(SUT::colorize('bg(white)')) ->then ->output ->isEqualTo("\033[47m"); } public function case_colorize_bg_default() { $this ->when(SUT::colorize('bg(default)')) ->then ->output ->isEqualTo("\033[49m"); } public function case_colorize_foreground_ff0066() { $this ->when(SUT::colorize('foreground(#ff0066)')) ->then ->output ->isEqualTo("\033[38;5;197m"); } public function case_colorize_background_ff0066() { $this ->when(SUT::colorize('background(#ff0066)')) ->then ->output ->isEqualTo("\033[48;5;197m"); } public function case_colorize_foreground_color_index() { $this ->when(SUT::colorize('foreground(42)')) ->then ->output ->isEqualTo("\033[38;5;42m"); } public function case_change_color() { $this ->when(SUT::changeColor(35, 0xff0066)) ->then ->output ->isEqualTo("\033]4;35;ff0066\033\\"); } public function case_set_style_b() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('b')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_block() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('block')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_▋() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('▋')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_block_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('block', false)) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_u() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('u')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_underline() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('underline')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style__() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('_')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_underline_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('underline', false)) ->then ->output ->isEqualTo("\033[3 q"); } public function case_set_style_v() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('v')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_vertical() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('vertical')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_pipe() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('|')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_vertical_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('vertical', false)) ->then ->output ->isEqualTo("\033[6 q"); } public function case_set_style_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setStyle('b')) ->then ->output ->isEmpty(); } public function case_bip() { $this ->when(SUT::bip()) ->then ->output ->isEqualTo("\007"); } } setListener( new Event\Listener( $this, [ 'mouseup', 'mousedown', 'wheelup', 'wheeldown', ] ) ); return; } public static function getInstance() { if (null === static::$_instance) { static::$_instance = new static(); } return static::$_instance; } public static function track() { if (true === static::$_enabled) { return; } static::$_enabled = true; Console::getOutput()->writeAll( "\033[1;2'z" . "\033[?1000h" . "\033[?1003h" ); $instance = static::getInstance(); $bucket = [ 'x' => 0, 'y' => 0, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ]; $input = Console::getInput(); $read = [$input->getStream()->getStream()]; while (true) { if (false === @stream_select($read, $write, $except, 30)) { static::untrack(); break; } $string = $input->readCharacter(); if ("\033" !== $string) { continue; } $char = $input->readCharacter(); if ('[' !== $char) { continue; } $char = $input->readCharacter(); if ('M' !== $char) { continue; } $data = $input->read(3); $cb = ord($data[0]); $cx = ord($data[1]) - 32; $cy = ord($data[2]) - 32; $bucket['x'] = $cx; $bucket['y'] = $cy; $bucket['shift'] = 0 !== ($cb & 4); $bucket['meta'] = 0 !== ($cb & 8); $bucket['ctrl'] = 0 !== ($cb & 16); $cb = ($cb | 28) ^ 28; $cb -= 32; switch ($cb) { case static::WHEEL_UP: $instance->getListener()->fire( 'wheelup', new Event\Bucket($bucket) ); break; case static::WHEEL_DOWN: $instance->getListener()->fire( 'wheeldown', new Event\Bucket($bucket) ); break; case static::BUTTON_RELEASE: $instance->getListener()->fire( 'mouseup', new Event\Bucket($bucket) ); $bucket['button'] = null; break; default: if (static::BUTTON_LEFT === $cb) { $bucket['button'] = 'left'; } elseif (static::BUTTON_MIDDLE === $cb) { $bucket['button'] = 'middle'; } elseif (static::BUTTON_RIGHT === $cb) { $bucket['button'] = 'right'; } else { continue 2; } $instance->getListener()->fire( 'mousedown', new Event\Bucket($bucket) ); } } return; } public static function untrack() { if (false === static::$_enabled) { return; } Console::getOutput()->writeAll( "\033[?1003l" . "\033[?1000l" ); static::$_enabled = false; return; } } Console::advancedInteraction(); Consistency::registerShutdownFunction(xcallable('Hoa\Console\Mouse::untrack')); getOption($v)) { switch ($c) { case 't': echo $tput->getTerm(); return; case 'f': echo $tput->getTerminfo(); return; case 'H': echo $tput->has($v) ? 1 : 0; return; case 'c': echo $tput->count($v); return; case 'g': echo $tput->get($v); return; case 'b': $informations = $tput->getInformations(); static::format($informations['booleans']); return; case 'n': $informations = $tput->getInformations(); static::format($informations['numbers']); return; case 's': $informations = $tput->getInformations(); static::format($informations['strings']); return; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } return $this->usage(); } public function usage() { echo 'Usage : console:termcap', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 't' => 'Get terminal name.', 'f' => 'Get path to the terminfo file.', 'H' => 'Get value of a boolean capability.', 'c' => 'Get value of a number capability.', 'g' => 'Get value of a string capability.', 'b' => 'Get all boolean capabilites.', 'n' => 'Get all number capabilites.', 's' => 'Get all string capabilites.', 'help' => 'This help.' ]), "\n", 'Examples:', "\n", ' $ hoa console:termcap --count max_colors', "\n", ' $ TERM=vt200 hoa console:termcap --has back_color_erase', "\n"; return; } public static function format(array $data) { $max = 0; foreach ($data as $key => $_) { if ($max < ($handle = strlen($key))) { $max = $handle; } } $format = '%-' . ($max + 1) . 's: %s' . "\n"; foreach ($data as $key => $value) { printf( $format, $key, is_bool($value) ? ($value ? 'true' : 'false') : (is_string($value) ? str_replace( [ "\033", "\n", ord(0xa), "\r", "\b", "\f", "\0" ], [ '\e', '\n', '\l', '\r', '\b', '\f', '\0' ], $value ) : $value) ); } return; } } __halt_compiler(); Terminal capabilities. _input = $input; return; } public function getStream() { return $this->_input; } public function eof() { return $this->_input->eof(); } public function read($length) { return $this->_input->read($length); } public function readString($length) { return $this->_input->readString($length); } public function readCharacter() { return $this->_input->readCharacter(); } public function readBoolean() { return $this->_input->readBoolean(); } public function readInteger($length = 1) { return $this->_input->readInteger($length); } public function readFloat($length = 1) { return $this->_input->readFloat($length); } public function readArray($argument = null) { return $this->_input->readArray($argument); } public function readLine() { return $this->_input->readLine(); } public function readAll($offset = 0) { return $this->_input->readAll($offset); } public function scanf($format) { return $this->_input->scanf($format); } } parse($terminfo); return; } protected function parse($terminfo) { if (!file_exists($terminfo)) { throw new Exception( 'Terminfo file %s does not exist.', 0, $terminfo ); } $data = file_get_contents($terminfo); $length = strlen($data); $out = ['file' => $terminfo]; $headers = [ 'data_size' => $length, 'header_size' => 12, 'magic_number' => (ord($data[ 1]) << 8) | ord($data[ 0]), 'names_size' => (ord($data[ 3]) << 8) | ord($data[ 2]), 'bool_count' => (ord($data[ 5]) << 8) | ord($data[ 4]), 'number_count' => (ord($data[ 7]) << 8) | ord($data[ 6]), 'string_count' => (ord($data[ 9]) << 8) | ord($data[ 8]), 'string_table_size' => (ord($data[11]) << 8) | ord($data[10]), ]; $out['headers'] = $headers; $i = $headers['header_size']; $nameAndDescription = explode('|', substr($data, $i, $headers['names_size'] - 1)); $out['name'] = $nameAndDescription[0]; $out['description'] = $nameAndDescription[1]; $i += $headers['names_size']; $booleans = []; $booleanNames = &static::$_booleans; for ( $e = 0, $max = $i + $headers['bool_count']; $i < $max; ++$e, ++$i ) { $booleans[$booleanNames[$e]] = 1 === ord($data[$i]); } $out['booleans'] = $booleans; if (1 === ($i % 2)) { ++$i; } $numbers = []; $numberNames = &static::$_numbers; for ( $e = 0, $max = $i + $headers['number_count'] * 2; $i < $max; ++$e, $i += 2 ) { $name = $numberNames[$e]; $data_i0 = ord($data[$i ]); $data_i1 = ord($data[$i + 1]); if ($data_i1 === 255 && $data_i0 === 255) { $numbers[$name] = -1; } else { $numbers[$name] = ($data_i1 << 8) | $data_i0; } } $out['numbers'] = $numbers; $strings = []; $stringNames = &static::$_strings; $ii = $i + $headers['string_count'] * 2; for ( $e = 0, $max = $ii; $i < $max; ++$e, $i += 2 ) { $name = $stringNames[$e]; $data_i0 = ord($data[$i ]); $data_i1 = ord($data[$i + 1]); if ($data_i1 === 255 && $data_i0 === 255) { continue; } $a = ($data_i1 << 8) | $data_i0; $strings[$name] = $a; if (65534 === $a) { continue; } $b = $ii + $a; $c = $b; while ($c < $length && ord($data[$c])) { $c++; } $value = substr($data, $b, $c - $b); $strings[$name] = false !== $value ? $value : null; } $out['strings'] = $strings; return $this->_informations = $out; } public function getInformations() { return $this->_informations; } public function has($boolean) { if (!isset($this->_informations['booleans'][$boolean])) { return false; } return $this->_informations['booleans'][$boolean]; } public function count($number) { if (!isset($this->_informations['numbers'][$number])) { return 0; } return $this->_informations['numbers'][$number]; } public function get($string) { if (!isset($this->_informations['strings'][$string])) { return null; } return $this->_informations['strings'][$string]; } public static function getTerm() { return isset($_SERVER['TERM']) && !empty($_SERVER['TERM']) ? $_SERVER['TERM'] : (OS_WIN ? 'windows-ansi' : 'xterm'); } public static function getTerminfo($term = null) { $paths = []; if (isset($_SERVER['TERMINFO'])) { $paths[] = $_SERVER['TERMINFO']; } if (isset($_SERVER['HOME'])) { $paths[] = $_SERVER['HOME'] . DS . '.terminfo'; } if (isset($_SERVER['TERMINFO_DIRS'])) { foreach (explode(':', $_SERVER['TERMINFO_DIRS']) as $path) { $paths[] = $path; } } $paths[] = '/usr/share/terminfo'; $paths[] = '/usr/share/lib/terminfo'; $paths[] = '/lib/terminfo'; $paths[] = '/usr/lib/terminfo'; $paths[] = '/usr/local/share/terminfo'; $paths[] = '/usr/local/share/lib/terminfo'; $paths[] = '/usr/local/lib/terminfo'; $paths[] = '/usr/local/ncurses/lib/terminfo'; $paths[] = 'hoa://Library/Console/Terminfo'; $term = $term ?: static::getTerm(); $fileHexa = dechex(ord($term[0])) . DS . $term; $fileAlpha = $term[0] . DS . $term; $pathname = null; foreach ($paths as $path) { if (file_exists($_ = $path . DS . $fileHexa) || file_exists($_ = $path . DS . $fileAlpha)) { $pathname = $_; break; } } if (null === $pathname && 'xterm' !== $term) { return static::getTerminfo('xterm'); } return $pathname; } } `tty` < `tty`', false ); } } writeAll($text); Console\Cursor::colorize($attributesAfter); $out = ob_get_contents(); ob_end_clean(); return $out; } public static function columnize( array $line, $alignement = self::ALIGN_LEFT, $horizontalPadding = 2, $verticalPadding = 0, $separator = null ) { if (empty($line)) { return ''; } $separator = explode('|', $separator); $nbColumn = 0; $nbLine = count($line); $xtraWidth = 2 * ($verticalPadding + 2); foreach ($line as $key => &$column) { if (!is_array($column)) { $column = [$column]; } $handle = count($column); $handle > $nbColumn and $nbColumn = $handle; } $xtraWidth += $horizontalPadding * $nbColumn; $columnWidth = array_fill(0, $nbColumn, 0); for ($e = 0; $e < $nbColumn; $e++) { for ($i = 0; $i < $nbLine; $i++) { if (!isset($line[$i][$e])) { continue; } $handle = self::getMaxLineWidth($line[$i][$e]); $handle > $columnWidth[$e] and $columnWidth[$e] = $handle; } } $window = Console\Window::getSize(); $envWindow = $window['x']; while ($envWindow <= ($cWidthSum = $xtraWidth + array_sum($columnWidth))) { $diff = $cWidthSum - $envWindow; $max = max($columnWidth) - $xtraWidth; $newWidth = $max - $diff; $i = array_search(max($columnWidth), $columnWidth); $columnWidth[$i] = $newWidth; foreach ($line as $key => &$c) { if (isset($c[$i])) { $c[$i] = self::wordwrap($c[$i], $newWidth); } } } $columnWidth = array_map( function ($x) use ($horizontalPadding) { return $x + 2 * $horizontalPadding; }, $columnWidth ); $newLine = []; foreach ($line as $key => $plpl) { $i = self::getMaxLineNumber($plpl); while ($i-- >= 0) { $newLine[] = array_fill(0, $nbColumn, null); } } $yek = 0; foreach ($line as $key => $col) { foreach ($col as $kkey => $value) { if (false === strpos($value, "\n")) { $newLine[$yek][$kkey] = $value; continue; } foreach (explode("\n", $value) as $foo => $oof) { $newLine[$yek + $foo][$kkey] = $oof; } } $i = self::getMaxLineNumber($col); $i > 0 and $yek += $i; $yek++; } foreach ($newLine as $key => $col) { foreach ($col as $kkey => $value) { if (isset($separator[$kkey])) { $newLine[$key][$kkey] = $separator[$kkey] . str_replace( "\n", "\n" . $separator[$kkey], $value ); } } } $line = $newLine; unset($newLine); foreach ($line as $key => &$column) { $handle = count($column); if ($nbColumn - $handle > 0) { $column += array_fill($handle, $nbColumn - $handle, null); } } $out = null; $dash = $alignement === self::ALIGN_LEFT ? '-' : ''; foreach ($line as $key => $handle) { $format = null; foreach ($handle as $i => $hand) { if (preg_match_all('#(\\e\[[0-9]+m)#', $hand, $match)) { $a = $columnWidth[$i]; foreach ($match as $m) { $a += strlen($m[1]); } $format .= '%' . $dash . ($a + floor(count($match) / 2)) . 's'; } else { $format .= '%' . $dash . $columnWidth[$i] . 's'; } } $format .= str_repeat("\n", $verticalPadding + 1); array_unshift($handle, $format); $out .= call_user_func_array('sprintf', $handle); } return $out; } public static function align( $text, $alignement = self::ALIGN_LEFT, $width = null ) { if (null === $width) { $window = Console\Window::getSize(); $width = $window['x']; } $out = null; switch ($alignement) { case self::ALIGN_LEFT: $out .= sprintf('%-' . $width . 's', self::wordwrap($text, $width)); break; case self::ALIGN_CENTER: foreach (explode("\n", self::wordwrap($text, $width)) as $key => $value) { $out .= str_repeat(' ', ceil(($width - strlen($value)) / 2)) . $value . "\n"; } break; case self::ALIGN_RIGHT: default: foreach (explode("\n", self::wordwrap($text, $width)) as $key => $value) { $out .= sprintf('%' . $width . 's' . "\n", $value); } break; } return $out; } protected static function getMaxLineWidth($lines) { if (!is_array($lines)) { $lines = [$lines]; } $width = 0; foreach ($lines as $foo => $line) { foreach (explode("\n", $line) as $fooo => $lin) { $lin = preg_replace('#\\e\[[0-9]+m#', '', $lin); strlen($lin) > $width and $width = strlen($lin); } } return $width; } protected static function getMaxLineNumber($lines) { if (!is_array($lines)) { $lines = [$lines]; } $number = 0; foreach ($lines as $foo => $line) { substr_count($line, "\n") > $number and $number = substr_count($line, "\n"); } return $number; } public static function wordwrap($text, $width = null, $break = "\n") { if (null === $width) { $window = Console\Window::getSize(); $width = $window['x']; } return wordwrap($text, $width, $break, true); } public static function underline($text, $pattern = '*') { $text = explode("\n", $text); $card = strlen($pattern); foreach ($text as $key => &$value) { $i = -1; $max = strlen($value); while ($value{++$i} == ' ' && $i < $max); $underline = str_repeat(' ', $i) . str_repeat($pattern, strlen(trim($value)) / $card) . str_repeat(' ', strlen($value) - $i - strlen(trim($value))); $value .= "\n" . $underline; } return implode("\n", $text); } } open(); } $process->writeAll($output); if ($mode & PHP_OUTPUT_HANDLER_FINAL) { $process->close(); } return null; } } _output = $output; return; } public function getStream() { return $this->_output; } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 0, $length ); } $out = substr($string, 0, $length); if (true === $this->isMultiplexerConsidered()) { if (true === Console::isTmuxRunning()) { $out = "\033Ptmux;" . str_replace("\033", "\033\033", $out) . "\033\\"; } $length = strlen($out); } if (null === $this->_output) { echo $out; } else { $this->_output->write($out, $length); } } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($character) { return $this->write((string) $character[0], 1); } public function writeBoolean($boolean) { return $this->write(((bool) $boolean) ? '1' : '0', 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return false; } public function considerMultiplexer($consider) { $old = $this->_considerMultiplexer; $this->_considerMultiplexer = $consider; return $old; } public function isMultiplexerConsidered() { return $this->_considerMultiplexer; } } writeAll("\033[8;" . $y . ";" . $x . "t"); return; } public static function getSize() { if (OS_WIN) { $modecon = explode("\n", ltrim(Processus::execute('mode con'))); $_y = trim($modecon[2]); preg_match('#[^:]+:\s*([0-9]+)#', $_y, $matches); $y = (int) $matches[1]; $_x = trim($modecon[3]); preg_match('#[^:]+:\s*([0-9]+)#', $_x, $matches); $x = (int) $matches[1]; return [ 'x' => $x, 'y' => $y ]; } $term = ''; if (isset($_SERVER['TERM'])) { $term = 'TERM="' . $_SERVER['TERM'] . '" '; } $command = $term . 'tput cols && ' . $term . 'tput lines'; $tput = Processus::execute($command, false); if (!empty($tput)) { list($x, $y) = explode("\n", $tput); return [ 'x' => intval($x), 'y' => intval($y) ]; } Console::getOutput()->writeAll("\033[18t"); $input = Console::getInput(); $input->read(4); $x = null; $y = null; $handle = &$y; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$x; break; case 't': break 2; default: if (false === ctype_digit($char)) { break 2; } $handle .= $char; } } while (true); if (null === $x || null === $y) { return [ 'x' => 0, 'y' => 0 ]; } return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function moveTo($x, $y) { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[3;" . $x . ";" . $y . "t"); return; } public static function getPosition() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[13t"); $input = Console::getInput(); $input->read(4); $x = null; $y = null; $handle = &$x; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$y; break; case 't': break 2; default: $handle .= $char; } } while (true); return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function scroll($directions, $repeat = 1) { if (OS_WIN) { return; } if (1 > $repeat) { return; } elseif (1 === $repeat) { $handle = explode(' ', $directions); } else { $handle = explode(' ', $directions, 1); } $tput = Console::getTput(); $count = ['up' => 0, 'down' => 0]; foreach ($handle as $direction) { switch ($direction) { case 'u': case 'up': case '↑': ++$count['up']; break; case 'd': case 'down': case '↓': ++$count['down']; break; } } $output = Console::getOutput(); if (0 < $count['up']) { $output->writeAll( str_replace( '%p1%d', $count['up'] * $repeat, $tput->get('parm_index') ) ); } if (0 < $count['down']) { $output->writeAll( str_replace( '%p1%d', $count['down'] * $repeat, $tput->get('parm_rindex') ) ); } return; } public static function minimize() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[2t"); return; } public static function restore() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[1t"); return; } public static function raise() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[5t"); return; } public static function lower() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[6t"); return; } public static function setTitle($title) { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033]0;" . $title . "\033\\"); return; } public static function getTitle() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[21t"); $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $write = []; $except = []; $out = null; if (0 === stream_select($read, $write, $except, 0, 50000)) { return $out; } $input->read(3); do { $char = $input->readCharacter(); if ("\033" === $char) { $chaar = $input->readCharacter(); if ('\\' === $chaar) { break; } $char .= $chaar; } $out .= $char; } while (true); return $out; } public static function getLabel() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[20t"); $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $write = []; $except = []; $out = null; if (0 === stream_select($read, $write, $except, 0, 50000)) { return $out; } $input->read(3); do { $char = $input->readCharacter(); if ("\033" === $char) { $chaar = $input->readCharacter(); if ('\\' === $chaar) { break; } $char .= $chaar; } $out .= $char; } while (true); return $out; } public static function refresh() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[7t"); return; } public static function copy($data) { if (OS_WIN) { return; } $out = "\033]52;;" . base64_encode($data) . "\033\\"; $output = Console::getOutput(); $considerMultiplexer = $output->considerMultiplexer(true); $output->writeAll($out); $output->considerMultiplexer($considerMultiplexer); return; } } Console::advancedInteraction(); if (function_exists('pcntl_signal')) { Window::getInstance(); pcntl_signal( SIGWINCH, function () { static $_window = null; if (null === $_window) { $_window = Window::getInstance(); } Event::notify( 'hoa://Event/Console/Window:resize', $_window, new Event\Bucket([ 'size' => Window::getSize() ]) ); } ); } parser = new Console\Parser(); return; } public function getOption(&$optionValue, $short = null) { if (null === $this->_options && !empty($this->options)) { $this->setOptions($this->options); } if (null === $this->_options) { return false; } return $this->_options->getOption($optionValue, $short); } public function setOptions(array $options) { $old = $this->options; $this->options = $options; $rule = $this->router->getTheRule(); $variables = $rule[Router::RULE_VARIABLES]; if (isset($variables['_tail'])) { $this->parser->parse($variables['_tail']); $this->_options = new Console\GetOption( $this->options, $this->parser ); } return $old; } public function makeUsageOptionsList(array $definitions = []) { $out = []; foreach ($this->options as $i => $options) { $out[] = [ ' -' . $options[Console\GetOption::OPTION_VAL] . ', --' . $options[Console\GetOption::OPTION_NAME] . ($options[Console\GetOption::OPTION_HAS_ARG] === Console\GetOption::REQUIRED_ARGUMENT ? '=' : ($options[Console\GetOption::OPTION_HAS_ARG] === Console\GetOption::OPTIONAL_ARGUMENT ? '[=]' : '')), (isset($definitions[$options[Console\GetOption::OPTION_VAL]]) ? $definitions[$options[Console\GetOption::OPTION_VAL]] : (isset($definitions[$options[0]]) ? $definitions[$options[Console\GetOption::OPTION_NAME]] : null ) ) ]; } return Console\Chrome\Text::columnize( $out, Console\Chrome\Text::ALIGN_LEFT, .5, 0, '|: ' ); } public function resolveOptionAmbiguity(array $solutions) { echo 'You have made a typo in the option ', $solutions['option'], '; it can match the following options: ', "\n", ' • ', implode(";\n • ", $solutions['solutions']), '.', "\n", 'Please, type the right option (empty to choose the first one):', "\n"; $new = $this->readLine('> '); if (empty($new)) { $new = $solutions['solutions'][0]; } $solutions['solutions'] = [$new]; $this->_options->resolveOptionAmbiguity($solutions); return; } public function status($text, $status) { $window = Console\Window::getSize(); $out = ' ' . Console\Chrome\Text::colorize('*', 'foreground(yellow)') . ' ' . $text . str_pad( ' ', $window['x'] - strlen(preg_replace('#' . "\033" . '\[[0-9]+m#', '', $text)) - 8 ) . ($status === true ? '[' . Console\Chrome\Text::colorize('ok', 'foreground(green)') . ']' : '[' . Console\Chrome\Text::colorize('!!', 'foreground(white) background(red)') . ']'); Console::getOutput()->writeAll($out . "\n"); return; } public function readLine($prefix = null) { static $_rl = null; if (null === $_rl) { $_rl = new Console\Readline(); } return $_rl->readLine($prefix); } public function readPassword($prefix = null) { static $_rl = null; if (null === $_rl) { $_rl = new Console\Readline\Password(); } return $_rl->readLine($prefix); } } ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ]; protected $_pipes = null; protected $_seekable = []; public function __construct( $command, array $options = null, array $descriptors = null, $cwd = null, array $environment = null, $timeout = 30 ) { $this->setCommand($command); if (null !== $options) { $this->setOptions($options); } if (null !== $descriptors) { $this->_descriptors = []; foreach ($descriptors as $descriptor => $nature) { if (isset($this->_descriptors[$descriptor])) { throw new Exception( 'Pipe descriptor %d already exists, cannot ' . 'redefine it.', 0, $descriptor ); } $this->_descriptors[$descriptor] = $nature; } } $this->setCwd($cwd ?: getcwd()); if (null !== $environment) { $this->setEnvironment($environment); } $this->setTimeout($timeout); parent::__construct($this->getCommandLine(), null, true); $this->getListener()->addIds(['input', 'output', 'timeout', 'start', 'stop']); return; } protected function &_open($streamName, Stream\Context $context = null) { $out = @proc_open( $streamName, $this->_descriptors, $this->_pipes, $this->getCwd(), $this->getEnvironment() ); if (false === $out) { throw new Exception( 'Something wrong happen when running %s.', 1, $streamName ); } return $out; } protected function _close() { foreach ($this->_pipes as $pipe) { @fclose($pipe); } return @proc_close($this->getStream()); } public function run() { if (false === $this->isOpened()) { $this->open(); } else { $this->_close(); $this->_setStream($this->_open( $this->getStreamName(), $this->getStreamContext() )); } $this->getListener()->fire('start', new Event\Bucket()); $_read = []; $_write = []; $_except = []; foreach ($this->_pipes as $p => $pipe) { switch ($this->_descriptors[$p][1]) { case 'r': stream_set_blocking($pipe, false); $_write[] = $pipe; break; case 'w': case 'a': stream_set_blocking($pipe, true); $_read[] = $pipe; break; } } while (true) { foreach ($_read as $i => $r) { if (false === is_resource($r)) { unset($_read[$i]); } } foreach ($_write as $i => $w) { if (false === is_resource($w)) { unset($_write[$i]); } } foreach ($_except as $i => $e) { if (false === is_resource($e)) { unset($_except[$i]); } } if (empty($_read) && empty($_write) && empty($_except)) { break; } $read = $_read; $write = $_write; $except = $_except; $select = stream_select($read, $write, $except, $this->getTimeout()); if (0 === $select) { $this->getListener()->fire('timeout', new Event\Bucket()); break; } foreach ($read as $i => $_r) { $pipe = array_search($_r, $this->_pipes); $line = $this->readLine($pipe); if (false === $line) { $result = [false]; } else { $result = $this->getListener()->fire( 'output', new Event\Bucket([ 'pipe' => $pipe, 'line' => $line ]) ); } if (true === feof($_r) || in_array(false, $result, true)) { fclose($_r); unset($_read[$i]); break; } } foreach ($write as $j => $_w) { $result = $this->getListener()->fire( 'input', new Event\Bucket([ 'pipe' => array_search($_w, $this->_pipes) ]) ); if (true === feof($_w) || in_array(false, $result, true)) { fclose($_w); unset($_write[$j]); } } if (empty($_read)) { break; } } $this->getListener()->fire('stop', new Event\Bucket()); return; } protected function getPipe($pipe) { if (!isset($this->_pipes[$pipe])) { throw new Exception( 'Pipe descriptor %d does not exist, cannot read from it.', 2, $pipe ); } return $this->_pipes[$pipe]; } protected function isPipeSeekable($pipe) { if (!isset($this->_seekable[$pipe])) { $_pipe = $this->getPipe($pipe); $data = stream_get_meta_data($_pipe); $this->_seekable[$pipe] = $data['seekable']; } return $this->_seekable[$pipe]; } public function eof($pipe = 1) { return feof($this->getPipe($pipe)); } public function read($length, $pipe = 1) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fread($this->getPipe($pipe), $length); } public function readString($length, $pipe = 1) { return $this->read($length, $pipe); } public function readCharacter($pipe = 1) { return fgetc($this->getPipe($pipe)); } public function readBoolean($pipe = 1) { return (bool) $this->read(1, $pipe); } public function readInteger($length = 1, $pipe = 1) { return (int) $this->read($length, $pipe); } public function readFloat($length = 1, $pipe = 1) { return (float) $this->read($length, $pipe); } public function readArray($format = null, $pipe = 1) { return $this->scanf($format, $pipe); } public function readLine($pipe = 1) { return stream_get_line($this->getPipe($pipe), 1 << 15, "\n"); } public function readAll($offset = -1, $pipe = 1) { $_pipe = $this->getPipe($pipe); if (true === $this->isPipeSeekable($pipe)) { $offset += ftell($_pipe); } else { $offset = -1; } return stream_get_contents($_pipe, -1, $offset); } public function scanf($format, $pipe = 1) { return fscanf($this->getPipe($pipe), $format); } public function write($string, $length, $pipe = 0) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 4, $length ); } return fwrite($this->getPipe($pipe), $string, $length); } public function writeString($string, $pipe = 0) { $string = (string) $string; return $this->write($string, strlen($string), $pipe); } public function writeCharacter($char, $pipe = 0) { return $this->write((string) $char[0], 1, $pipe); } public function writeBoolean($boolean, $pipe = 0) { return $this->write((string) (bool) $boolean, 1, $pipe); } public function writeInteger($integer, $pipe = 0) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer), $pipe); } public function writeFloat($float, $pipe = 0) { $float = (string) (float) $float; return $this->write($float, strlen($float), $pipe); } public function writeArray(array $array, $pipe = 0) { $array = var_export($array, true); return $this->write($array, strlen($array), $pipe); } public function writeLine($line, $pipe = 0) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1, $pipe); } ++$n; return $this->write(substr($line, 0, $n), $n, $pipe); } public function writeAll($string, $pipe = 0) { return $this->write($string, strlen($string), $pipe); } public function truncate($size, $pipe = 0) { return ftruncate($this->getPipe($pipe), $size); } public function getBasename() { return basename($this->getCommand()); } public function getDirname() { return dirname($this->getCommand()); } public function getStatus() { return proc_get_status($this->getStream()); } public function getExitCode() { $handle = $this->getStatus(); return $handle['exitcode']; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function terminate($signal = self::SIGTERM) { return proc_terminate($this->getStream(), $signal); } protected function setCommand($command) { $old = $this->_command; $this->_command = escapeshellcmd($command); return $old; } public function getCommand() { return $this->_command; } protected function setOptions(array $options) { foreach ($options as &$option) { $option = escapeshellarg($option); } $old = $this->_options; $this->_options = $options; return $old; } public function getOptions() { return $this->_options; } public function getCommandLine() { $out = $this->getCommand(); foreach ($this->getOptions() as $key => $value) { if (!is_int($key)) { $out .= ' ' . $key . '=' . $value; } else { $out .= ' ' . $value; } } return $out; } protected function setCwd($cwd) { $old = $this->_cwd; $this->_cwd = $cwd; return $old; } public function getCwd() { return $this->_cwd; } protected function setEnvironment(array $environment) { $old = $this->_environment; $this->_environment = $environment; return $old; } public function getEnvironment() { return $this->_environment; } public function setTimeout($timeout) { $old = $this->_timeout; $this->_timeout = $timeout; return $old; } public function getTimeout() { return $this->_timeout; } public static function setTitle($title) { if (PHP_VERSION_ID < 50500) { return; } cli_set_process_title($title); return; } public static function getTitle() { if (PHP_VERSION_ID < 50500) { return null; } return cli_get_process_title(); } public static function locate($binary) { if (isset($_ENV['PATH'])) { $separator = ':'; $path = &$_ENV['PATH']; } elseif (isset($_SERVER['PATH'])) { $separator = ':'; $path = &$_SERVER['PATH']; } elseif (isset($_SERVER['Path'])) { $separator = ';'; $path = &$_SERVER['Path']; } else { return null; } foreach (explode($separator, $path) as $directory) { if (true === file_exists($out = $directory . DS . $binary)) { return $out; } } return null; } public static function execute($commandLine, $escape = true) { if (true === $escape) { $commandLine = escapeshellcmd($commandLine); } return rtrim(shell_exec($commandLine)); } } _options = $options; $this->_parser = $parser; if (empty($options)) { $this->_pipette[null] = null; return; } $names = []; foreach ($options as $i => $option) { if (isset($option[self::OPTION_NAME])) { $names[$option[self::OPTION_NAME]] = $i; } if (isset($option[self::OPTION_VAL])) { $names[$option[self::OPTION_VAL]] = $i; } } $_names = array_keys($names); $switches = $parser->getSwitches(); foreach ($switches as $name => $value) { if (false === in_array($name, $_names)) { if (1 === strlen($name)) { $this->_pipette[] = ['__ambiguous', [ 'solutions' => [], 'value' => $value, 'option' => $name ]]; continue; } $haystack = implode(';', $_names); $differences = (int) ceil(strlen($name) / 3); $searched = Ustring\Search::approximated( $haystack, $name, $differences ); $solutions = []; foreach ($searched as $s) { $h = substr($haystack, $s['i'], $s['l']); if (false !== strpos($h, ';') || false !== in_array($h, array_keys($switches)) || false === in_array($h, $_names)) { continue; } $solutions[] = $h; } if (empty($solutions)) { continue; } $this->_pipette[] = ['__ambiguous', [ 'solutions' => $solutions, 'value' => $value, 'option' => $name ]]; continue; } $option = $options[$names[$name]]; $argument = $option[self::OPTION_HAS_ARG]; if (self::NO_ARGUMENT === $argument) { if (!is_bool($value)) { $parser->transferSwitchToInput($name, $value); } } elseif (self::REQUIRED_ARGUMENT === $argument && !is_string($value)) { throw new Exception( 'The argument %s requires a value (it is not a switch).', 0, $name ); } $this->_pipette[] = [$option[self::OPTION_VAL], $value]; } $this->_pipette[null] = null; reset($this->_pipette); return; } public function getOption(&$optionValue, $short = null) { static $first = true; $optionValue = null; if (true === $this->isPipetteEmpty() && true === $first) { $first = false; return false; } $k = key($this->_pipette); $c = current($this->_pipette); $key = $c[0]; $value = $c[1]; if (null == $k && null === $c) { reset($this->_pipette); $first = true; return false; } $allow = []; if (null === $short) { foreach ($this->_options as $option) { $allow[] = $option[self::OPTION_VAL]; } } else { $allow = str_split($short); } if (!in_array($key, $allow) && '__ambiguous' != $key) { return false; } $optionValue = $value; $return = $key; next($this->_pipette); return $return; } public function isPipetteEmpty() { return count($this->_pipette) == 1; } public function resolveOptionAmbiguity(array $solutions) { if (!isset($solutions['solutions']) || !isset($solutions['value']) || !isset($solutions['option'])) { throw new Exception( 'Cannot resolve option ambiguity because the given solution ' . 'seems to be corruped.', 1 ); } $choices = $solutions['solutions']; if (1 > count($choices)) { throw new Exception( 'Cannot resolve ambiguity, fix your typo in the option %s :-).', 2, $solutions['option'] ); } $theSolution = $choices[0]; foreach ($this->_options as $option) { if ($theSolution == $option[self::OPTION_NAME] || $theSolution == $option[self::OPTION_VAL]) { $argument = $option[self::OPTION_HAS_ARG]; $value = $solutions['value']; if (self::NO_ARGUMENT === $argument) { if (!is_bool($value)) { $this->_parser->transferSwitchToInput($theSolution, $value); } } elseif (self::REQUIRED_ARGUMENT === $argument && !is_string($value)) { throw new Exception( 'The argument %s requires a value (it is not a switch).', 3, $theSolution ); } unset($this->_pipette[null]); $this->_pipette[] = [$option[self::OPTION_VAL], $value]; $this->_pipette[null] = null; return; } } return; } } _parsed); $this->_parsed = [ 'input' => [], 'switch' => [] ]; $regex = '#(?:(?--?[^=\s]+)(?:(?:(=)|(\s))(?(?(3)[^-]|).*?)(?(4)(?.*?)(?(6)(?addInput($match); } elseif (!isset($match['i']) && !isset($match['s'])) { if (isset($matches[$i + 1])) { $nextMatch = $matches[$i + 1]; if (!empty($nextMatch['i']) && '=' === $nextMatch['i'][0]) { ++$i; $match[2] = '='; $match[3] = $match[4] = null; $match['s'] = $match[5] = substr($nextMatch[7], 1); $this->addValuedSwitch($match); continue; } } $this->addBoolSwitch($match); } elseif (!isset($match['i']) && isset($match['s'])) { $this->addValuedSwitch($match); } } return; } protected function addInput(array $input) { $handle = $input['i']; if (!empty($input[6])) { $handle = str_replace('\\' . $input[6], $input[6], $handle); } else { $handle = str_replace('\\ ', ' ', $handle); } $this->_parsed['input'][] = $handle; return; } protected function addBoolSwitch(array $switch) { $this->addSwitch($switch['b'], true); return; } protected function addValuedSwitch(array $switch) { $this->addSwitch($switch['b'], $switch['s'], $switch[4]); return; } protected function addSwitch($name, $value, $escape = null) { if (substr($name, 0, 2) == '--') { return $this->addSwitch(substr($name, 2), $value, $escape); } if (substr($name, 0, 1) == '-') { if (true === $this->getLongOnly()) { return $this->addSwitch('-' . $name, $value, $escape); } foreach (str_split(substr($name, 1)) as $foo => $switch) { $this->addSwitch($switch, $value, $escape); } return; } if (null !== $escape) { $escape = '' == $escape ? ' ' : $escape; if (is_string($value)) { $value = str_replace('\\' . $escape, $escape, $value); } } elseif (is_string($value)) { $value = str_replace('\\ ', ' ', $value); } if (isset($this->_parsed['switch'][$name])) { if (is_bool($this->_parsed['switch'][$name])) { $value = !$this->_parsed['switch'][$name]; } else { $value = [$this->_parsed['switch'][$name], $value]; } } if (empty($name)) { return $this->addInput([6 => null, 'i' => $value]); } $this->_parsed['switch'][$name] = $value; return; } public function transferSwitchToInput($name, &$value) { if (!isset($this->_parsed['switch'][$name])) { return; } $this->_parsed['input'][] = $this->_parsed['switch'][$name]; $value = true; unset($this->_parsed['switch'][$name]); return; } public function getInputs() { return $this->_parsed['input']; } public function listInputs( &$a, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null, &$g = null, &$h = null, &$i = null, &$j = null, &$k = null, &$l = null, &$m = null, &$n = null, &$o = null, &$p = null, &$q = null, &$r = null, &$s = null, &$t = null, &$u = null, &$v = null, &$w = null, &$x = null, &$y = null, &$z = null ) { $inputs = $this->getInputs(); $i = 'a'; $ii = -1; while (isset($inputs[++$ii]) && $i <= 'z') { ${$i++} = $inputs[$ii]; } return; } public function getSwitches() { return $this->_parsed['switch']; } public function parseSpecialValue($value, array $keywords = []) { $out = []; foreach (explode(',', $value) as $key => $subvalue) { $subvalue = str_replace( array_keys($keywords), array_values($keywords), $subvalue ); if (0 !== preg_match('#^(-?[0-9]+):(-?[0-9]+)$#', $subvalue, $matches)) { if (0 > $matches[1] && 0 > $matches[2]) { throw new Exception( 'Cannot give two negative numbers, given %s.', 0, $subvalue ); } array_shift($matches); $max = max($matches); $min = min($matches); if (0 > $max || 0 > $min) { if (0 > $max - $min) { throw new Exception( 'The difference between operands must be ' . 'positive.', 1 ); } $min = $max + $min; } $out = array_merge(range($min, $max), $out); } else { $out[] = $subvalue; } } return $out; } public function setLongOnly($longonly = false) { $old = $this->_longonly; $this->_longonly = $longonly; return $old; } public function getLongOnly() { return $this->_longonly; } } _mapping["\033[A"] = xcallable($this, '_bindArrowUp'); $this->_mapping["\033[B"] = xcallable($this, '_bindArrowDown'); $this->_mapping["\033[C"] = xcallable($this, '_bindArrowRight'); $this->_mapping["\033[D"] = xcallable($this, '_bindArrowLeft'); $this->_mapping["\001"] = xcallable($this, '_bindControlA'); $this->_mapping["\002"] = xcallable($this, '_bindControlB'); $this->_mapping["\005"] = xcallable($this, '_bindControlE'); $this->_mapping["\006"] = xcallable($this, '_bindControlF'); $this->_mapping["\010"] = $this->_mapping["\177"] = xcallable($this, '_bindBackspace'); $this->_mapping["\027"] = xcallable($this, '_bindControlW'); $this->_mapping["\n"] = xcallable($this, '_bindNewline'); $this->_mapping["\t"] = xcallable($this, '_bindTab'); return; } public function readLine($prefix = null) { $input = Console::getInput(); if (true === $input->eof()) { return false; } $direct = Console::isDirect($input->getStream()->getStream()); $output = Console::getOutput(); if (false === $direct || OS_WIN) { $out = $input->readLine(); if (false === $out) { return false; } $out = substr($out, 0, -1); if (true === $direct) { $output->writeAll($prefix); } else { $output->writeAll($prefix . $out . "\n"); } return $out; } $this->resetLine(); $this->setPrefix($prefix); $read = [$input->getStream()->getStream()]; $output->writeAll($prefix); while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = [$input->getStream()->getStream()]; continue; } $char = $this->_read(); $this->_buffer = $char; $return = $this->_readLine($char); if (0 === ($return & self::STATE_NO_ECHO)) { $output->writeAll($this->_buffer); } if (0 !== ($return & self::STATE_BREAK)) { break; } } return $this->getLine(); } public function _readLine($char) { if (isset($this->_mapping[$char]) && is_callable($this->_mapping[$char])) { $mapping = $this->_mapping[$char]; return $mapping($this); } if (isset($this->_mapping[$char])) { $this->_buffer = $this->_mapping[$char]; } elseif (false === Ustring::isCharPrintable($char)) { Console\Cursor::bip(); return static::STATE_CONTINUE | static::STATE_NO_ECHO; } if ($this->getLineLength() == $this->getLineCurrent()) { $this->appendLine($this->_buffer); return static::STATE_CONTINUE; } $this->insertLine($this->_buffer); $tail = mb_substr( $this->getLine(), $this->getLineCurrent() - 1 ); $this->_buffer = "\033[K" . $tail . str_repeat( "\033[D", mb_strlen($tail) - 1 ); return static::STATE_CONTINUE; } public function addMappings(array $mappings) { foreach ($mappings as $key => $mapping) { $this->addMapping($key, $mapping); } return; } public function addMapping($key, $mapping) { if ('\e[' === substr($key, 0, 3)) { $this->_mapping["\033[" . substr($key, 3)] = $mapping; } elseif ('\C-' === substr($key, 0, 3)) { $_key = ord(strtolower(substr($key, 3))) - 96; $this->_mapping[chr($_key)] = $mapping; } else { $this->_mapping[$key] = $mapping; } return; } public function addHistory($line = null) { if (empty($line)) { return; } $this->_history[] = $line; $this->_historyCurrent = $this->_historySize++; return; } public function clearHistory() { unset($this->_history); $this->_history = []; $this->_historyCurrent = 0; $this->_historySize = 1; return; } public function getHistory($i = null) { if (null === $i) { $i = $this->_historyCurrent; } if (!isset($this->_history[$i])) { return null; } return $this->_history[$i]; } public function previousHistory() { if (0 >= $this->_historyCurrent) { return $this->getHistory(0); } return $this->getHistory($this->_historyCurrent--); } public function nextHistory() { if ($this->_historyCurrent + 1 >= $this->_historySize) { return $this->getLine(); } return $this->getHistory(++$this->_historyCurrent); } public function getLine() { return $this->_line; } public function appendLine($append) { $this->_line .= $append; $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent = $this->_lineLength; return; } public function insertLine($insert) { if ($this->_lineLength == $this->_lineCurrent) { return $this->appendLine($insert); } $this->_line = mb_substr($this->_line, 0, $this->_lineCurrent) . $insert . mb_substr($this->_line, $this->_lineCurrent); $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent += mb_strlen($insert); return; } protected function resetLine() { $this->_line = null; $this->_lineCurrent = 0; $this->_lineLength = 0; return; } public function getLineCurrent() { return $this->_lineCurrent; } public function getLineLength() { return $this->_lineLength; } public function setPrefix($prefix) { $this->_prefix = $prefix; return; } public function getPrefix() { return $this->_prefix; } public function getBuffer() { return $this->_buffer; } public function setAutocompleter(Autocompleter $autocompleter) { $old = $this->_autocompleter; $this->_autocompleter = $autocompleter; return $old; } public function getAutocompleter() { return $this->_autocompleter; } public function _read($length = 512) { return Console::getInput()->read($length); } public function setLine($line) { $this->_line = $line; $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent = $this->_lineLength; return; } public function setLineCurrent($current) { $this->_lineCurrent = $current; return; } public function setLineLength($length) { $this->_lineLength = $length; return; } public function setBuffer($buffer) { $this->_buffer = $buffer; return; } public function _bindArrowUp(Readline $self) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::clear('↔'); Console::getOutput()->writeAll($self->getPrefix()); } $self->setBuffer($buffer = $self->previousHistory()); $self->setLine($buffer); return static::STATE_CONTINUE; } public function _bindArrowDown(Readline $self) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::clear('↔'); Console::getOutput()->writeAll($self->getPrefix()); } $self->setBuffer($buffer = $self->nextHistory()); $self->setLine($buffer); return static::STATE_CONTINUE; } public function _bindArrowRight(Readline $self) { if ($self->getLineLength() > $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('→'); } $self->setLineCurrent($self->getLineCurrent() + 1); } $self->setBuffer(null); return static::STATE_CONTINUE; } public function _bindArrowLeft(Readline $self) { if (0 < $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('←'); } $self->setLineCurrent($self->getLineCurrent() - 1); } $self->setBuffer(null); return static::STATE_CONTINUE; } public function _bindBackspace(Readline $self) { $buffer = null; if (0 < $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('←'); Console\Cursor::clear('→'); } if ($self->getLineLength() == $current = $self->getLineCurrent()) { $self->setLine(mb_substr($self->getLine(), 0, -1)); } else { $line = $self->getLine(); $current = $self->getLineCurrent(); $tail = mb_substr($line, $current); $buffer = $tail . str_repeat("\033[D", mb_strlen($tail)); $self->setLine(mb_substr($line, 0, $current - 1) . $tail); $self->setLineCurrent($current - 1); } } $self->setBuffer($buffer); return static::STATE_CONTINUE; } public function _bindControlA(Readline $self) { for ($i = $self->getLineCurrent() - 1; 0 <= $i; --$i) { $self->_bindArrowLeft($self); } return static::STATE_CONTINUE; } public function _bindControlB(Readline $self) { $current = $self->getLineCurrent(); if (0 === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i + 1][1] < $current; ++$i ); for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { $self->_bindArrowLeft($self); } return static::STATE_CONTINUE; } public function _bindControlE(Readline $self) { for ( $i = $self->getLineCurrent(), $max = $self->getLineLength(); $i < $max; ++$i ) { $self->_bindArrowRight($self); } return static::STATE_CONTINUE; } public function _bindControlF(Readline $self) { $current = $self->getLineCurrent(); if ($self->getLineLength() === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i][1] < $current; ++$i ); if (!isset($words[$i + 1])) { $words[$i + 1] = [1 => $self->getLineLength()]; } for ($j = $words[$i + 1][1]; $j > $current; --$j) { $self->_bindArrowRight($self); } return static::STATE_CONTINUE; } public function _bindControlW(Readline $self) { $current = $self->getLineCurrent(); if (0 === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i + 1][1] < $current; ++$i ); for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { $self->_bindBackspace($self); } return static::STATE_CONTINUE; } public function _bindNewline(Readline $self) { $self->addHistory($self->getLine()); return static::STATE_BREAK; } public function _bindTab(Readline $self) { $output = Console::getOutput(); $autocompleter = $self->getAutocompleter(); $state = static::STATE_CONTINUE | static::STATE_NO_ECHO; if (null === $autocompleter) { return $state; } $current = $self->getLineCurrent(); $line = $self->getLine(); if (0 === $current) { return $state; } $matches = preg_match_all( '#' . $autocompleter->getWordDefinition() . '$#u', mb_substr($line, 0, $current), $words ); if (0 === $matches) { return $state; } $word = $words[0][0]; if ('' === trim($word)) { return $state; } $solution = $autocompleter->complete($word); $length = mb_strlen($word); if (null === $solution) { return $state; } if (is_array($solution)) { $_solution = $solution; $count = count($_solution) - 1; $cWidth = 0; $window = Console\Window::getSize(); $wWidth = $window['x']; $cursor = Console\Cursor::getPosition(); array_walk($_solution, function (&$value) use (&$cWidth) { $handle = mb_strlen($value); if ($handle > $cWidth) { $cWidth = $handle; } return; }); array_walk($_solution, function (&$value) use (&$cWidth) { $handle = mb_strlen($value); if ($handle >= $cWidth) { return; } $value .= str_repeat(' ', $cWidth - $handle); return; }); $mColumns = (int) floor($wWidth / ($cWidth + 2)); $mLines = (int) ceil(($count + 1) / $mColumns); --$mColumns; $i = 0; if (0 > $window['y'] - $cursor['y'] - $mLines) { Console\Window::scroll('↑', $mLines); Console\Cursor::move('↑', $mLines); } Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); foreach ($_solution as $j => $s) { $output->writeAll("\033[0m" . $s . "\033[0m"); if ($i++ < $mColumns) { $output->writeAll(' '); } else { $i = 0; if (isset($_solution[$j + 1])) { $output->writeAll("\n"); } } } Console\Cursor::restore(); Console\Cursor::show(); ++$mColumns; $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $mColumn = -1; $mLine = -1; $coord = -1; $unselect = function () use ( &$mColumn, &$mLine, &$coord, &$_solution, &$cWidth, $output ) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); $output->writeAll("\033[0m" . $_solution[$coord] . "\033[0m"); Console\Cursor::restore(); Console\Cursor::show(); return; }; $select = function () use ( &$mColumn, &$mLine, &$coord, &$_solution, &$cWidth, $output ) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); $output->writeAll("\033[7m" . $_solution[$coord] . "\033[0m"); Console\Cursor::restore(); Console\Cursor::show(); return; }; $init = function () use ( &$mColumn, &$mLine, &$coord, &$select ) { $mColumn = 0; $mLine = 0; $coord = 0; $select(); return; }; while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = [$input->getStream()->getStream()]; continue; } switch ($char = $self->_read()) { case "\033[A": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\033[B": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\t": case "\033[C": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\033[D": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\n": if (-1 !== $mColumn && -1 !== $mLine) { $tail = mb_substr($line, $current); $current -= $length; $self->setLine( mb_substr($line, 0, $current) . $solution[$coord] . $tail ); $self->setLineCurrent( $current + mb_strlen($solution[$coord]) ); Console\Cursor::move('←', $length); $output->writeAll($solution[$coord]); Console\Cursor::clear('→'); $output->writeAll($tail); Console\Cursor::move('←', mb_strlen($tail)); } default: $mColumn = -1; $mLine = -1; $coord = -1; Console\Cursor::save(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); Console\Cursor::restore(); if ("\033" !== $char && "\n" !== $char) { $self->setBuffer($char); return $self->_readLine($char); } break 2; } } return $state; } $tail = mb_substr($line, $current); $current -= $length; $self->setLine( mb_substr($line, 0, $current) . $solution . $tail ); $self->setLineCurrent( $current + mb_strlen($solution) ); Console\Cursor::move('←', $length); $output->writeAll($solution); Console\Cursor::clear('→'); $output->writeAll($tail); Console\Cursor::move('←', mb_strlen($tail)); return $state; } } Console::advancedInteraction(); Consistency::flexEntity('Hoa\Console\Readline\Readline'); setAutocompleters($autocompleters); return; } public function complete(&$prefix) { foreach ($this->getAutocompleters() as $autocompleter) { $preg = preg_match( '#(' . $autocompleter->getWordDefinition() . ')$#u', $prefix, $match ); if (0 === $preg) { continue; } $_prefix = $match[0]; if (null === $out = $autocompleter->complete($_prefix)) { continue; } $prefix = $_prefix; return $out; } return null; } protected function setAutocompleters(array $autocompleters) { $old = $this->_autocompleters; $this->_autocompleters = new \ArrayObject($autocompleters); return $old; } public function getAutocompleters() { return $this->_autocompleters; } public function getWordDefinition() { return '.*'; } } setWords($words); return; } public function complete(&$prefix) { $out = []; $length = mb_strlen($prefix); foreach ($this->getWords() as $word) { if (mb_substr($word, 0, $length) === $prefix) { $out[] = $word; } } if (empty($out)) { return null; } if (1 === count($out)) { return $out[0]; } return $out; } public function getWordDefinition() { return '\b\w+'; } public function setWords(array $words) { $old = $this->_words; $this->_words = $words; return $old; } public function getWords() { return $this->_words; } } setRoot($root); if (null !== $iteratorFactory) { $this->setIteratorFactory($iteratorFactory); } return; } public function complete(&$prefix) { $root = $this->getRoot(); if (static::PWD === $root) { $root = getcwd(); } $path = $root . DS . $prefix; if (!is_dir($path)) { $path = dirname($path) . DS; $prefix = basename($prefix); } else { $prefix = null; } $iteratorFactory = $this->getIteratorFactory() ?: static::getDefaultIteratorFactory(); try { $iterator = $iteratorFactory($path); $out = []; $length = mb_strlen($prefix); foreach ($iterator as $fileinfo) { $filename = $fileinfo->getFilename(); if (null === $prefix || (mb_substr($filename, 0, $length) === $prefix)) { if ($fileinfo->isDir()) { $out[] = $filename . '/'; } else { $out[] = $filename; } } } } catch (\Exception $e) { return null; } $count = count($out); if (1 === $count) { return $out[0]; } if (0 === $count) { return null; } return $out; } public function getWordDefinition() { return '/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*'; } public function setRoot($root) { $old = $this->_root; $this->_root = $root; return $old; } public function getRoot() { return $this->_root; } public function setIteratorFactory(\Closure $iteratorFactory) { $old = $this->_iteratorFactory; $this->_iteratorFactory = $iteratorFactory; return $old; } public function getIteratorFactory() { return $this->_iteratorFactory; } public static function getDefaultIteratorFactory() { return function ($path) { return new \DirectoryIterator($path); }; } } $repeat) { return; } elseif (1 === $repeat) { $handle = explode(' ', $steps); } else { $handle = explode(' ', $steps, 1); } $tput = Console::getTput(); $output = Console::getOutput(); foreach ($handle as $step) { switch ($step) { case 'u': case 'up': case '↑': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_up_cursor') ) ); break; case 'U': case 'UP': static::moveTo(null, 1); break; case 'r': case 'right': case '→': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_right_cursor') ) ); break; case 'R': case 'RIGHT': static::moveTo(9999); break; case 'd': case 'down': case '↓': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_down_cursor') ) ); break; case 'D': case 'DOWN': static::moveTo(null, 9999); break; case 'l': case 'left': case '←': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_left_cursor') ) ); break; case 'L': case 'LEFT': static::moveTo(1); break; } } return; } public static function moveTo($x = null, $y = null) { if (null === $x || null === $y) { $position = static::getPosition(); if (null === $x) { $x = $position['x']; } if (null === $y) { $y = $position['y']; } } Console::getOutput()->writeAll( str_replace( ['%i%p1%d', '%p2%d'], [$y, $x], Console::getTput()->get('cursor_address') ) ); return; } public static function getPosition() { $tput = Console::getTput(); $user7 = $tput->get('user7'); if (null === $user7) { return [ 'x' => 0, 'y' => 0 ]; } Console::getOutput()->writeAll($user7); $input = Console::getInput(); $input->read(2); $x = null; $y = null; $handle = &$y; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$x; break; case 'R': break 2; default: $handle .= $char; } } while (true); return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function save() { Console::getOutput()->writeAll( Console::getTput()->get('save_cursor') ); return; } public static function restore() { Console::getOutput()->writeAll( Console::getTput()->get('restore_cursor') ); return; } public static function clear($parts = 'all') { $tput = Console::getTput(); $output = Console::getOutput(); foreach (explode(' ', $parts) as $part) { switch ($part) { case 'a': case 'all': case '↕': $output->writeAll($tput->get('clear_screen')); static::moveTo(1, 1); break; case 'u': case 'up': case '↑': $output->writeAll("\033[1J"); break; case 'r': case 'right': case '→': $output->writeAll($tput->get('clr_eol')); break; case 'd': case 'down': case '↓': $output->writeAll($tput->get('clr_eos')); break; case 'l': case 'left': case '←': $output->writeAll($tput->get('clr_bol')); break; case 'line': case '↔': $output->writeAll("\r" . $tput->get('clr_eol')); break; } } return; } public static function hide() { Console::getOutput()->writeAll( Console::getTput()->get('cursor_invisible') ); return; } public static function show() { Console::getOutput()->writeAll( Console::getTput()->get('cursor_visible') ); return; } public static function colorize($attributes) { static $_rgbTo256 = null; if (null === $_rgbTo256) { $_rgbTo256 = [ '000000', '800000', '008000', '808000', '000080', '800080', '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', '585858', '606060', '666666', '767676', '808080', '8a8a8a', '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee' ]; } $tput = Console::getTput(); if (1 >= $tput->count('max_colors')) { return; } $handle = []; foreach (explode(' ', $attributes) as $attribute) { switch ($attribute) { case 'n': case 'normal': $handle[] = 0; break; case 'b': case 'bold': $handle[] = 1; break; case 'u': case 'underlined': $handle[] = 4; break; case 'bl': case 'blink': $handle[] = 5; break; case 'i': case 'inverse': $handle[] = 7; break; case '!b': case '!bold': $handle[] = 22; break; case '!u': case '!underlined': $handle[] = 24; break; case '!bl': case '!blink': $handle[] = 25; break; case '!i': case '!inverse': $handle[] = 27; break; default: if (0 === preg_match('#^([^\(]+)\(([^\)]+)\)$#', $attribute, $m)) { break; } $shift = 0; switch ($m[1]) { case 'fg': case 'foreground': $shift = 0; break; case 'bg': case 'background': $shift = 10; break; default: break 2; } $_handle = 0; $_keyword = true; switch ($m[2]) { case 'black': $_handle = 30; break; case 'red': $_handle = 31; break; case 'green': $_handle = 32; break; case 'yellow': $_handle = 33; break; case 'blue': $_handle = 34; break; case 'magenta': $_handle = 35; break; case 'cyan': $_handle = 36; break; case 'white': $_handle = 37; break; case 'default': $_handle = 39; break; default: $_keyword = false; if (256 <= $tput->count('max_colors') && '#' === $m[2][0]) { $rgb = hexdec(substr($m[2], 1)); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; $distance = null; foreach ($_rgbTo256 as $i => $_rgb) { $_rgb = hexdec($_rgb); $_r = ($_rgb >> 16) & 255; $_g = ($_rgb >> 8) & 255; $_b = $_rgb & 255; $d = sqrt( pow($_r - $r, 2) + pow($_g - $g, 2) + pow($_b - $b, 2) ); if (null === $distance || $d <= $distance) { $distance = $d; $_handle = $i; } } } else { $_handle = intval($m[2]); } } if (true === $_keyword) { $handle[] = $_handle + $shift; } else { $handle[] = (38 + $shift) . ';5;' . $_handle; } } } Console::getOutput()->writeAll("\033[" . implode(';', $handle) . "m"); return; } public static function changeColor($fromCode, $toColor) { $tput = Console::getTput(); if (true !== $tput->has('can_change')) { return; } $r = ($toColor >> 16) & 255; $g = ($toColor >> 8) & 255; $b = $toColor & 255; Console::getOutput()->writeAll( str_replace( [ '%p1%d', 'rgb:', '%p2%{255}%*%{1000}%/%2.2X/', '%p3%{255}%*%{1000}%/%2.2X/', '%p4%{255}%*%{1000}%/%2.2X' ], [ $fromCode, '', sprintf('%02x', $r), sprintf('%02x', $g), sprintf('%02x', $b) ], $tput->get('initialize_color') ) ); return; } public static function setStyle($style, $blink = true) { if (OS_WIN) { return; } switch ($style) { case 'b': case 'block': case '▋': $_style = 1; break; case 'u': case 'underline': case '_': $_style = 2; break; case 'v': case 'vertical': case '|': $_style = 5; break; } if (false === $blink) { ++$_style; } Console::getOutput()->writeAll("\033[" . $_style . " q"); return; } public static function bip() { Console::getOutput()->writeAll( Console::getTput()->get('bell') ); return; } } Console::advancedInteraction(); _infos[] = $default; } else { $this->_infos[$infos] = $default; } return $out; } public function current() { $out = parent::current(); foreach ($out as $key => &$value) { if (null === $value) { $value = $this->_infos[$key]; } } return $out; } } _callback = $callback; return; } public function current() { $handle = $this->_callback; return $this->_current = $handle($this->_key); } public function key() { return $this->_key; } public function next() { ++$this->_key; return; } public function rewind() { $this->_key = 0; $this->_current = null; return; } public function valid() { return true; } } _splFileInfoClass = $splFileInfoClass; parent::__construct($path); $this->setRelativePath($path); return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); if ($out instanceof \Hoa\Iterator\SplFileInfo) { $out->setRelativePath($this->getRelativePath()); } } return $out; } protected function setRelativePath($path) { $old = $this->_relativePath; $this->_relativePath = $path; return $old; } public function getRelativePath() { return $this->_relativePath; } } given( $foo = new LUT\Map(['f', 'o', 'o']), $bar = new LUT\Map(['b', 'a', 'r']), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($foo, 'one'), $multiple->attachIterator($bar, 'two') ) ->when($result = iterator_to_array($multiple, false)) ->then ->array($result) ->isEqualTo([ ['one' => 'f', 'two' => 'b'], ['one' => 'o', 'two' => 'a'], ['one' => 'o', 'two' => 'r'] ]); } public function case_default_value() { $this ->given( $foobar = new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']), $baz = new LUT\Map(['b', 'a', 'z']), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($foobar, 'one', '!'), $multiple->attachIterator($baz, 'two', '?') ) ->when($result = iterator_to_array($multiple, false)) ->then ->array($result) ->isEqualTo([ ['one' => 'f', 'two' => 'b'], ['one' => 'o', 'two' => 'a'], ['one' => 'o', 'two' => 'z'], ['one' => 'b', 'two' => '?'], ['one' => 'a', 'two' => '?'], ['one' => 'r', 'two' => '?'], ]); } public function case_empty() { $this ->given($multiple = new LUT\Multiple()) ->when($result = iterator_to_array($multiple)) ->then ->array($result) ->isEmpty(); } } given( $iterator = new LUT\CallbackGenerator(function ($key) { return $key * 2; }), $limit = new LUT\Limit($iterator, 0, 5) ) ->when($result = iterator_to_array($limit)) ->then ->array($result) ->isEqualTo([ 0, 2, 4, 6, 8 ]); } } given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/Aa?type=file'), resolve('hoa://Test/Vfs/Root/Aaa?type=file'), $iterator = new LUT\Directory($root), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $key => $file) { $result[$key] = $file->getFilename(); $this ->object($file) ->isInstanceOf(LUT\Directory::class); } }) ->then ->array($result) ->isEqualTo([ 0 => 'A', 1 => 'Aa', 2 => 'Aaa' ]); } public function case_seek_and_dots() { $this ->given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/.?type=directory'), resolve('hoa://Test/Vfs/Root/..?type=directory'), resolve('hoa://Test/Vfs/Root/Skip?type=file'), resolve('hoa://Test/Vfs/Root/Gotcha?type=file'), $iterator = new LUT\Directory($root) ) ->when($result = $iterator->current()) ->then ->boolean($result->isDot()) ->isTrue() ->when( $iterator->next(), $result = $iterator->current() ) ->then ->boolean($result->isDot()) ->isTrue() ->when( $iterator->seek(3), $result = $iterator->current() ) ->then ->string($result->getFilename()) ->isEqualTo('Gotcha') ->when( $iterator->seek(2), $result = $iterator->current() ) ->then ->string($result->getFilename()) ->isEqualTo('Skip'); } public function case_recursive() { $this ->given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/Aa?type=file'), resolve('hoa://Test/Vfs/Root/Aaa?type=file'), resolve('hoa://Test/Vfs/Root/Foo?type=directory'), resolve('hoa://Test/Vfs/Root/Foo/Bar?type=directory'), resolve('hoa://Test/Vfs/Root/Foo/Bar/B?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Bar/Bb?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Bar/Bbb?type=file'), resolve('hoa://Test/Vfs/Root/Foo/C?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Cc?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Ccc?type=file'), $directory = new LUT\Recursive\Directory($root), $iterator = new LUT\Recursive\Iterator($directory), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $file) { $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'A', 'Aa', 'Aaa', 'B', 'Bb', 'Bbb', 'C', 'Cc', 'Ccc' ]); } public function case_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/a?type=file'), resolve('hoa://Test/Vfs/Root/b?type=file'), resolve('hoa://Test/Vfs/Root/c?type=file'), resolve('hoa://Test/Vfs/Root/d?type=file'), resolve('hoa://Test/Vfs/Root/e?type=file'), resolve('hoa://Test/Vfs/Root/f?type=file'), $iterator = new LUT\Directory( $root, $splFileInfo ), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } public function case_recursive_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=directory'), resolve('hoa://Test/Vfs/Root/A/a?type=file'), resolve('hoa://Test/Vfs/Root/A/b?type=file'), resolve('hoa://Test/Vfs/Root/A/c?type=file'), resolve('hoa://Test/Vfs/Root/B?type=directory'), resolve('hoa://Test/Vfs/Root/B/d?type=file'), resolve('hoa://Test/Vfs/Root/B/e?type=file'), resolve('hoa://Test/Vfs/Root/B/c?type=directory'), resolve('hoa://Test/Vfs/Root/B/c/f?type=file'), $directory = new LUT\Recursive\Directory( $root, LUT\FileSystem::CURRENT_AS_FILEINFO, $splFileInfo ), $iterator = new LUT\Recursive\Iterator($directory), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } } given($pathname = 'hoa://Test/Vfs/Foo.bar?type=file') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isFile()) ->isTrue() ->string($result->getType()) ->isEqualTo('file'); } public function case_directory() { $this ->given($pathname = 'hoa://Test/Vfs/Foo?type=directory') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isDir()) ->isTrue() ->string($result->getType()) ->isEqualTo('dir'); } public function case_path_informations() { $this ->given( $relativePath = 'hoa://Test/Vfs/A/B/', $relativePathname = 'C/Foo.bar', $pathname = $relativePath . $relativePathname ) ->when($result = new LUT\SplFileInfo($pathname . '?type=file', $relativePath)) ->then ->boolean($result->isFile()) ->isTrue() ->string($result->getBasename()) ->isEqualTo('Foo.bar?type=file') ->string($result->getExtension()) ->isEqualTo('bar?type=file') ->string($result->getRelativePath()) ->isEqualTo($relativePath) ->string($result->getRelativePathname()) ->isEqualTo($relativePathname . '?type=file') ->string($result->getPath()) ->isEqualTo('hoa://Test/Vfs/A/B/C') ->string($result->getPathname()) ->isEqualTo($pathname . '?type=file'); } public function case_times() { $this ->given( $timestamp = $this->realdom->boundinteger( $this->realdom->timestamp('yesterday'), $this->realdom->timestamp('tomorrow') ), $atime = $this->sample($timestamp), $ctime = $this->sample($timestamp), $mtime = $this->sample($timestamp), $pathname = 'hoa://Test/Vfs/Foo.bar?' . http_build_query([ 'type' => 'file', 'atime' => $atime, 'ctime' => $ctime, 'mtime' => $mtime ]) ) ->when($result = new LUT\SplFileInfo($pathname)) ->then ->integer($result->getATime()) ->isEqualTo($atime) ->integer($result->getCTime()) ->isEqualTo($ctime) ->integer($result->getMTime()) ->isEqualTo($mtime); } public function case_permissions() { $this ->given($pathname = 'hoa://Test/Vfs/Fo.bar?type=file&permissions=0744') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isTrue() ->boolean($result->isExecutable()) ->isTrue() ->given($pathname = 'hoa://Test/Vfs/Foo.bar?type=file&permissions=0644') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isTrue() ->boolean($result->isExecutable()) ->isFalse() ->given($pathname = 'hoa://Test/Vfs/Fooo.bar?type=file&permissions=0444') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isFalse() ->boolean($result->isExecutable()) ->isFalse() ->given($pathname = 'hoa://Test/Vfs/Foooo.bar?type=file&permissions=0044') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isFalse() ->boolean($result->isWritable()) ->isFalse() ->boolean($result->isExecutable()) ->isFalse(); } } given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/.?type=directory'), resolve('hoa://Test/Vfs/Root/..?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/B?type=file'), $iterator = new LUT\FileSystem($root), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $pathname => $file) { $this ->object($file) ->isInstanceOf('SplFileInfo'); $result[basename($pathname)] = $file->getFilename(); } }) ->array($result) ->isEqualTo([ 'A' => 'A', 'B' => 'B' ]); } public function case_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/a?type=file'), resolve('hoa://Test/Vfs/Root/b?type=file'), resolve('hoa://Test/Vfs/Root/c?type=file'), resolve('hoa://Test/Vfs/Root/d?type=file'), resolve('hoa://Test/Vfs/Root/e?type=file'), resolve('hoa://Test/Vfs/Root/f?type=file'), $iterator = new LUT\FileSystem( $root, LUT\FileSystem::CURRENT_AS_FILEINFO, $splFileInfo ), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } } given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function ($value) { return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]); } public function case_all_callback_parameters() { $self = $this; $this ->given( $foobar = $this->getDummyIterator(), $keys = [], $values = [], $filter = new LUT\CallbackFilter( $foobar, function ($value, $key, $iterator) use ( $self, $foobar, &$keys, &$values ) { $self ->object($iterator) ->isIdenticalTo($foobar); $keys[] = $key; $values[] = $value; return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]) ->array(array_combine($keys, $values)) ->isEqualTo(iterator_to_array($foobar)); } public function case_remove_all() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function () { return false; } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEmpty(); } public function case_remove_none() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function () { return true; } ) ) ->when( $foobarResult = iterator_to_array($foobar), $filterResult = iterator_to_array($filter) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } public function case_recursive() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function ($value) { return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ), $iterator = new LUT\Recursive\Iterator($filter) ) ->when($result = iterator_to_array($iterator, false)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 1 => 'b', 2 => 'r' ]); } public function case_recursive_remove_all() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function () { return false; } ), $iterator = new LUT\Recursive\Iterator($filter) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive_remove_none() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function () { return true; } ), $foobarIterator = new LUT\Recursive\Iterator($foobar), $filterIterator = new LUT\Recursive\Iterator($filter) ) ->when( $foobarResult = iterator_to_array($foobarIterator), $filterResult = iterator_to_array($filterIterator) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } protected function getDummyIterator() { return new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']); } protected function getDummyRecursiveIterator() { return new LUT\Recursive\Map([['f', 'o', 'o'], ['b', 'a', 'r']]); } } given( $counter1 = new LUT\Counter(0, 12, 3), $counter2 = new LUT\Counter(13, 23, 2), $append = new LUT\Append(), $append->append($counter1), $append->append($counter2) ) ->when($result = iterator_to_array($append, false)) ->then ->array($result) ->isEqualTo([ 0, 3, 6, 9, 13, 15, 17, 19, 21 ]); } public function case_singleton() { $this ->given( $counter1 = new LUT\Counter(0, 12, 3), $append = new LUT\Append(), $append->append($counter1) ) ->when($result = iterator_to_array($append)) ->then ->array($result) ->isEqualTo([ 0, 3, 6, 9 ]); } public function case_empty() { $this ->given($append = new LUT\Append()) ->when($result = iterator_to_array($append)) ->then ->array($result) ->isEmpty(); } } given( $counter = new LUT\Counter(0, 10, 1), $multiple = new LUT\Multiple(), $multiple->attachIterator($counter), $multiple->attachIterator(clone $counter), $demultiplexer = new LUT\Demultiplexer( $multiple, function ($current) { return $current[0] * $current[1]; } ) ) ->when($result = iterator_to_array($demultiplexer, false)) ->then ->array($result) ->isEqualTo([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]); } public function case_associative_keys() { $this ->given( $counter = new LUT\Counter(0, 10, 1), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($counter, 'one'), $multiple->attachIterator(clone $counter, 'two'), $demultiplexer = new LUT\Demultiplexer( $multiple, function ($current) { return $current['one'] * $current['two']; } ) ) ->when($result = iterator_to_array($demultiplexer, false)) ->then ->array($result) ->isEqualTo([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]); } } given($iterator = new LUT\Mock()) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive_mock_mock() { $this ->when($iterator = new LUT\Recursive\Mock(new LUT\Mock())) ->then ->variable($iterator->getChildren()) ->isNull() ->boolean($iterator->hasChildren()) ->isFalse(); } public function case_recursive() { $this ->given( $map = new LUT\Map(['a', 'b', 'c']), $mock = new LUT\Recursive\Mock($map), $iteratoriterator = new LUT\Recursive\Iterator($mock) ) ->when($result = iterator_to_array($map, false)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } } given($iterator = new LUT\Map(self::$_dummyArray)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(self::$_dummyArray); } public function case_empty() { $this ->given($iterator = new LUT\Map()) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'], 'k' ], $iterator = new LUT\Recursive\Map($array) ) ->when(function () use ($iterator) { foreach ($iterator as $key => $value) { if ('a' === $key) { $this ->boolean($iterator->hasChildren()) ->isTrue() ->object($children = $iterator->getChildren()) ->isInstanceOf(LUT\Recursive\Map::class) ->array(iterator_to_array($children)) ->isEqualTo(['b', 'c', 'd']); } elseif ('e' === $key) { $this ->boolean($iterator->hasChildren()) ->isTrue() ->object($children = $iterator->getChildren()) ->isInstanceOf(LUT\Recursive\Map::class) ->array(iterator_to_array($children)) ->isEqualTo(['f', 'g', 'i']); } elseif ('k' === $value) { $this ->boolean($iterator->hasChildren()) ->isFalse(); } } }); } } given( $iterator = new LUT\Map(self::$_dummyArray), $limit = new LUT\Limit($iterator, 2, 3) ) ->when($result = iterator_to_array($limit)) ->then ->array($result) ->isEqualTo([ 2 => 'o', 3 => 'b', 4 => 'a' ]); } public function case_negative_offset() { $this ->given($iterator = new LUT\Map(self::$_dummyArray)) ->exception(function () use ($iterator) { new LUT\Limit($iterator, -2, 3); }) ->isInstanceOf(\OutOfRangeException::class); } public function case_empty() { $this ->given( $iterator = new LUT\Map(self::$_dummyArray), $limit = new LUT\Limit($iterator, 0, 0) ) ->exception(function () use ($limit) { iterator_to_array($limit); }) ->isInstanceOf(\OutOfBoundsException::class); } } given($iterator = new LUT\Counter(0, 12, 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([0, 3, 6, 9]); } public function case_offset() { $this ->given($iterator = new LUT\Counter(6, 12, 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([6, 9]); } public function case_too_small() { $this ->exception(function () { new LUT\Counter(0, 0, 0); }) ->isInstanceOf(LUT\Exception::class); } public function case_too_big() { $this ->given($iterator = new LUT\Counter(0, 12, 13)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([0]); } } given( $iterator = new LUT\Map([]), $iteratoriterator = new LUT\IteratorIterator($iterator) ) ->when($result = $iteratoriterator->getInnerIterator()) ->then ->object($result) ->isIdenticalTo($iterator); } public function case_traverse() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $iteratoriterator = new LUT\IteratorIterator($iterator) ) ->when($result = iterator_to_array($iteratoriterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_recursive_leaves_only() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::LEAVES_ONLY ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo(['b', 'c', 'd', 'f', 'g', 'i']); } public function case_recursive_self_first() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::SELF_FIRST ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo([ ['b', 'c', 'd'], 'b', 'c', 'd', ['f', 'g', 'i'], 'f', 'g', 'i' ]); } public function case_recursive_child_first() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::CHILD_FIRST ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo([ 'b', 'c', 'd', ['b', 'c', 'd'], 'f', 'g', 'i', ['f', 'g', 'i'] ]); } } given( $foobar = $this->getDummyIterator(), $filter = new \Mock\Hoa\Iterator\Test\Unit\MyFilter($foobar), $this->calling($filter)->accept = function () { $value = $this->current(); return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]); } public function case_remove_all() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new \Mock\Hoa\Iterator\Test\Unit\MyFilter($foobar), $this->calling($filter)->accept = false ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEmpty(); } public function case_remove_none() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new MyFilter($foobar) ) ->when( $foobarResult = iterator_to_array($foobar), $filterResult = iterator_to_array($filter) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } protected function getDummyIterator() { return new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']); } } given( $iterator = new LUT\Map(self::$_dummyArray), $repeater = new LUT\Repeater($iterator, 3) ) ->when($result = iterator_to_array($repeater)) ->then ->array($result) ->isEqualTo( self::$_dummyArray + self::$_dummyArray + self::$_dummyArray ); } public function case_with_body() { $self = $this; $this ->given( $iterator = new LUT\Map(self::$_dummyArray), $count = 0, $repeater = new LUT\Repeater( $iterator, 3, function ($repetition) use ($self, &$count) { $this ->integer($repetition) ->isEqualTo($count + 1); ++$count; }) ) ->when($result = iterator_to_array($repeater)) ->then ->array($result) ->isEqualTo( self::$_dummyArray + self::$_dummyArray + self::$_dummyArray ) ->integer($count) ->isEqualTo(3); } } given( $map = new LUT\Map([ 'abc', 'dea', 'fgh', 'iaj', 'klm' ]), $iterator = new LUT\RegularExpression($map, '/a/') ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([ 0 => 'abc', 1 => 'dea', 3 => 'iaj' ]); } public function case_recursive() { $this ->given( $map = new LUT\Recursive\Map([ ['abc', 'dea', 'fgh'], ['iaj', 'klm'] ]), $regex = new LUT\Recursive\RegularExpression($map, '/a/'), $iterator = new LUT\Recursive\Iterator($regex) ) ->when($result = iterator_to_array($iterator, false)) ->then ->array($result) ->isEqualTo([ 0 => 'abc', 1 => 'dea', 2 => 'iaj' ]); } public function case_recursive_children_flags() { $this ->given( $map = new LUT\Recursive\Map([ ['abc', 'dea', 'fgh'], ['iaj', 'klm'] ]), $mode = LUT\Recursive\RegularExpression::ALL_MATCHES, $flag = LUT\Recursive\RegularExpression::USE_KEY, $pregFlag = LUT\Recursive\RegularExpression::ALL_MATCHES, $iterator = new LUT\Recursive\RegularExpression( $map, '/a/', $mode, $flag, $pregFlag ) ) ->when($result = $iterator->getChildren()) ->then ->object($result) ->isInstanceOf(LUT\Recursive\RegularExpression::class) ->integer($result->getMode()) ->isEqualTo($mode) ->integer($result->getFlags()) ->isEqualTo($flag) ->integer($result->getPregFlags()) ->isEqualTo($pregFlag); } } given( $iterator = new LUT\Map(['a']), $infinite = new LUT\Infinite($iterator), $limit = new LUT\Limit($infinite, 0, 100) ) ->when($result = iterator_to_array($limit, false)) ->then ->array($result) ->isEqualTo(array_fill(0, 100, 'a')) ->size ->isEqualTo(100); } } given( $dummyArray = ['f', 'o', 'o', 'b', 'a', 'r'], $iterator = new LUT\Map($dummyArray), $norewind = new LUT\NoRewind($iterator) ) ->when($result = iterator_to_array($norewind)) ->then ->array($result) ->isEqualTo($dummyArray) ->when($norewind->rewind()) ->boolean($norewind->valid()) ->isFalse() ->when($result = iterator_to_array($norewind)) ->then ->array($result) ->isEmpty(); } } given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_check_ahead() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b') ->when( $lookahead->next(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(1) ->string($current) ->isEqualTo('b') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('c') ->when( $lookahead->next(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(2) ->string($current) ->isEqualTo('c') ->boolean($hasNext) ->isFalse() ->variable($next) ->isNull(); } public function case_double_rewind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b') ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b'); } public function case_empty() { $this ->given( $iterator = new LUT\Mock(), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $valid = $lookahead->valid() ) ->then ->boolean($valid) ->isFalse(); } } given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_check_behind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse() ->variable($previous) ->isNull() ->when( $lookbehind->next(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(1) ->string($current) ->isEqualTo('b') ->boolean($hasPrevious) ->isTrue() ->string($previous) ->isEqualTo('a') ->when( $lookbehind->next(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(2) ->string($current) ->isEqualTo('c') ->boolean($hasPrevious) ->isTrue() ->string($previous) ->isEqualTo('b'); } public function case_double_rewind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse() ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse(); } public function case_empty() { $this ->given( $iterator = new LUT\Mock(), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $valid = $lookbehind->valid() ) ->then ->boolean($valid) ->isFalse(); } } given( $innerIterator = $this->getInnerIterator(), $bufferSize = 3 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->object($result->getInnerIterator()) ->isIdenticalTo($innerIterator) ->integer($result->getBufferSize()) ->isEqualTo($bufferSize) ->let($buffer = $this->invoke($result)->getBuffer()) ->object($buffer) ->isInstanceOf(\SplDoublyLinkedList::class) ->boolean($buffer->isEmpty()) ->isTrue(); } public function case_negative_buffer_size() { $this ->given( $innerIterator = $this->getInnerIterator(), $bufferSize = -42 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->integer($result->getBufferSize()) ->isEqualTo(1); } public function case_null_buffer_size() { $this ->given( $innerIterator = $this->getInnerIterator(), $bufferSize = 0 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->integer($result->getBufferSize()) ->isEqualTo(1); } public function case_fast_forward() { $this ->given($iterator = new SUT($this->getInnerIterator(), 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => 3, $iterator::BUFFER_VALUE => 'd' ], 1 => [ $iterator::BUFFER_KEY => 4, $iterator::BUFFER_VALUE => 'e' ], 2 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_fast_forward_with_too_big_buffer() { $this ->given($iterator = new SUT($this->getInnerIterator(), 10)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => 0, $iterator::BUFFER_VALUE => 'a' ], 1 => [ $iterator::BUFFER_KEY => 1, $iterator::BUFFER_VALUE => 'b' ], 2 => [ $iterator::BUFFER_KEY => 2, $iterator::BUFFER_VALUE => 'c' ], 3 => [ $iterator::BUFFER_KEY => 3, $iterator::BUFFER_VALUE => 'd' ], 4 => [ $iterator::BUFFER_KEY => 4, $iterator::BUFFER_VALUE => 'e' ], 5 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_fast_forward_with_smallest_buffer() { $this ->given($iterator = new SUT($this->getInnerIterator(), 1)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_forward_forward_forward() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 2)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isFalse() ->variable($result->key()) ->isNull() ->variable($result->current()) ->isNull(); } public function case_forward_forward_backward_backward_forward_forward_forward_step_by_step() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 3)) ->then ->variable($result->rewind()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->previous()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->previous()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 1 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ], 2 => [ $result::BUFFER_KEY => null, $result::BUFFER_VALUE => null ] ]) ->boolean($result->valid()) ->isFalse() ->variable($result->key()) ->isNull() ->variable($result->current()) ->isNull(); } public function case_backward_out_of_buffer() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 1)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->previous()) ->isNull() ->boolean($result->valid()) ->isFalse(); } public function case_rewind_rewind() { $this ->when($result = new SUT(new LUT\Map(['a', 'b']), 3)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ] ]); } protected function getInnerIterator() { return new LUT\Map(['a', 'b', 'c', 'd', 'e']); } } getMTime()) { $this->_hash = md5($this->getPathname() . $mtime); } $this->_relativePath = $relativePath; return; } public function getHash() { return $this->_hash; } public function getMTime() { try { return parent::getMTime(); } catch (\RuntimeException $e) { return -1; } } public function setRelativePath($relativePath) { $old = $this->_relativePath; $this->_relativePath = $relativePath; return $old; } public function getRelativePath() { return $this->_relativePath; } public function getRelativePathname() { if (null === $relative = $this->getRelativePath()) { return $this->getPathname(); } return substr($this->getPathname(), strlen($relative)); } } _splFileInfoClass = $splFileInfoClass; if (null === $flags) { parent::__construct($path); } else { parent::__construct($path, $flags); } return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); } return $out; } } getIterator(); } $this->_iterator = $iterator; $this->_demuxer = $demuxer; return; } public function current() { if (null !== $this->_current) { return $this->_current; } $demuxer = $this->_demuxer; return $this->_current = $demuxer($this->_iterator->current()); } public function key() { return $this->_iterator->key(); } public function next() { $this->_current = null; return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { return $this->_iterator->valid(); } } _from = $from; $this->_to = $to; $this->_step = $step; return; } public function current() { return $this->_i; } public function key() { return $this->_key; } public function next() { ++$this->_key; $this->_i += $this->_step; return; } public function rewind() { $this->_key = 0; $this->_i = $this->_from; return; } public function valid() { return $this->_i < $this->_to; } } _relativePath = self::$_handlePath; self::$_handlePath = null; } else { $this->_relativePath = $path; } $this->setSplFileInfoClass($splFileInfoClass); return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); if ($out instanceof \Hoa\Iterator\SplFileInfo) { $out->setRelativePath($this->getRelativePath()); } } return $out; } public function getChildren() { self::$_handlePath = $this->getRelativePath(); $out = parent::getChildren(); if ($out instanceof \RecursiveDirectoryIterator) { $out->setSplFileInfoClass($this->_splFileInfoClass); } return $out; } public function setSplFileInfoClass($splFileInfoClass) { $this->_splFileInfoClass = $splFileInfoClass; return; } public function getRelativePath() { return $this->_relativePath; } } getIterator(); } $this->_iterator = $iterator; return; } public function current() { return $this->_iterator->current(); } public function key() { return $this->_iterator->key(); } public function next() { return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { return $this->_iterator->valid(); } public function getChildren() { return null; } public function hasChildren() { return false; } } hasChildren() || true === parent::accept(); } public function getChildren() { return new static( true === $this->hasChildren() ? $this->getInnerIterator()->getChildren() : null, $this->getRegex(), $this->getMode(), $this->getFlags(), $this->getPregFlags() ); } public function hasChildren() { return $this->getInnerIterator()->hasChildren(); } } = $n) { throw new Exception( 'n must be greater than 0, given %d.', 0, $n ); } if ($iterator instanceof \IteratorAggregate) { $iterator = $iterator->getIterator(); } $this->_iterator = $iterator; $this->_n = $n; $this->_body = $body; return; } public function current() { return $this->_iterator->current(); } public function key() { return $this->_iterator->key(); } public function next() { return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { $valid = $this->_iterator->valid(); if (true === $valid) { return true; } if (null !== $this->_body) { $handle = &$this->_body; $handle($this->_i); } $this->rewind(); if ($this->_n <= $this->_i++) { $this->_i = 1; return false; } return true; } } _regex = $regex; $this->setMode($mode); $this->setFlags($flags); $this->setPregFlags($pregFlags); $this->replacement = null; return; } public function accept() { if (is_array(parent::current())) { return false; } $this->_key = parent::key(); $this->_current = parent::current(); $matches = []; $useKey = $this->_flags & self::USE_KEY; $subject = $useKey ? $this->_key : $this->_current; $out = false; switch ($this->_mode) { case self::MATCH: $out = 0 !== preg_match( $this->_regex, $subject, $matches, $this->_pregFlags ); break; case self::GET_MATCH: $this->_current = []; $out = 0 !== preg_match( $this->_regex, $subject, $this->_current, $this->_pregFlags ); break; case self::ALL_MATCHES: $this->_current = []; $out = 0 < preg_match_all( $this->_regex, $subject, $this->_current, $this->_pregFlags ); break; case self::SPLIT: $this->_current = preg_split( $this->_regex, $subject, null, $this->_pregFlags ); $out = is_array($this->_current) && 1 < count($this->_current); break; case self::REPLACE: $numberOfReplacement = 0; $result = preg_replace( $this->_regex, $this->replacement, $subject, -1, $numberOfReplacement ); if (null === $result || 0 === $numberOfReplacement) { $out = false; break; } if (0 !== $useKey) { $this->_key = $result; $out = true; break; } $this->_current = $result; $out = true; break; default: $out = false; break; } if (0 !== ($this->_flags & self::INVERT_MATCH)) { return false === $out; } return $out; } public function key() { return $this->_key; } public function current() { return $this->_current; } public function setMode($mode) { if ($mode < self::MATCH || $mode > self::REPLACE) { throw new \InvalidArgumentException( 'Illegal mode ' . $mode . '.' ); } $this->_mode = $mode; return; } public function setFlags($flags) { $this->_flags = $flags; return; } public function setPregFlags($pregFlags) { $this->_pregFlags = $pregFlags; return; } public function getRegex() { return $this->_regex; } public function getMode() { return $this->_mode; } public function getFlags() { return $this->_flags; } public function getPregFlags() { return $this->_pregFlags; } } _iterator = $iterator; return; } public function getInnerIterator() { return $this->_iterator; } public function current() { return $this->_current; } public function key() { return $this->_key; } public function next() { $innerIterator = $this->getInnerIterator(); $this->_valid = $innerIterator->valid(); if (false === $this->_valid) { return; } $this->_key = $innerIterator->key(); $this->_current = $innerIterator->current(); return $innerIterator->next(); } public function rewind() { $out = $this->getInnerIterator()->rewind(); $this->next(); return $out; } public function valid() { return $this->_valid; } public function hasNext() { return $this->getInnerIterator()->valid(); } public function getNext() { return $this->getInnerIterator()->current(); } public function getNextKey() { return $this->getInnerIterator()->key(); } } _iterator = $iterator; return; } public function getInnerIterator() { return $this->_iterator; } public function current() { return $this->getInnerIterator()->current(); } public function key() { return $this->getInnerIterator()->key(); } public function next() { $this->_previousKey = $this->key(); $this->_previousCurrent = $this->current(); return $this->getInnerIterator()->next(); } public function rewind() { $this->_previousKey = -1; $this->_previousCurrent = null; return $this->getInnerIterator()->rewind(); } public function valid() { return $this->getInnerIterator()->valid(); } public function hasPrevious() { return -1 !== $this->_previousKey; } public function getPrevious() { return $this->_previousCurrent; } public function getPreviousKey() { return $this->_previousKey; } } _iterator = $iterator; $this->_bufferSize = max($bufferSize, 1); $this->_buffer = new \SplDoublyLinkedList(); return; } public function getInnerIterator() { return $this->_iterator; } protected function getBuffer() { return $this->_buffer; } public function getBufferSize() { return $this->_bufferSize; } public function current() { return $this->getBuffer()->current()[self::BUFFER_VALUE]; } public function key() { return $this->getBuffer()->current()[self::BUFFER_KEY]; } public function next() { $innerIterator = $this->getInnerIterator(); $buffer = $this->getBuffer(); $buffer->next(); if (false === $buffer->valid()) { for ( $bufferSize = count($buffer), $maximumBufferSize = $this->getBufferSize(); $bufferSize >= $maximumBufferSize; --$bufferSize ) { $buffer->shift(); } $innerIterator->next(); $buffer->push([ self::BUFFER_KEY => $innerIterator->key(), self::BUFFER_VALUE => $innerIterator->current() ]); $buffer->setIteratorMode($buffer::IT_MODE_LIFO | $buffer::IT_MODE_KEEP); $buffer->rewind(); $buffer->setIteratorMode($buffer::IT_MODE_FIFO | $buffer::IT_MODE_KEEP); } return; } public function previous() { $this->getBuffer()->prev(); return; } public function rewind() { $innerIterator = $this->getInnerIterator(); $buffer = $this->getBuffer(); $innerIterator->rewind(); if (true === $buffer->isEmpty()) { $buffer->push([ self::BUFFER_KEY => $innerIterator->key(), self::BUFFER_VALUE => $innerIterator->current() ]); } $buffer->rewind(); return; } public function valid() { return $this->getBuffer()->valid() && $this->getInnerIterator()->valid(); } } given( $this->function->class_exists = $class, $this->function->interface_exists = $interface, $this->function->trait_exists = $trait ) ->when($result = SUT::entityExists('foo')) ->then ->boolean($result) ->isTrue(); } public function case_entity_exists_with_class() { return $this->_entity_exists_with_xxx(true, false, false); } public function case_entity_exists_with_interface() { return $this->_entity_exists_with_xxx(false, true, false); } public function case_entity_exists_with_trait() { return $this->_entity_exists_with_xxx(false, false, true); } public function case_entity_does_not_exists() { $this ->given( $this->function->class_exists = false, $this->function->interface_exists = false, $this->function->trait_exists = false ) ->when($result = SUT::entityExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_entity_shortest_name() { $this ->when($result = SUT::getEntityShortestName('Foo\Bar\Bar')) ->then ->string($result) ->isEqualTo('Foo\Bar'); } public function case_get_entity_shortest_name_with_already_the_shortest() { $this ->when($result = SUT::getEntityShortestName('Foo\Bar')) ->then ->string($result) ->isEqualTo('Foo\Bar'); } public function case_get_entity_shortest_name_with_no_namespace() { $this ->when($result = SUT::getEntityShortestName('Foo')) ->then ->string($result) ->isEqualTo('Foo'); } public function case_is_keyword() { $this ->given( $keywords = [ '__HALT_COMPILER', 'abstract', 'and', 'array', 'as', 'bool', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'false', 'final', 'float', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'int', 'interface', 'isset', 'list', 'mixed', 'namespace', 'new', 'null', 'numeric', 'object', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'resource', 'return', 'static', 'string', 'switch', 'throw', 'trait', 'true', 'try', 'unset', 'use', 'var', 'void', 'while', 'xor', 'yield', '__CLASS__', '__DIR__', '__FILE__', '__FUNCTION__', '__LINE__', '__METHOD__', '__NAMESPACE__', '__TRAIT__' ] ) ->when(function () use ($keywords) { foreach ($keywords as $keyword) { $this ->boolean(SUT::isKeyword($keyword)) ->isTrue(); } }); } public function case_is_identifier() { $this ->given($_identifier = $this->realdom->regex('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#')) ->when(function () use ($_identifier) { foreach ($this->sampleMany($_identifier, 1000) as $identifier) { $this ->boolean(SUT::isIdentifier($identifier)) ->isTrue(); } }); } public function case_register_shutdown_function() { $self = $this; $this ->given( $callable = function () { }, $this->function->register_shutdown_function = function ($_callable) use (&$called, $self, &$callable) { $called = true; $self ->variable($_callable) ->isEqualTo($callable); return true; } ) ->when($result = SUT::registerShutdownFunction($callable)) ->then ->boolean($result) ->isTrue(); } public function case_get_php_binary_with_constant() { $this ->given($this->constant->PHP_BINARY = '/foo/php') ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/foo/php'); } public function case_get_php_binary_with_server() { $this ->given( $this->function->defined = false, $_SERVER['_'] = '/bar/php' ) ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/bar/php'); } public function case_get_php_binary_with_bin_directory() { unset($_SERVER['_']); $this ->given( $this->function->defined = false, $this->function->file_exists = true, $this->function->realpath = '/baz/php' ) ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/baz/php'); } public function case_uuid() { $this ->given($this->function->mt_rand = 42) ->when($result = SUT::uuid()) ->then ->string($result) ->isEqualTo('002a002a-002a-402a-802a-002a002a002a'); } public function case_uuid_all_differents() { $this ->when(function () { $uuids = []; for ($i = 0; $i < 10000; ++$i) { $uuids[] = SUT::uuid(); } $this ->integer(count($uuids)) ->isEqualTo(count(array_unique($uuids))); }); } } when($result = new SUT('strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->string($result->getValidCallback()) ->isEqualTo('strtoupper') ->string($result->getHash()) ->isEqualTo('function#strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionFunction') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_class___method() { $this ->when($result = new SUT(__CLASS__ . '::strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_class_method() { $this ->when($result = new SUT(__CLASS__, 'strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_object_method() { $this ->when($result = new SUT($this, 'strtolower')) ->then ->string($result('FOO')) ->isEqualTo('foo') ->array($result->getValidCallback()) ->isEqualTo([$this, 'strtolower']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::strtolower$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtolower'); } public function case_form_object_invoke() { $this ->when($result = new SUT($this)) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([$this, '__invoke']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::__invoke$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('__invoke'); } public function case_form_closure() { $this ->given( $closure = function ($string) { return strtoupper($string); } ) ->when($result = new SUT($closure)) ->then ->string($result('foo')) ->isEqualTo('FOO') ->object($result->getValidCallback()) ->isIdenticalTo($closure) ->string($result->getHash()) ->matches('/^closure\([^:]+\)$/') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionFunction') ->string($reflection->getName()) ->isEqualTo('Hoa\Consistency\Test\Unit\{closure}'); } public function case_form_array_of_class_method() { $this ->when($result = new SUT([__CLASS__, 'strtoupper'])) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_array_of_object_method() { $this ->when($result = new SUT([$this, 'strtolower'])) ->then ->string($result('FOO')) ->isEqualTo('foo') ->array($result->getValidCallback()) ->isEqualTo([$this, 'strtolower']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::strtolower$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtolower'); } public function case_form_able_not_a_string() { $this ->exception(function () { new SUT(__CLASS__, 123); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_form_function_not_defined() { $this ->exception(function () { new SUT('__hoa_test_undefined_function__'); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_form_able_cannot_be_deduced() { $this ->given($this->function->method_exists = false) ->exception(function () { new SUT($this); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_invoke() { $this ->given( $callable = new SUT( function ($x, $y, $z) { return [$x, $y, $z]; } ) ) ->when($result = $callable(7, [4.2], 'foo')) ->then ->array($result) ->isEqualTo([7, [4.2], 'foo']); } public function case_distribute_arguments() { $this ->given( $callable = new SUT( function ($x, $y, $z) { return [$x, $y, $z]; } ) ) ->when($result = $callable->distributeArguments([7, [4.2], 'foo'])) ->then ->array($result) ->isEqualTo([7, [4.2], 'foo']); } protected function _get_valid_callback_stream_xxx($argument, $method) { $this ->given( $stream = new \Mock\Hoa\Stream\IStream\Out(), $arguments = [$argument], $xcallable = new SUT($stream) ) ->when($result = $xcallable->getValidCallback($arguments)) ->then ->array($result) ->isEqualTo([$stream, $method]); } public function case_get_valid_callback_stream_character() { return $this->_get_valid_callback_stream_xxx('f', 'writeCharacter'); } public function case_get_valid_callback_stream_string() { return $this->_get_valid_callback_stream_xxx('foo', 'writeString'); } public function case_get_valid_callback_stream_boolean() { return $this->_get_valid_callback_stream_xxx(true, 'writeBoolean'); } public function case_get_valid_callback_stream_integer() { return $this->_get_valid_callback_stream_xxx(7, 'writeInteger'); } public function case_get_valid_callback_stream_array() { return $this->_get_valid_callback_stream_xxx([4, 2], 'writeArray'); } public function case_get_valid_callback_stream_float() { return $this->_get_valid_callback_stream_xxx(4.2, 'writeFloat'); } public function case_get_valid_callback_stream_other() { return $this->_get_valid_callback_stream_xxx($this, 'writeAll'); } public static function strtoupper($string) { return strtoupper($string); } public function strtolower($string) { return strtolower($string); } public function __invoke($string) { return strtoupper($string); } public function __toString() { return 'hello'; } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectoryA = 'Source/Foo/Bar/', $baseDirectoryB = 'Source/Foo/Bar/' ) ->when( $autoloader->addNamespace($prefix, $baseDirectoryA), $result = $autoloader->addNamespace($prefix, $baseDirectoryB) ) ->then ->boolean($autoloader->hasBaseDirectory($prefix)) ->isTrue() ->array($autoloader->getBaseDirectories($prefix)) ->isEqualTo([ $baseDirectoryB, $baseDirectoryA ]); } public function case_add_namespace_append() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectoryA = 'Source/Foo/Bar/', $baseDirectoryB = 'Source/Foo/Bar/' ) ->when( $autoloader->addNamespace($prefix, $baseDirectoryA), $result = $autoloader->addNamespace($prefix, $baseDirectoryB) ) ->then ->boolean($autoloader->hasBaseDirectory($prefix)) ->isTrue() ->array($autoloader->getBaseDirectories($prefix)) ->isEqualTo([ $baseDirectoryA, $baseDirectoryB ]); } public function case_add_namespace_with_invalid_prefix() { $this ->given( $autoloader = new SUT(), $prefix = '\\\\Foo\Bar', $baseDirectory = 'Source/Foo/Bar/' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo([$baseDirectory]); } public function case_add_namespace_with_invalid_base_directory() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectory = 'Source/Foo/Bar' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo(['Source/Foo/Bar/']); } public function case_add_namespace_with_crazy_invalid_base_directory() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectory = 'Source/Foo/Bar/////' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo(['Source/Foo/Bar/']); } public function case_load() { $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/Bar/'), $this->calling($autoloader)->requireFile = function ($file) { return $file; } ) ->when($result = $autoloader->load('Foo\Bar\Baz\Qux')) ->then ->string($result) ->isEqualTo('Source/Foo/Bar/Baz/Qux.php'); } public function case_load_invalid_entity() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->load('Foo')) ->then ->variable($result) ->isNull(); } public function case_load_flex_entity() { $self = $this; $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/'), $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) { $called = true; $self ->string($entity) ->isEqualTo('Foo\Bar\Baz\Baz'); return; }, $autoloader->register() ) ->when($result = $autoloader->load('Foo\Bar\Baz')) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_load_unmapped_flex_entity() { $self = $this; $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) { $called = true; return; }, $autoloader->register() ) ->when($result = $autoloader->load('Foo\Bar\Baz')) ->then ->variable($result) ->isNull() ->variable($called) ->isNull(); } public function case_require_existing_file() { $this ->given( $autoloader = new SUT(), $this->function->file_exists = true, $constantName = 'HOA_TEST_' . uniqid(), $filename = 'hoa://Test/Vfs/Foo?type=file', file_put_contents($filename, 'when($result = $autoloader->requireFile($filename)) ->then ->boolean($result) ->isTrue() ->string(constant($constantName)) ->isEqualTo('BAR'); } public function case_require_not_existing_file() { $this ->given( $autoloader = new SUT(), $this->function->file_exists = false ) ->when($result = $autoloader->requireFile('/hoa/flatland')) ->then ->boolean($result) ->isFalse(); } public function case_has_not_base_directory() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->hasBaseDirectory('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_base_undeclared_namespace_prefix() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->getBaseDirectories('foo')) ->then ->array($result) ->isEmpty(); } public function case_dnew() { $this ->given($classname = 'Hoa\Consistency\Autoloader') ->when($result = SUT::dnew($classname)) ->then ->object($result) ->isInstanceOf($classname); } public function case_dnew_unknown_class() { $this ->given($this->function->spl_autoload_call = null) ->exception(function () { SUT::dnew('Foo'); }) ->isInstanceOf('ReflectionException'); } public function case_get_loaded_classes() { $this ->given( $declaredClasses = get_declared_classes(), $this->function->get_declared_classes = $declaredClasses ) ->when($result = SUT::getLoadedClasses()) ->then ->array($result) ->isEqualTo($declaredClasses); } public function case_register() { $self = $this; $this ->given($autoloader = new SUT()) ->when($result = $autoloader->register()) ->then ->boolean($result) ->isTrue() ->array($autoloader->getRegisteredAutoloaders()) ->isEqualTo(spl_autoload_functions()); } public function case_unregister() { $this ->given( $autoloader = new SUT(), $oldRegisteredAutoloaders = $autoloader->getRegisteredAutoloaders() ) ->when($result = $autoloader->register()) ->then ->boolean($result) ->isTrue() ->integer(count($autoloader->getRegisteredAutoloaders())) ->isEqualTo(count($oldRegisteredAutoloaders) + 1) ->when($result = $autoloader->unregister()) ->then ->boolean($result) ->isTrue() ->array($autoloader->getRegisteredAutoloaders()) ->isEqualTo($oldRegisteredAutoloaders); } } = $count) { return $entityName; } if ($parts[$count - 2] === $parts[$count - 1]) { return implode('\\', array_slice($parts, 0, -1)); } return $entityName; } public static function flexEntity($entityName) { return class_alias( $entityName, static::getEntityShortestName($entityName), false ); } public static function isKeyword($word) { static $_list = [ '__halt_compiler', 'abstract', 'and', 'array', 'as', 'bool', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'false', 'final', 'float', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'int', 'interface', 'isset', 'list', 'mixed', 'namespace', 'new', 'null', 'numeric', 'object', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'resource', 'return', 'static', 'string', 'switch', 'throw', 'trait', 'true', 'try', 'unset', 'use', 'var', 'void', 'while', 'xor', 'yield', '__class__', '__dir__', '__file__', '__function__', '__line__', '__method__', '__namespace__', '__trait__' ]; return in_array(strtolower($word), $_list); } public static function isIdentifier($id) { return 0 !== preg_match( '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#', $id ); } public static function registerShutdownFunction($callable) { return register_shutdown_function($callable); } public static function getPHPBinary() { if (defined('PHP_BINARY')) { return PHP_BINARY; } if (isset($_SERVER['_'])) { return $_SERVER['_']; } foreach (['', '.exe'] as $extension) { if (file_exists($_ = PHP_BINDIR . DS . 'php' . $extension)) { return realpath($_); } } return null; } public static function uuid() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); } } } namespace { if (70000 > PHP_VERSION_ID && false === interface_exists('Throwable', false)) { interface Throwable { public function getMessage(); public function getCode(); public function getFile(); public function getLine(); public function getTrace(); public function getPrevious(); public function getTraceAsString(); public function __toString(); } } if (50600 > PHP_VERSION_ID) { $define = function ($constantName, $constantValue, $case = false) { if (!defined($constantName)) { return define($constantName, $constantValue, $case); } return false; }; $define('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER', 8); $define('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', 16); $define('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER', 32); $define('STREAM_CRYPTO_METHOD_ANY_SERVER', 62); $define('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT', 9); $define('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', 17); $define('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', 33); $define('STREAM_CRYPTO_METHOD_ANY_CLIENT', 63); } if (!function_exists('curry')) { function curry($callable) { $arguments = func_get_args(); array_shift($arguments); $ii = array_keys($arguments, …, true); return function () use ($callable, $arguments, $ii) { return call_user_func_array( $callable, array_replace($arguments, array_combine($ii, func_get_args())) ); }; } } Hoa\Consistency\Consistency::flexEntity('Hoa\Consistency\Consistency'); } _callback = $call; return; } if (!is_string($able)) { throw new Exception( 'Bad callback form; the able part must be a string.', 0 ); } if ('' === $able) { if (is_string($call)) { if (false === strpos($call, '::')) { if (!function_exists($call)) { throw new Exception( 'Bad callback form; function %s does not exist.', 1, $call ); } $this->_callback = $call; return; } list($call, $able) = explode('::', $call); } elseif (is_object($call)) { if ($call instanceof Stream\IStream\Out) { $able = null; } elseif (method_exists($call, '__invoke')) { $able = '__invoke'; } else { throw new Exception( 'Bad callback form; an object but without a known ' . 'method.', 2 ); } } elseif (is_array($call) && isset($call[0])) { if (!isset($call[1])) { return $this->__construct($call[0]); } return $this->__construct($call[0], $call[1]); } else { throw new Exception( 'Bad callback form.', 3 ); } } $this->_callback = [$call, $able]; return; } public function __invoke() { $arguments = func_get_args(); $valid = $this->getValidCallback($arguments); return call_user_func_array($valid, $arguments); } public function distributeArguments(array $arguments) { return call_user_func_array([$this, '__invoke'], $arguments); } public function getValidCallback(array &$arguments = []) { $callback = $this->_callback; $head = null; if (isset($arguments[0])) { $head = &$arguments[0]; } if (null !== $head && is_array($callback) && null === $callback[1]) { if ($head instanceof Event\Bucket) { $head = $head->getData(); } switch ($type = gettype($head)) { case 'string': if (1 === strlen($head)) { $method = 'writeCharacter'; } else { $method = 'writeString'; } break; case 'boolean': case 'integer': case 'array': $method = 'write' . ucfirst($type); break; case 'double': $method = 'writeFloat'; break; default: $method = 'writeAll'; $head = $head . "\n"; } $callback[1] = $method; } return $callback; } public function getHash() { if (null !== $this->_hash) { return $this->_hash; } $_ = &$this->_callback; if (is_string($_)) { return $this->_hash = 'function#' . $_; } if (is_array($_)) { return $this->_hash = (is_object($_[0]) ? 'object(' . spl_object_hash($_[0]) . ')' . '#' . get_class($_[0]) : 'class#' . $_[0]) . '::' . (null !== $_[1] ? $_[1] : '???'); } return $this->_hash = 'closure(' . spl_object_hash($_) . ')'; } public function getReflection() { $arguments = func_get_args(); $valid = $this->getValidCallback($arguments); if (is_string($valid)) { return new \ReflectionFunction($valid); } if ($valid instanceof \Closure) { return new \ReflectionFunction($valid); } if (is_array($valid)) { if (is_string($valid[0])) { if (false === method_exists($valid[0], $valid[1])) { return new \ReflectionClass($valid[0]); } return new \ReflectionMethod($valid[0], $valid[1]); } $object = new \ReflectionObject($valid[0]); if (null === $valid[1]) { return $object; } return $object->getMethod($valid[1]); } } public function __toString() { return $this->getHash(); } } _namespacePrefixesToBaseDirectories[$prefix])) { $this->_namespacePrefixesToBaseDirectories[$prefix] = []; } if (true === $prepend) { array_unshift( $this->_namespacePrefixesToBaseDirectories[$prefix], $baseDirectory ); } else { array_push( $this->_namespacePrefixesToBaseDirectories[$prefix], $baseDirectory ); } return; } public function load($entity) { $entityPrefix = $entity; $hasBaseDirectory = false; while (false !== $pos = strrpos($entityPrefix, '\\')) { $currentEntityPrefix = substr($entity, 0, $pos + 1); $entityPrefix = rtrim($currentEntityPrefix, '\\'); $entitySuffix = substr($entity, $pos + 1); $entitySuffixAsPath = str_replace('\\', '/', $entitySuffix); if (false === $this->hasBaseDirectory($currentEntityPrefix)) { continue; } $hasBaseDirectory = true; foreach ($this->getBaseDirectories($currentEntityPrefix) as $baseDirectory) { $file = $baseDirectory . $entitySuffixAsPath . '.php'; if (false !== $this->requireFile($file)) { return $file; } } } if (true === $hasBaseDirectory && $entity === Consistency::getEntityShortestName($entity) && false !== $pos = strrpos($entity, '\\')) { return $this->runAutoloaderStack( $entity . '\\' . substr($entity, $pos + 1) ); } return null; } public function requireFile($filename) { if (false === file_exists($filename)) { return false; } require $filename; return true; } public function hasBaseDirectory($namespacePrefix) { return isset($this->_namespacePrefixesToBaseDirectories[$namespacePrefix]); } public function getBaseDirectories($namespacePrefix) { if (false === $this->hasBaseDirectory($namespacePrefix)) { return []; } return $this->_namespacePrefixesToBaseDirectories[$namespacePrefix]; } public static function getLoadedClasses() { return get_declared_classes(); } public function runAutoloaderStack($entity) { return spl_autoload_call($entity); } public function register($prepend = false) { return spl_autoload_register([$this, 'load'], true, $prepend); } public function unregister() { return spl_autoload_unregister([$this, 'load']); } public function getRegisteredAutoloaders() { return spl_autoload_functions(); } public static function dnew($classname, array $arguments = []) { $classname = ltrim($classname, '\\'); if (false === Consistency::entityExists($classname, false)) { spl_autoload_call($classname); } $class = new \ReflectionClass($classname); if (empty($arguments) || false === $class->hasMethod('__construct')) { return $class->newInstance(); } return $class->newInstanceArgs($arguments); } } $autoloader = new Autoloader(); $autoloader->addNamespace('Hoa', dirname(__DIR__)); $autoloader->register(); 'Content-Type', 'CONTENT_LENGTH' => 'Content-Length', 'CONTENT_MD5' => 'Content-Md5', ); foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) === 'HTTP_') { $key = substr($key, 5); if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); $headers[$key] = $value; } } elseif (isset($copy_server[$key])) { $headers[$copy_server[$key]] = $value; } } if (!isset($headers['Authorization'])) { if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; } elseif (isset($_SERVER['PHP_AUTH_USER'])) { $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; } } return $headers; } } wantTo('create article'); * $I->click('New Article'); * $I->fillField('Title', sq('Article')); * $I->fillField('Body', 'Demo article with Lorem Ipsum'); * $I->click('save'); * $I->see(sq('Article') ,'#articles') * ``` * * Populating Database: * * ``` php * haveInDatabase('users', array('login' => sq("user$i"), 'email' => sq("user$i").'@email.com'); * } * ?> * ``` * * Cest Suite tests: * * ``` php * createUser(sqs('user') . '@mailserver.com', sqs('login'), sqs('pwd')); * } * * public function checkEmail(AcceptanceTester $I) * { * $I->seeInEmailTo(sqs('user') . '@mailserver.com', sqs('login')); * } * * public function removeUser(AcceptanceTester $I) * { * $I->removeUser(sqs('user') . '@mailserver.com'); * } * } * ?> * ``` * * ### Config * * By default produces unique string with param as a prefix: * * ``` * sq('user') => 'user_876asd8as87a' * ``` * * This behavior can be configured using `prefix` config param. * * Old style sequences: * * ```yaml * Sequence: * prefix: '_' * ``` * * Using id param inside prefix: * * ```yaml * Sequence: * prefix: '{id}.' * ``` */ class Sequence extends CodeceptionModule { public static $hash = []; public static $suiteHash = []; public static $prefix = ''; protected $config = ['prefix' => '{id}_']; public function _initialize() { static::$prefix = $this->config['prefix']; } public function _after(TestInterface $t) { self::$hash = []; } public function _afterSuite() { self::$suiteHash = []; } } if (!function_exists('sq') && !function_exists('sqs')) { require_once __DIR__ . '/../Util/sq.php'; } else { throw new ModuleException('Codeception\Module\Sequence', "function 'sq' and 'sqs' already defined"); } client = new \MongoDB\Client($dsn, $options); $this->dbh = $this->client->selectDatabase($this->dbName); } catch (\MongoDB\Driver\Exception $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Connect to the Mongo server using the legacy mongo extension. */ protected function setupMongo($dsn, $options) { try { $this->client = new \MongoClient($dsn, $options); $this->dbh = $this->client->selectDB($this->dbName); } catch (\MongoConnectionException $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the MongoDB extension. */ protected function cleanupMongoDB() { try { $this->dbh->drop(); } catch (\MongoDB\Driver\Exception $e) { throw new \Exception(sprintf('Failed to drop the DB: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the legacy Mongo extension. */ protected function cleanupMongo() { try { $list = $this->dbh->listCollections(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to list collections of the DB: %s', $e->getMessage())); } foreach ($list as $collection) { try { $collection->drop(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to drop collection: %s', $e->getMessage())); } } } /** * $dsn has to contain db_name after the host. E.g. "mongodb://localhost:27017/mongo_test_db" * * @static * * @param $dsn * @param $user * @param $password * * @throws ModuleConfigException * @throws \Exception */ public function __construct($dsn, $user, $password) { $this->legacy = extension_loaded('mongodb') === false && class_exists('\\MongoClient') && strpos(\MongoClient::VERSION, 'mongofill') === false; /* defining DB name */ $this->dbName = preg_replace('/\?.*/', '', substr($dsn, strrpos($dsn, '/') + 1)); if (strlen($this->dbName) == 0) { throw new ModuleConfigException($this, 'Please specify valid $dsn with DB name after the host:port'); } /* defining host */ if (strpos($dsn, 'mongodb://') !== false) { $this->host = str_replace('mongodb://', '', preg_replace('/\?.*/', '', $dsn)); } else { $this->host = $dsn; } $this->host = rtrim(str_replace($this->dbName, '', $this->host), '/'); $options = [ 'connect' => true ]; if ($user && $password) { $options += [ 'username' => $user, 'password' => $password ]; } $this->{$this->legacy ? 'setupMongo' : 'setupMongoDB'}($dsn, $options); $this->dsn = $dsn; $this->user = $user; $this->password = $password; } /** * @static * * @param $dsn * @param $user * @param $password * * @return MongoDb */ public static function create($dsn, $user, $password) { return new MongoDb($dsn, $user, $password); } public function cleanup() { $this->{$this->legacy ? 'cleanupMongo' : 'cleanupMongoDB'}(); } /** * dump file has to be a javascript document where one can use all the mongo shell's commands * just FYI: this file can be easily created be RockMongo's export button * * @param $dumpFile */ public function load($dumpFile) { $cmd = sprintf( 'mongo %s %s%s', $this->host . '/' . $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $cmd = sprintf( "mongorestore %s --host %s --port %s -d %s %s %s", $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromTarGzMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $getDirCmd = sprintf( "tar -tf %s | awk 'BEGIN { FS = \"/\" } ; { print $1 }' | uniq", escapeshellarg($dumpFile) ); $dirCountCmd = $getDirCmd . ' | wc -l'; if (trim(shell_exec($dirCountCmd)) !== '1') { throw new ModuleException( $this, 'Archive MUST contain single directory with db dump' ); } $dirName = trim(shell_exec($getDirCmd)); $cmd = sprintf( 'tar -xzf %s && mongorestore %s --host %s --port %s -d %s %s %s && rm -r %s', escapeshellarg($dumpFile), $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), $dirName, $dirName ); shell_exec($cmd); } private function createUserPasswordCmdString() { if ($this->user && $this->password) { return sprintf( '--username %s --password %s ', $this->user, $this->password ); } return ''; } public function getDbh() { return $this->dbh; } public function setDatabase($dbName) { $this->dbh = $this->client->{$this->legacy ? 'selectDB' : 'selectDatabase'}($dbName); } public function getDbHash() { $result = $this->dbh->command(['dbHash' => 1]); if (!is_array($result)) { $result = iterator_to_array($result); } return isset($result[0]->md5) ? $result[0]->md5 : null; } /** * Determine if this driver is using the legacy extension or not. * * @return bool */ public function isLegacy() { return $this->legacy; } private function getHostPort() { $hostPort = explode(':', $this->host); if (count($hostPort) === 2) { return $hostPort; } if (count($hostPort) === 1) { return [$hostPort[0], self::DEFAULT_PORT]; } throw new ModuleException($this, '$dsn MUST be like (mongodb://):/'); } public function setQuiet($quiet) { $this->quiet = $quiet ? '--quiet' : ''; } } .tar.gz ```. * Just put it in ``` tests/_data ``` dir (by default) and specify path to it in config. * Next time after database is cleared all your data will be restored from dump. * The DB preparation should as following: * - clean database * - system collection system.users should contain the user which will be authenticated while script performs DB operations * * Connection is done by MongoDb driver, which is stored in Codeception\Lib\Driver namespace. * Check out the driver if you get problems loading dumps and cleaning databases. * * HINT: This module can be used with [Mongofill](https://github.com/mongofill/mongofill) library which is Mongo client written in PHP without extension. * * ## Status * * * Maintainer: **judgedim**, **davert** * * Stability: **beta** * * Contact: davert@codeception.com * * *Please review the code of non-stable modules and provide patches if you have issues.* * * ## Config * * * dsn *required* - MongoDb DSN with the db name specified at the end of the host after slash * * user *required* - user to access database * * password *required* - password * * dump_type *required* - type of dump. * One of 'js' (MongoDb::DUMP_TYPE_JS), 'mongodump' (MongoDb::DUMP_TYPE_MONGODUMP) or 'mongodump-tar-gz' (MongoDb::DUMP_TYPE_MONGODUMP_TAR_GZ). * default: MongoDb::DUMP_TYPE_JS). * * dump - path to database dump * * populate: true - should the dump be loaded before test suite is started. * * cleanup: true - should the dump be reloaded after each test. * Boolean or 'dirty'. If cleanup is set to 'dirty', the dump is only reloaded if any data has been written to the db during a test. This is * checked using the [dbHash](https://docs.mongodb.com/manual/reference/command/dbHash/) command. * */ class MongoDb extends CodeceptionModule implements RequiresPackage { const DUMP_TYPE_JS = 'js'; const DUMP_TYPE_MONGODUMP = 'mongodump'; const DUMP_TYPE_MONGODUMP_TAR_GZ = 'mongodump-tar-gz'; /** * @api * @var */ public $dbh; /** * @var */ protected $dumpFile; protected $isDumpFileEmpty = true; protected $dbHash; protected $config = [ 'populate' => true, 'cleanup' => true, 'dump' => null, 'dump_type' => self::DUMP_TYPE_JS, 'user' => null, 'password' => null, 'quiet' => false, ]; protected $populated = false; /** * @var \Codeception\Lib\Driver\MongoDb */ public $driver; protected $requiredFields = ['dsn']; public function _initialize() { try { $this->driver = MongoDbDriver::create( $this->config['dsn'], $this->config['user'], $this->config['password'] ); } catch (\MongoConnectionException $e) { throw new ModuleException(__CLASS__, $e->getMessage() . ' while creating Mongo connection'); } // starting with loading dump if ($this->config['populate']) { $this->cleanup(); $this->loadDump(); $this->populated = true; } } private function validateDump() { if ($this->config['dump'] && ($this->config['cleanup'] or ($this->config['populate']))) { if (!file_exists(Configuration::projectDir() . $this->config['dump'])) { throw new ModuleConfigException( __CLASS__, "File with dump doesn't exist.\n Please, check path for dump file: " . $this->config['dump'] ); } $this->dumpFile = Configuration::projectDir() . $this->config['dump']; $this->isDumpFileEmpty = false; if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $content = file_get_contents($this->dumpFile); $content = trim(preg_replace('%/\*(?:(?!\*/).)*\*/%s', "", $content)); if (!sizeof(explode("\n", $content))) { $this->isDumpFileEmpty = true; } return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { if (!is_dir($this->dumpFile)) { throw new ModuleConfigException( __CLASS__, "Dump must be a directory.\n Please, check dump: " . $this->config['dump'] ); } $this->isDumpFileEmpty = true; $dumpDir = dir($this->dumpFile); while (false !== ($entry = $dumpDir->read())) { if ($entry !== '..' && $entry !== '.') { $this->isDumpFileEmpty = false; break; } } $dumpDir->close(); return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { throw new ModuleConfigException( __CLASS__, "Tar gunzip archives are not supported for Windows systems" ); } if (!preg_match('/(\.tar\.gz|\.tgz)$/', $this->dumpFile)) { throw new ModuleConfigException( __CLASS__, "Dump file must be a valid tar gunzip archive.\n Please, check dump file: " . $this->config['dump'] ); } return; } throw new ModuleConfigException( __CLASS__, '\"dump_type\" must be one of ["' . self::DUMP_TYPE_JS . '", "' . self::DUMP_TYPE_MONGODUMP . '", "' . self::DUMP_TYPE_MONGODUMP_TAR_GZ . '"].' ); } } public function _before(TestInterface $test) { if ($this->shouldCleanup()) { $this->cleanup(); $this->loadDump(); } } public function _after(TestInterface $test) { $this->populated = false; } protected function shouldCleanup() { if ($this->populated) { return false; } return $this->config['cleanup'] === 'dirty' ? ($this->dbHash === null || $this->driver->getDbHash() !== $this->dbHash) : (bool)$this->config['cleanup']; } protected function cleanup() { $dbh = $this->driver->getDbh(); if (!$dbh) { throw new ModuleConfigException( __CLASS__, "No connection to database. Remove this module from config if you don't need database repopulation" ); } try { $this->driver->cleanup(); } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } } protected function loadDump() { $this->validateDump(); if ($this->isDumpFileEmpty) { return; } try { if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $this->driver->load($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromMongoDump($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromTarGzMongoDump($this->dumpFile); } } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } if ($this->config['cleanup'] === 'dirty') { $this->dbHash = $this->driver->getDbHash(); } } /** * Specify the database to use * * ``` php * useDatabase('db_1'); * ``` * * @param $dbName */ public function useDatabase($dbName) { $this->driver->setDatabase($dbName); } /** * Inserts data into collection * * ``` php * haveInCollection('users', array('name' => 'John', 'email' => 'john@coltrane.com')); * $user_id = $I->haveInCollection('users', array('email' => 'john@coltrane.com')); * ``` * * @param $collection * @param array $data */ public function haveInCollection($collection, array $data) { $collection = $this->driver->getDbh()->selectCollection($collection); if ($this->driver->isLegacy()) { $collection->insert($data); return $data['_id']; } $response = $collection->insertOne($data); return (string) $response->getInsertedId(); } /** * Checks if collection contains an item. * * ``` php * seeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function seeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertGreaterThan(0, $res); } /** * Checks if collection doesn't contain an item. * * ``` php * dontSeeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function dontSeeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertLessThan(1, $res); } /** * Grabs a data from collection * * ``` php * grabFromCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria * @return array */ public function grabFromCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->findOne($criteria); } /** * Grabs the documents count from a collection * * ``` php * grabCollectionCount('users'); * // or * $count = $I->grabCollectionCount('users', array('isAdmin' => true)); * ``` * * @param $collection * @param array $criteria * @return integer */ public function grabCollectionCount($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->count($criteria); } /** * Asserts that an element in a collection exists and is an Array * * ``` php * seeElementIsArray('users', array('name' => 'John Doe') , 'data.skills'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsArray($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "Array.isArray(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit\Framework\ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsArray' ); } \PHPUnit\Framework\Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Asserts that an element in a collection exists and is an Object * * ``` php * seeElementIsObject('users', array('name' => 'John Doe') , 'data'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsObject($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "! Array.isArray(this.{$elementToCheck}) && isObject(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit\Framework\ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsObject' ); } \PHPUnit\Framework\Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Count number of records in a collection * * ``` php * seeNumElementsInCollection('users', 2); * $I->seeNumElementsInCollection('users', 1, array('name' => 'miles')); * ``` * * @param $collection * @param integer $expected * @param array $criteria */ public function seeNumElementsInCollection($collection, $expected, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertSame($expected, $res); } /** * Returns list of classes and corresponding packages required for this module */ public function _requires() { return ['MongoDB\Client' => '"mongodb/mongodb": "^1.0"']; } } expectException(MyException::class, function() { * $this->doSomethingBad(); * }); * * $I->expectException(new MyException(), function() { * $this->doSomethingBad(); * }); * ``` * If you want to check message or exception code, you can pass them with exception instance: * ```php * expectException(new MyException("Don't do bad things"), function() { * $this->doSomethingBad(); * }); * ``` * * @deprecated Use expectThrowable() instead * @param \Exception|string $exception * @param callable $callback */ public function expectException($exception, $callback) { Notification::deprecate('Use expectThrowable() instead'); $this->expectThrowable($exception, $callback); } /** * Handles and checks throwables (Exceptions/Errors) called inside the callback function. * Either throwable class name or throwable instance should be provided. * * ```php * expectThrowable(MyThrowable::class, function() { * $this->doSomethingBad(); * }); * * $I->expectThrowable(new MyException(), function() { * $this->doSomethingBad(); * }); * ``` * If you want to check message or throwable code, you can pass them with throwable instance: * ```php * expectThrowable(new MyError("Don't do bad things"), function() { * $this->doSomethingBad(); * }); * ``` * * @param \Throwable|string $throwable * @param callable $callback */ public function expectThrowable($throwable, $callback) { if (is_object($throwable)) { $class = get_class($throwable); $msg = $throwable->getMessage(); $code = $throwable->getCode(); } else { $class = $throwable; $msg = null; $code = null; } try { $callback(); } catch (\Exception $t) { $this->checkThrowable($t, $class, $msg, $code); return; } catch (\Throwable $t) { $this->checkThrowable($t, $class, $msg, $code); return; } $this->fail("Expected throwable of class '$class' to be thrown, but nothing was caught"); } /** * Check if the given throwable matches the expected data, * fail (throws an exception) if it does not. * * @param \Throwable $throwable * @param string $expectedClass * @param string $expectedMsg * @param int $expectedCode */ protected function checkThrowable($throwable, $expectedClass, $expectedMsg, $expectedCode) { if (!($throwable instanceof $expectedClass)) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to be thrown, but class '%s' was caught", get_class($throwable) )); } if (null !== $expectedMsg && $throwable->getMessage() !== $expectedMsg) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to have message '$expectedMsg', but actual message was '%s'", $throwable->getMessage() )); } if (null !== $expectedCode && $throwable->getCode() !== $expectedCode) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to have code '$expectedCode', but actual code was '%s'", $throwable->getCode() )); } $this->assertTrue(true); // increment assertion counter } } testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE) { $status = "\033[41;37mFAIL\033[0m"; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED) { $status = 'Skipped'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) { $status = 'Incomplete'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY) { $status = 'Useless'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR) { $status = 'ERROR'; } else { $status = 'Ok'; } if (strlen($name) > 75) { $name = substr($name, 0, 70); } $line = $name . str_repeat('.', 75 - strlen($name)); $line .= $status; $this->write($line . "\n"); } protected function endRun() : void { } public function printResult(\PHPUnit\Framework\TestResult $result) { $this->write("\nCodeception Results\n"); $this->write(sprintf( "Successful: %d. Failed: %d. Incomplete: %d. Skipped: %d. Useless: %d", $this->successful, $this->failed, $this->incomplete, $this->skipped, $this->risky ) . "\n"); } public function write(string $buffer) : void { parent::write($buffer); } }

    {toggle} {name} {time}s

    {steps} {failure} {png} {html}
    Test results
    {header} {scenarios}

    Summary

    Successful scenarios: {successfulScenarios}
    Failed scenarios: {failedScenarios}
    Skipped scenarios: {skippedScenarios}
    Incomplete scenarios: {incompleteScenarios}
    Useless scenarios: {uselessScenarios}

    {suite} Tests

    + {metaStep}

    {steps}

    {name} {status} ({time}s)

        {action} {fail} templatePath = sprintf( '%s%stemplate%s', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ); } /** * Handler for 'start class' event. * * @param string $name */ protected function startClass(string $name):void { } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { $steps = []; $success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($test instanceof ScenarioDriven) { $steps = $test->getScenario()->getSteps(); } $this->timeTaken += $time; switch ($this->testStatus) { case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE: $scenarioStatus = 'scenarioFailed'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED: $scenarioStatus = 'scenarioSkipped'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE: $scenarioStatus = 'scenarioIncomplete'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR: $scenarioStatus = 'scenarioFailed'; break; default: $scenarioStatus = 'scenarioSuccess'; } $stepsBuffer = ''; $subStepsRendered = []; foreach ($steps as $step) { if ($step->getMetaStep()) { $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction(); $subStepsRendered[$key][] = $this->renderStep($step); } } foreach ($steps as $step) { if ($step->getMetaStep()) { $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction(); if (! empty($subStepsRendered[$key])) { $subStepsBuffer = implode('', $subStepsRendered[$key]); unset($subStepsRendered[$key]); $stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer); } } else { $stepsBuffer .= $this->renderStep($step); } } $scenarioTemplate = new \Text_Template( $this->templatePath . 'scenario.html' ); $failures = ''; $name = Descriptor::getTestSignatureUnique($test); if (isset($this->failures[$name])) { $failTemplate = new \Text_Template( $this->templatePath . 'fail.html' ); foreach ($this->failures[$name] as $failure) { $failTemplate->setVar(['fail' => nl2br($failure)]); $failures .= $failTemplate->render() . PHP_EOL; } $this->failures[$name] = []; } $png = ''; $html = ''; if ($test instanceof TestInterface) { $reports = $test->getMetadata()->getReports(); if (isset($reports['png'])) { $localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir()); $png = "
    failure screenshot
    "; } if (isset($reports['html'])) { $localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir()); $html = "See HTML snapshot of a failed page"; } } $toggle = $stepsBuffer ? '+' : ''; $testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test))); $testString = preg_replace('~^([\s\w\\\]+):\s~', '$1 » ', $testString); $scenarioTemplate->setVar( [ 'id' => ++$this->id, 'name' => $testString, 'scenarioStatus' => $scenarioStatus, 'steps' => $stepsBuffer, 'toggle' => $toggle, 'failure' => $failures, 'png' => $png, 'html' => $html, 'time' => round($time, 2) ] ); $this->scenarios .= $scenarioTemplate->render(); } public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $suiteTemplate = new \Text_Template( $this->templatePath . 'suite.html' ); if (!$suite->getName()) { return; } $suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]); $this->scenarios .= $suiteTemplate->render(); } /** * Handler for 'end run' event. */ protected function endRun():void { $scenarioHeaderTemplate = new \Text_Template( $this->templatePath . 'scenario_header.html' ); $status = !$this->failed ? 'OK' : 'FAILED'; $scenarioHeaderTemplate->setVar( [ 'name' => 'Codeception Results', 'status' => $status, 'time' => round($this->timeTaken, 1) ] ); $header = $scenarioHeaderTemplate->render(); $scenariosTemplate = new \Text_Template( $this->templatePath . 'scenarios.html' ); $scenariosTemplate->setVar( [ 'header' => $header, 'scenarios' => $this->scenarios, 'successfulScenarios' => $this->successful, 'failedScenarios' => $this->failed, 'skippedScenarios' => $this->skipped, 'incompleteScenarios' => $this->incomplete, 'uselessScenarios' => $this->risky, ] ); $this->write($scenariosTemplate->render()); } /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param \Exception $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e); parent::addError($test, $e, $time); } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e); parent::addFailure($test, $e, $time); } /** * Starts test * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test):void { $name = Descriptor::getTestSignatureUnique($test); if (isset($this->failures[$name])) { // test failed in before hook return; } // start test and mark initialize as passed parent::startTest($test); } /** * @param $step * @return string */ protected function renderStep(Step $step) { $stepTemplate = new \Text_Template($this->templatePath . 'step.html'); $stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']); return $stepTemplate->render(); } /** * @param $metaStep * @param $substepsBuffer * @return string */ protected function renderSubsteps(Meta $metaStep, $substepsBuffer) { $metaTemplate = new \Text_Template($this->templatePath . 'substeps.html'); $metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]); return $metaTemplate->render(); } private function cleanMessage($exception) { $msg = $exception->getMessage(); if ($exception instanceof \PHPUnit\Framework\ExpectationFailedException && $exception->getComparisonFailure()) { $msg .= $exception->getComparisonFailure()->getDiff(); } $msg = str_replace(['','','',''], ['','','',''], $msg); return htmlentities($msg); } } OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never'); $this->dispatcher = $dispatcher; } protected function printDefect(\PHPUnit\Framework\TestFailure $defect, int $count): void { $this->write("\n---------\n"); $this->dispatch( $this->dispatcher, Events::TEST_FAIL_PRINT, new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count) ); } /** * @param \PHPUnit\Framework\TestFailure $defect */ protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect): void { $this->write($defect->getExceptionAsString()); $this->writeNewLine(); $stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException()); foreach ($stackTrace as $i => $frame) { if (!isset($frame['file'])) { continue; } $this->write( sprintf( "#%d %s(%s)", $i + 1, $frame['file'], isset($frame['line']) ? $frame['line'] : '?' ) ); $this->writeNewLine(); } } public function startTest(\PHPUnit\Framework\Test $test) : void { if ($test instanceof Unit) { parent::startTest($test); } } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { if ($test instanceof \PHPUnit\Framework\TestCase or $test instanceof \Codeception\Test\Test) { $this->numAssertions += $test->getNumAssertions(); } $this->lastTestFailed = false; } public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->lastTestFailed = true; } public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { $this->lastTestFailed = true; } public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } } dispatcher = $dispatcher; } /** * Risky test. * * @param PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire('test.useless', new FailEvent($test, $time, $e)); } public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e)); } public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_ERROR, new FailEvent($test, $time, $e)); } // This method was added in PHPUnit 6 public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_WARNING, new FailEvent($test, $time, $e)); } public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_INCOMPLETE, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_SKIPPED, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $this->dispatch($this->dispatcher, 'suite.start', new SuiteEvent($suite)); } public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $this->dispatch($this->dispatcher, 'suite.end', new SuiteEvent($suite)); } public function startTest(\PHPUnit\Framework\Test $test) : void { $this->dispatch($this->dispatcher, Events::TEST_START, new TestEvent($test)); if (!$test instanceof TestInterface) { return; } if ($test->getMetadata()->isBlocked()) { return; } try { $this->startedTests[] = spl_object_hash($test); $this->fire(Events::TEST_BEFORE, new TestEvent($test)); } catch (\PHPUnit\Framework\IncompleteTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\PHPUnit\Framework\SkippedTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\Throwable $e) { $test->getTestResultObject()->addError($test, $e, 0); } } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { $hash = spl_object_hash($test); if (!in_array($hash, $this->unsuccessfulTests)) { $this->fire(Events::TEST_SUCCESS, new TestEvent($test, $time)); } if (in_array($hash, $this->startedTests)) { $this->fire(Events::TEST_AFTER, new TestEvent($test, $time)); } $this->dispatch($this->dispatcher, Events::TEST_END, new TestEvent($test, $time)); } protected function fire($event, TestEvent $eventType) { $test = $eventType->getTest(); if ($test instanceof TestInterface) { foreach ($test->getMetadata()->getGroups() as $group) { $this->dispatch($this->dispatcher, $event . '.' . $group, $eventType); } } $this->dispatch($this->dispatcher, $event, $eventType); } } dispatch($eventObject, $eventType); } else { //Symfony 4.2 or lower $dispatcher->dispatch($eventType, $eventObject); } } } string) { throw new \PHPUnit\Framework\ExpectationFailedException( "Element $selector was found", $comparisonFailure ); } $output = "There was $selector element"; $output .= $this->uriMessage("on page"); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } public function toString() : string { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } expected = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) : bool { $jsonResponseArray = new JsonArray($other); if (!is_array($jsonResponseArray->toArray())) { throw new \PHPUnit\Framework\AssertionFailedError('JSON response is not an array: ' . $other); } if ($jsonResponseArray->containsArray($this->expected)) { return true; } $comparator = new ArrayComparator(); $comparator->setFactory(new Factory); try { $comparator->assertEquals($this->expected, $jsonResponseArray->toArray()); } catch (ComparisonFailure $failure) { throw new \PHPUnit\Framework\ExpectationFailedException( "Response JSON does not contain the provided JSON\n", $failure ); } return false; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { //unused return ''; } protected function failureDescription($other) : string { //unused return ''; } } string = $this->normalizeText((string)$string); $this->uri = $uri; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) : bool { $other = $this->normalizeText($other); return mb_stripos($other, $this->string, null, 'UTF-8') !== false; } /** * @param $text * @return string */ private function normalizeText($text) { $text = strtr($text, "\r\n", " "); return trim(preg_replace('/\\s{2,}/', ' ', $text)); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { return sprintf( 'contains "%s"', $this->string ); } protected function failureDescription($pageContent) : string { $message = $this->uriMessage('on page'); $message->append("\n--> "); $message->append(mb_substr($pageContent, 0, 300, 'utf-8')); if (mb_strlen($pageContent, 'utf-8') > 300) { $debugMessage = new Message( "[Content too long to display. See complete response in '" . codecept_output_dir() . "' directory]" ); $message->append("\n")->append($debugMessage); } $message->append("\n--> "); return $message->getMessage() . $this->toString(); } protected function uriMessage($onPage = "") { if (!$this->uri) { return new Message(''); } $message = new Message($this->uri); $message->prepend(" $onPage "); return $message; } } string) { throw new \PHPUnit\Framework\ExpectationFailedException( "Element '$selector' was found", $comparisonFailure ); } /** @var $nodes DomCrawler * */ $output = "There was '$selector' element"; $output .= $this->uriMessage('on page'); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } public function toString() : string { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } jsonType = $jsonType; $this->match = $match; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $jsonArray Value or object to evaluate. * * @return bool */ protected function matches($jsonArray) : bool { if ($jsonArray instanceof JsonArray) { $jsonArray = $jsonArray->toArray(); } $matched = (new JsonTypeUtil($jsonArray))->matches($this->jsonType); if ($this->match) { if ($matched !== true) { throw new \PHPUnit\Framework\ExpectationFailedException($matched); } } else { if ($matched === true) { throw new \PHPUnit\Framework\ExpectationFailedException('Unexpectedly response matched: ' . json_encode($jsonArray)); } } return true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { //unused return ''; } protected function failureDescription($other) : string { //unused return ''; } } count()) { return false; } if ($this->string === '') { return true; } foreach ($nodes as $node) { if (parent::matches($node->nodeValue)) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null):void { /** @var $nodes DomCrawler * */ if (!$nodes->count()) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by '$selector'"; $output .= $this->uriMessage('on page'); $output .= " "; if ($nodes->count() < 10) { $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with($nodes->count())->getMessage(); } $output .= "\ncontains text '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($other) : string { $desc = ''; foreach ($other as $o) { $desc .= parent::failureDescription($o->textContent); } return $desc; } protected function nodesList(DomCrawler $nodes, $contains = null) { $output = ""; foreach ($nodes as $node) { if ($contains && strpos($node->nodeValue, $contains) === false) { continue; } $output .= "\n+ " . $node->C14N(); } return $output; } } string === '') { return true; } foreach ($nodes as $node) { /** @var $node \WebDriverElement * */ if (!$node->isDisplayed()) { continue; } if (parent::matches(htmlspecialchars_decode($node->getText()))) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void { if (!count($nodes)) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by " . Locator::humanReadableString($selector); $output .= $this->uriMessage('on page'); if (count($nodes) < 5) { $output .= "\nElements: "; $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with(count($nodes)); } $output .= "\ncontains text '" . $this->string . "'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($nodes) : string { $desc = ''; foreach ($nodes as $node) { $desc .= parent::failureDescription($node->getText()); } return $desc; } protected function nodesList($nodes, $contains = null) { $output = ""; foreach ($nodes as $node) { if ($contains && strpos($node->getText(), $contains) === false) { continue; } /** @var $node \WebDriverElement * */ $message = new Message("\n+ <%s> %s"); $output .= $message->with($node->getTagName(), $node->getText()); } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use PHPUnit\Framework\Error\Deprecated; use PHPUnit\Framework\Error\Notice; use PHPUnit\Framework\Error\Warning; use PHPUnit\Framework\Exception; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestResult; use PHPUnit\Framework\TestSuite; use PHPUnit\Runner\AfterLastTestHook; use PHPUnit\Runner\BaseTestRunner; use PHPUnit\Runner\BeforeFirstTestHook; use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; use PHPUnit\Runner\Filter\Factory; use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; use PHPUnit\Runner\Filter\NameFilterIterator; use PHPUnit\Runner\Hook; use PHPUnit\Runner\NullTestResultCache; use PHPUnit\Runner\ResultCacheExtension; use PHPUnit\Runner\StandardTestSuiteLoader; use PHPUnit\Runner\TestHook; use PHPUnit\Runner\TestListenerAdapter; use PHPUnit\Runner\TestResultCache; use PHPUnit\Runner\TestSuiteLoader; use PHPUnit\Runner\TestSuiteSorter; use PHPUnit\Runner\Version; use PHPUnit\TextUI\ResultPrinter; use PHPUnit\Util\Configuration; use PHPUnit\Util\Filesystem; use PHPUnit\Util\Log\JUnit; use PHPUnit\Util\Log\TeamCity; use PHPUnit\Util\Printer; use PHPUnit\Util\TestDox\CliTestDoxPrinter; use PHPUnit\Util\TestDox\HtmlResultPrinter; use PHPUnit\Util\TestDox\TextResultPrinter; use PHPUnit\Util\TestDox\XmlResultPrinter; use PHPUnit\Util\XdebugFilterScriptGenerator; use ReflectionClass; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; use SebastianBergmann\CodeCoverage\Report\Text as TextReport; use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Environment\Runtime; use SebastianBergmann\Invoker\Invoker; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class TestRunner extends BaseTestRunner { public const SUCCESS_EXIT = 0; public const FAILURE_EXIT = 1; public const EXCEPTION_EXIT = 2; /** * @var bool */ protected static $versionStringPrinted = false; /** * @var CodeCoverageFilter */ protected $codeCoverageFilter; /** * @var TestSuiteLoader */ protected $loader; /** * @var ResultPrinter */ protected $printer; /** * @var Runtime */ private $runtime; /** * @var bool */ private $messagePrinted = false; /** * @var Hook[] */ private $extensions = []; /** * @param ReflectionClass|Test $test * @param bool $exit * * @throws \RuntimeException * @throws \InvalidArgumentException * @throws Exception * @throws \ReflectionException */ public static function run($test, array $arguments = [], $exit = true): TestResult { if ($test instanceof ReflectionClass) { $test = new TestSuite($test); } if ($test instanceof Test) { $aTestRunner = new self; return $aTestRunner->doRun( $test, $arguments, $exit ); } throw new Exception('No test case or test suite found.'); } public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) { if ($filter === null) { $filter = new CodeCoverageFilter; } $this->codeCoverageFilter = $filter; $this->loader = $loader; $this->runtime = new Runtime; } /** * @throws \PHPUnit\Runner\Exception * @throws Exception * @throws \InvalidArgumentException * @throws \RuntimeException * @throws \ReflectionException */ public function doRun(Test $suite, array $arguments = [], bool $exit = true): TestResult { if (isset($arguments['configuration'])) { $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; } $this->handleConfiguration($arguments); if (\is_int($arguments['columns']) && $arguments['columns'] < 16) { $arguments['columns'] = 16; $tooFewColumnsRequested = true; } if (isset($arguments['bootstrap'])) { $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } if ($suite instanceof TestCase || $suite instanceof TestSuite) { if ($arguments['backupGlobals'] === true) { $suite->setBackupGlobals(true); } if ($arguments['backupStaticAttributes'] === true) { $suite->setBackupStaticAttributes(true); } if ($arguments['beStrictAboutChangesToGlobalState'] === true) { $suite->setBeStrictAboutChangesToGlobalState(true); } } if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { \mt_srand($arguments['randomOrderSeed']); } if ($arguments['cacheResult']) { if (isset($arguments['cacheResultFile'])) { $cache = new TestResultCache($arguments['cacheResultFile']); } else { $cache = new TestResultCache; } $this->addExtension(new ResultCacheExtension($cache)); } if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { $cache = $cache ?? new NullTestResultCache; $cache->load(); $sorter = new TestSuiteSorter($cache); $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); unset($sorter); } if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) { $_suite = new TestSuite; /* @noinspection PhpUnusedLocalVariableInspection */ foreach (\range(1, $arguments['repeat']) as $step) { $_suite->addTest($suite); } $suite = $_suite; unset($_suite); } $result = $this->createTestResult(); $listener = new TestListenerAdapter; $listenerNeeded = false; foreach ($this->extensions as $extension) { if ($extension instanceof TestHook) { $listener->add($extension); $listenerNeeded = true; } } if ($listenerNeeded) { $result->addListener($listener); } unset($listener, $listenerNeeded); if (!$arguments['convertErrorsToExceptions']) { $result->convertErrorsToExceptions(false); } if (!$arguments['convertDeprecationsToExceptions']) { Deprecated::$enabled = false; } if (!$arguments['convertNoticesToExceptions']) { Notice::$enabled = false; } if (!$arguments['convertWarningsToExceptions']) { Warning::$enabled = false; } if ($arguments['stopOnError']) { $result->stopOnError(true); } if ($arguments['stopOnFailure']) { $result->stopOnFailure(true); } if ($arguments['stopOnWarning']) { $result->stopOnWarning(true); } if ($arguments['stopOnIncomplete']) { $result->stopOnIncomplete(true); } if ($arguments['stopOnRisky']) { $result->stopOnRisky(true); } if ($arguments['stopOnSkipped']) { $result->stopOnSkipped(true); } if ($arguments['stopOnDefect']) { $result->stopOnDefect(true); } if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); } if ($this->printer === null) { if (isset($arguments['printer']) && $arguments['printer'] instanceof Printer) { $this->printer = $arguments['printer']; } else { $printerClass = ResultPrinter::class; if (isset($arguments['printer']) && \is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) { $class = new ReflectionClass($arguments['printer']); if ($class->isSubclassOf(ResultPrinter::class)) { $printerClass = $arguments['printer']; } } $this->printer = new $printerClass( (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList'] ); if (isset($originalExecutionOrder) && ($this->printer instanceof CliTestDoxPrinter)) { /* @var CliTestDoxPrinter */ $this->printer->setOriginalExecutionOrder($originalExecutionOrder); } } } $this->printer->write( Version::getVersionString() . "\n" ); self::$versionStringPrinted = true; if ($arguments['verbose']) { $this->writeMessage('Runtime', $this->runtime->getNameWithVersionAndCodeCoverageDriver()); if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { $this->writeMessage( 'Random seed', (string) $arguments['randomOrderSeed'] ); } if (isset($arguments['configuration'])) { $this->writeMessage( 'Configuration', $arguments['configuration']->getFilename() ); } foreach ($arguments['loadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } foreach ($arguments['notLoadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } } if (isset($tooFewColumnsRequested)) { $this->writeMessage('Error', 'Less than 16 columns requested, number of columns set to 16'); } if ($this->runtime->discardsComments()) { $this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work'); } if (isset($arguments['configuration']) && $arguments['configuration']->hasValidationErrors()) { $this->write( "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n" ); foreach ($arguments['configuration']->getValidationErrors() as $line => $errors) { $this->write(\sprintf("\n Line %d:\n", $line)); foreach ($errors as $msg) { $this->write(\sprintf(" - %s\n", $msg)); } } $this->write("\n Test results may not be as expected.\n\n"); } foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $result->addListener($this->printer); $codeCoverageReports = 0; if (!isset($arguments['noLogging'])) { if (isset($arguments['testdoxHTMLFile'])) { $result->addListener( new HtmlResultPrinter( $arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxTextFile'])) { $result->addListener( new TextResultPrinter( $arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxXMLFile'])) { $result->addListener( new XmlResultPrinter( $arguments['testdoxXMLFile'] ) ); } if (isset($arguments['teamcityLogfile'])) { $result->addListener( new TeamCity($arguments['teamcityLogfile']) ); } if (isset($arguments['junitLogfile'])) { $result->addListener( new JUnit( $arguments['junitLogfile'], $arguments['reportUselessTests'] ) ); } if (isset($arguments['coverageClover'])) { $codeCoverageReports++; } if (isset($arguments['coverageCrap4J'])) { $codeCoverageReports++; } if (isset($arguments['coverageHtml'])) { $codeCoverageReports++; } if (isset($arguments['coveragePHP'])) { $codeCoverageReports++; } if (isset($arguments['coverageText'])) { $codeCoverageReports++; } if (isset($arguments['coverageXml'])) { $codeCoverageReports++; } } if (isset($arguments['noCoverage'])) { $codeCoverageReports = 0; } if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) { $this->writeMessage('Error', 'No code coverage driver is available'); $codeCoverageReports = 0; } if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { $whitelistFromConfigurationFile = false; $whitelistFromOption = false; if (isset($arguments['whitelist'])) { $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']); $whitelistFromOption = true; } if (isset($arguments['configuration'])) { $filterConfiguration = $arguments['configuration']->getFilterConfiguration(); if (!empty($filterConfiguration['whitelist'])) { $whitelistFromConfigurationFile = true; } if (!empty($filterConfiguration['whitelist'])) { foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromWhitelist($file); } } } } if ($codeCoverageReports > 0) { $codeCoverage = new CodeCoverage( null, $this->codeCoverageFilter ); $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist( [Comparator::class] ); $codeCoverage->setCheckForUnintentionallyCoveredCode( $arguments['strictCoverage'] ); $codeCoverage->setCheckForMissingCoversAnnotation( $arguments['strictCoverage'] ); if (isset($arguments['forceCoversAnnotation'])) { $codeCoverage->setForceCoversAnnotation( $arguments['forceCoversAnnotation'] ); } if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $codeCoverage->setIgnoreDeprecatedCode( $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ); } if (isset($arguments['disableCodeCoverageIgnore'])) { $codeCoverage->setDisableIgnoredLines(true); } if (!empty($filterConfiguration['whitelist'])) { $codeCoverage->setAddUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist'] ); $codeCoverage->setProcessUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist'] ); } if (!$this->codeCoverageFilter->hasWhitelist()) { if (!$whitelistFromConfigurationFile && !$whitelistFromOption) { $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.'); } else { $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.'); } $codeCoverageReports = 0; unset($codeCoverage); } } if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) { $this->write("\n"); $script = (new XdebugFilterScriptGenerator)->generate($filterConfiguration['whitelist']); if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(\dirname($arguments['xdebugFilterFile']))) { $this->write(\sprintf('Cannot write Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::EXCEPTION_EXIT); } \file_put_contents($arguments['xdebugFilterFile'], $script); $this->write(\sprintf('Wrote Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::SUCCESS_EXIT); } $this->printer->write("\n"); if (isset($codeCoverage)) { $result->setCodeCoverage($codeCoverage); if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) { $codeCoverage->setCacheTokens($arguments['cacheTokens']); } } $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); if ($arguments['enforceTimeLimit'] === true) { if (!\class_exists(Invoker::class)) { $this->writeMessage('Error', 'Package phpunit/php-invoker is required for enforcing time limits'); } if (!\extension_loaded('pcntl') || \strpos(\ini_get('disable_functions'), 'pcntl') !== false) { $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); } } $result->enforceTimeLimit($arguments['enforceTimeLimit']); $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); if ($suite instanceof TestSuite) { $this->processSuiteFilters($suite, $arguments); $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } foreach ($this->extensions as $extension) { if ($extension instanceof BeforeFirstTestHook) { $extension->executeBeforeFirstTest(); } } $suite->run($result); foreach ($this->extensions as $extension) { if ($extension instanceof AfterLastTestHook) { $extension->executeAfterLastTest(); } } $result->flushListeners(); if ($this->printer instanceof ResultPrinter) { $this->printer->printResult($result); } if (isset($codeCoverage)) { if (isset($arguments['coverageClover'])) { $this->printer->write( "\nGenerating code coverage report in Clover XML format ..." ); try { $writer = new CloverReport; $writer->process($codeCoverage, $arguments['coverageClover']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageCrap4J'])) { $this->printer->write( "\nGenerating Crap4J report XML file ..." ); try { $writer = new Crap4jReport($arguments['crap4jThreshold']); $writer->process($codeCoverage, $arguments['coverageCrap4J']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageHtml'])) { $this->printer->write( "\nGenerating code coverage report in HTML format ..." ); try { $writer = new HtmlReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], \sprintf( ' and PHPUnit %s', Version::id() ) ); $writer->process($codeCoverage, $arguments['coverageHtml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coveragePHP'])) { $this->printer->write( "\nGenerating code coverage report in PHP format ..." ); try { $writer = new PhpReport; $writer->process($codeCoverage, $arguments['coveragePHP']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageText'])) { if ($arguments['coverageText'] == 'php://stdout') { $outputStream = $this->printer; $colors = $arguments['colors'] && $arguments['colors'] != ResultPrinter::COLOR_NEVER; } else { $outputStream = new Printer($arguments['coverageText']); $colors = false; } $processor = new TextReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary'] ); $outputStream->write( $processor->process($codeCoverage, $colors) ); } if (isset($arguments['coverageXml'])) { $this->printer->write( "\nGenerating code coverage report in PHPUnit XML format ..." ); try { $writer = new XmlReport(Version::id()); $writer->process($codeCoverage, $arguments['coverageXml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } } if ($exit) { if ($result->wasSuccessful()) { if ($arguments['failOnRisky'] && !$result->allHarmless()) { exit(self::FAILURE_EXIT); } if ($arguments['failOnWarning'] && $result->warningCount() > 0) { exit(self::FAILURE_EXIT); } exit(self::SUCCESS_EXIT); } if ($result->errorCount() > 0) { exit(self::EXCEPTION_EXIT); } if ($result->failureCount() > 0) { exit(self::FAILURE_EXIT); } } return $result; } public function setPrinter(ResultPrinter $resultPrinter): void { $this->printer = $resultPrinter; } /** * Returns the loader to be used. */ public function getLoader(): TestSuiteLoader { if ($this->loader === null) { $this->loader = new StandardTestSuiteLoader; } return $this->loader; } public function addExtension(TestHook $extension): void { $this->extensions[] = $extension; } protected function createTestResult(): TestResult { return new TestResult; } /** * Override to define how to handle a failed loading of * a test suite. */ protected function runFailed(string $message): void { $this->write($message . \PHP_EOL); exit(self::FAILURE_EXIT); } protected function write(string $buffer): void { if (\PHP_SAPI != 'cli' && \PHP_SAPI != 'phpdbg') { $buffer = \htmlspecialchars($buffer); } if ($this->printer !== null) { $this->printer->write($buffer); } else { print $buffer; } } /** * @throws Exception */ protected function handleConfiguration(array &$arguments): void { if (isset($arguments['configuration']) && !$arguments['configuration'] instanceof Configuration) { $arguments['configuration'] = Configuration::getInstance( $arguments['configuration'] ); } $arguments['debug'] = $arguments['debug'] ?? false; $arguments['filter'] = $arguments['filter'] ?? false; $arguments['listeners'] = $arguments['listeners'] ?? []; if (isset($arguments['configuration'])) { $arguments['configuration']->handlePHPConfiguration(); $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration(); if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) { $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals']; } if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) { $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes']; } if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) { $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState']; } if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) { $arguments['bootstrap'] = $phpunitConfiguration['bootstrap']; } if (isset($phpunitConfiguration['cacheResult']) && !isset($arguments['cacheResult'])) { $arguments['cacheResult'] = $phpunitConfiguration['cacheResult']; } if (isset($phpunitConfiguration['cacheResultFile']) && !isset($arguments['cacheResultFile'])) { $arguments['cacheResultFile'] = $phpunitConfiguration['cacheResultFile']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) { $arguments['colors'] = $phpunitConfiguration['colors']; } if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) { $arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions']; } if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) { $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions']; } if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) { $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions']; } if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) { $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions']; } if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) { $arguments['processIsolation'] = $phpunitConfiguration['processIsolation']; } if (isset($phpunitConfiguration['stopOnDefect']) && !isset($arguments['stopOnDefect'])) { $arguments['stopOnDefect'] = $phpunitConfiguration['stopOnDefect']; } if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) { $arguments['stopOnError'] = $phpunitConfiguration['stopOnError']; } if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) { $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure']; } if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) { $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning']; } if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) { $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete']; } if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) { $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky']; } if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) { $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped']; } if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) { $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning']; } if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) { $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky']; } if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) { $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests']; } if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) { $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests']; } if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) { $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests']; } if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) { $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests']; } if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) { $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage']; } if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']; } if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) { $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput']; } if (isset($phpunitConfiguration['defaultTimeLimit']) && !isset($arguments['defaultTimeLimit'])) { $arguments['defaultTimeLimit'] = $phpunitConfiguration['defaultTimeLimit']; } if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) { $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit']; } if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) { $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests']; } if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) { $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']; } if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) { $arguments['verbose'] = $phpunitConfiguration['verbose']; } if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) { $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList']; } if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) { $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation']; } if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) { $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore']; } if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) { $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']; } if (isset($phpunitConfiguration['executionOrder']) && !isset($arguments['executionOrder'])) { $arguments['executionOrder'] = $phpunitConfiguration['executionOrder']; } if (isset($phpunitConfiguration['executionOrderDefects']) && !isset($arguments['executionOrderDefects'])) { $arguments['executionOrderDefects'] = $phpunitConfiguration['executionOrderDefects']; } if (isset($phpunitConfiguration['resolveDependencies']) && !isset($arguments['resolveDependencies'])) { $arguments['resolveDependencies'] = $phpunitConfiguration['resolveDependencies']; } $groupCliArgs = []; if (!empty($arguments['groups'])) { $groupCliArgs = $arguments['groups']; } $groupConfiguration = $arguments['configuration']->getGroupConfiguration(); if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) { $arguments['groups'] = $groupConfiguration['include']; } if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) { $arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs); } foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) { if (!\class_exists($extension['class'], false) && $extension['file'] !== '') { require_once $extension['file']; } if (!\class_exists($extension['class'])) { throw new Exception( \sprintf( 'Class "%s" does not exist', $extension['class'] ) ); } $extensionClass = new ReflectionClass($extension['class']); if (!$extensionClass->implementsInterface(Hook::class)) { throw new Exception( \sprintf( 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', $extension['class'] ) ); } if (\count($extension['arguments']) == 0) { $extensionObject = $extensionClass->newInstance(); } else { $extensionObject = $extensionClass->newInstanceArgs( $extension['arguments'] ); } \assert($extensionObject instanceof TestHook); $this->addExtension($extensionObject); } foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if (!\class_exists($listener['class'], false) && $listener['file'] !== '') { require_once $listener['file']; } if (!\class_exists($listener['class'])) { throw new Exception( \sprintf( 'Class "%s" does not exist', $listener['class'] ) ); } $listenerClass = new ReflectionClass($listener['class']); if (!$listenerClass->implementsInterface(TestListener::class)) { throw new Exception( \sprintf( 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', $listener['class'] ) ); } if (\count($listener['arguments']) == 0) { $listener = new $listener['class']; } else { $listener = $listenerClass->newInstanceArgs( $listener['arguments'] ); } $arguments['listeners'][] = $listener; } $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration(); if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) { $arguments['coverageClover'] = $loggingConfiguration['coverage-clover']; } if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) { $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j']; if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) { $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold']; } } if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) { if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) { $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound']; } if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) { $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound']; } $arguments['coverageHtml'] = $loggingConfiguration['coverage-html']; } if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) { $arguments['coveragePHP'] = $loggingConfiguration['coverage-php']; } if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) { $arguments['coverageText'] = $loggingConfiguration['coverage-text']; if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) { $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles']; } else { $arguments['coverageTextShowUncoveredFiles'] = false; } if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) { $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary']; } else { $arguments['coverageTextShowOnlySummary'] = false; } } if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) { $arguments['coverageXml'] = $loggingConfiguration['coverage-xml']; } if (isset($loggingConfiguration['plain'])) { $arguments['listeners'][] = new ResultPrinter( $loggingConfiguration['plain'], true ); } if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) { $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity']; } if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) { $arguments['junitLogfile'] = $loggingConfiguration['junit']; } if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) { $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html']; } if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) { $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text']; } if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) { $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml']; } $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration(); if (isset($testdoxGroupConfiguration['include']) && !isset($arguments['testdoxGroups'])) { $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include']; } if (isset($testdoxGroupConfiguration['exclude']) && !isset($arguments['testdoxExcludeGroups'])) { $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude']; } } $arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true; $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false; $arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT; $arguments['columns'] = $arguments['columns'] ?? 80; $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? true; $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['groups'] = $arguments['groups'] ?? []; $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; $arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false; $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? \time(); $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; $arguments['repeat'] = $arguments['repeat'] ?? false; $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; $arguments['reverseList'] = $arguments['reverseList'] ?? false; $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; $arguments['verbose'] = $arguments['verbose'] ?? false; } /** * @throws \ReflectionException * @throws \InvalidArgumentException */ private function processSuiteFilters(TestSuite $suite, array $arguments): void { if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups'])) { return; } $filterFactory = new Factory; if (!empty($arguments['excludeGroups'])) { $filterFactory->addFilter( new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups'] ); } if (!empty($arguments['groups'])) { $filterFactory->addFilter( new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new ReflectionClass(NameFilterIterator::class), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } private function writeMessage(string $type, string $message): void { if (!$this->messagePrinted) { $this->write("\n"); } $this->write( \sprintf( "%-15s%s\n", $type . ':', $message ) ); $this->messagePrinted = true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use PHPUnit\Framework\TestSuite; use PHPUnit\Framework\WarningTestCase; use PHPUnit\Util\RegularExpression; use RecursiveFilterIterator; use RecursiveIterator; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class NameFilterIterator extends RecursiveFilterIterator { /** * @var string */ protected $filter; /** * @var int */ protected $filterMin; /** * @var int */ protected $filterMax; /** * @throws \Exception */ public function __construct(RecursiveIterator $iterator, string $filter) { parent::__construct($iterator); $this->setFilter($filter); } /** * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function accept(): bool { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } $tmp = \PHPUnit\Util\Test::describe($test); if ($test instanceof WarningTestCase) { $name = $test->getMessage(); } else { if ($tmp[0] !== '') { $name = \implode('::', $tmp); } else { $name = $tmp[1]; } } $accepted = @\preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = \end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return (bool) $accepted; } /** * @throws \Exception */ protected function setFilter(string $filter): void { if (RegularExpression::safeMatch($filter, '') === false) { // Handles: // * testAssertEqualsSucceeds#4 // * testAssertEqualsSucceeds#4-8 if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = \sprintf( '%s.*with data set #(\d+)$', $matches[1] ); $this->filterMin = $matches[2]; $this->filterMax = $matches[3]; } else { $filter = \sprintf( '%s.*with data set #%s$', $matches[1], $matches[2] ); } } // Handles: // * testDetermineJsonError@JSON_ERROR_NONE // * testDetermineJsonError@JSON.* elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = \sprintf( '%s.*with data set "%s"$', $matches[1], $matches[2] ); } // Escape delimiters in regular expression. Do NOT use preg_quote, // to keep magic characters. $filter = \sprintf('/%s/i', \str_replace( '/', '\\/', $filter )); } $this->filter = $filter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use DOMDocument; use DOMElement; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\ExceptionWrapper; use PHPUnit\Framework\SelfDescribing; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestFailure; use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestSuite; use PHPUnit\Framework\Warning; use PHPUnit\Util\Filter; use PHPUnit\Util\Printer; use PHPUnit\Util\Xml; use ReflectionClass; use ReflectionException; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class JUnit extends Printer implements TestListener { /** * @var DOMDocument */ protected $document; /** * @var DOMElement */ protected $root; /** * @var bool */ protected $reportUselessTests = false; /** * @var bool */ protected $writeDocument = true; /** * @var DOMElement[] */ protected $testSuites = []; /** * @var int[] */ protected $testSuiteTests = [0]; /** * @var int[] */ protected $testSuiteAssertions = [0]; /** * @var int[] */ protected $testSuiteErrors = [0]; /** * @var int[] */ protected $testSuiteFailures = [0]; /** * @var int[] */ protected $testSuiteSkipped = [0]; /** * @var int[] */ protected $testSuiteTimes = [0]; /** * @var int */ protected $testSuiteLevel = 0; /** * @var DOMElement */ protected $currentTestCase; /** * Constructor. * * @param null|mixed $out * * @throws \PHPUnit\Framework\Exception */ public function __construct($out = null, bool $reportUselessTests = false) { $this->document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('testsuites'); $this->document->appendChild($this->root); parent::__construct($out); $this->reportUselessTests = $reportUselessTests; } /** * Flush buffer and close output. */ public function flush(): void { if ($this->writeDocument === true) { $this->write($this->getXML()); } parent::flush(); } /** * An error occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addError(Test $test, \Throwable $t, float $time): void { $this->doAddFault($test, $t, $time, 'error'); $this->testSuiteErrors[$this->testSuiteLevel]++; } /** * A warning occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addWarning(Test $test, Warning $e, float $time): void { $this->doAddFault($test, $e, $time, 'warning'); $this->testSuiteFailures[$this->testSuiteLevel]++; } /** * A failure occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->doAddFault($test, $e, $time, 'failure'); $this->testSuiteFailures[$this->testSuiteLevel]++; } /** * Incomplete test. */ public function addIncompleteTest(Test $test, \Throwable $t, float $time): void { $this->doAddSkipped($test); } /** * Risky test. * * @throws ReflectionException */ public function addRiskyTest(Test $test, \Throwable $t, float $time): void { if (!$this->reportUselessTests || $this->currentTestCase === null) { return; } $error = $this->document->createElement( 'error', Xml::prepareString( "Risky Test\n" . Filter::getFilteredStacktrace($t) ) ); $error->setAttribute('type', \get_class($t)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } /** * Skipped test. */ public function addSkippedTest(Test $test, \Throwable $t, float $time): void { $this->doAddSkipped($test); } /** * A testsuite started. */ public function startTestSuite(TestSuite $suite): void { $testSuite = $this->document->createElement('testsuite'); $testSuite->setAttribute('name', $suite->getName()); if (\class_exists($suite->getName(), false)) { try { $class = new ReflectionClass($suite->getName()); $testSuite->setAttribute('file', $class->getFileName()); } catch (ReflectionException $e) { } } if ($this->testSuiteLevel > 0) { $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); } else { $this->root->appendChild($testSuite); } $this->testSuiteLevel++; $this->testSuites[$this->testSuiteLevel] = $testSuite; $this->testSuiteTests[$this->testSuiteLevel] = 0; $this->testSuiteAssertions[$this->testSuiteLevel] = 0; $this->testSuiteErrors[$this->testSuiteLevel] = 0; $this->testSuiteFailures[$this->testSuiteLevel] = 0; $this->testSuiteSkipped[$this->testSuiteLevel] = 0; $this->testSuiteTimes[$this->testSuiteLevel] = 0; } /** * A testsuite ended. */ public function endTestSuite(TestSuite $suite): void { $this->testSuites[$this->testSuiteLevel]->setAttribute( 'tests', (string) $this->testSuiteTests[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'assertions', (string) $this->testSuiteAssertions[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'errors', (string) $this->testSuiteErrors[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'failures', (string) $this->testSuiteFailures[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'skipped', (string) $this->testSuiteSkipped[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'time', \sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) ); if ($this->testSuiteLevel > 1) { $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } $this->testSuiteLevel--; } /** * A test started. * * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ReflectionException */ public function startTest(Test $test): void { $usesDataprovider = false; if (\method_exists($test, 'usesDataProvider')) { $usesDataprovider = $test->usesDataProvider(); } $testCase = $this->document->createElement('testcase'); $testCase->setAttribute('name', $test->getName()); $class = new ReflectionClass($test); $methodName = $test->getName(!$usesDataprovider); if ($class->hasMethod($methodName)) { $method = $class->getMethod($methodName); $testCase->setAttribute('class', $class->getName()); $testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName())); $testCase->setAttribute('file', $class->getFileName()); $testCase->setAttribute('line', (string) $method->getStartLine()); } $this->currentTestCase = $testCase; } /** * A test ended. */ public function endTest(Test $test, float $time): void { $numAssertions = 0; if (\method_exists($test, 'getNumAssertions')) { $numAssertions = $test->getNumAssertions(); } $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', (string) $numAssertions ); $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $testOutput = ''; if (\method_exists($test, 'hasOutput') && \method_exists($test, 'getActualOutput')) { $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; } if (!empty($testOutput)) { $systemOut = $this->document->createElement( 'system-out', Xml::prepareString($testOutput) ); $this->currentTestCase->appendChild($systemOut); } $this->currentTestCase = null; } /** * Returns the XML as a string. */ public function getXML(): string { return $this->document->saveXML(); } /** * Enables or disables the writing of the document * in flush(). * * This is a "hack" needed for the integration of * PHPUnit with Phing. */ public function setWriteDocument(/*bool*/ $flag): void { if (\is_bool($flag)) { $this->writeDocument = $flag; } } /** * Method which generalizes addError() and addFailure() * * @throws \InvalidArgumentException * @throws ReflectionException */ private function doAddFault(Test $test, \Throwable $t, float $time, $type): void { if ($this->currentTestCase === null) { return; } if ($test instanceof SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= TestFailure::exceptionToString($t) . "\n" . Filter::getFilteredStacktrace($t); $fault = $this->document->createElement( $type, Xml::prepareString($buffer) ); if ($t instanceof ExceptionWrapper) { $fault->setAttribute('type', $t->getClassName()); } else { $fault->setAttribute('type', \get_class($t)); } $this->currentTestCase->appendChild($fault); } private function doAddSkipped(Test $test): void { if ($this->currentTestCase === null) { return; } $skipped = $this->document->createElement('skipped'); $this->currentTestCase->appendChild($skipped); $this->testSuiteSkipped[$this->testSuiteLevel]++; } } _setUp(); } } protected function tearDown(): void { if (method_exists($this, '_tearDown')) { $this->_tearDown(); } } public static function setUpBeforeClass(): void { if (method_exists(get_called_class(), '_setUpBeforeClass')) { static::_setUpBeforeClass(); } } public static function tearDownAfterClass(): void { if (method_exists(get_called_class(), '_tearDownAfterClass')) { static::_tearDownAfterClass(); } } public function expectExceptionMessageRegExp(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } } getFileName(); } else { $reflector = new \ReflectionClass($test); $filename = $reflector->getFileName(); } if ($filename !== $this->currentFile) { if ($this->currentFile !== null) { parent::endTestSuite(new TestSuite()); } //initialize all values to avoid warnings $this->testSuiteAssertions[self::FILE_LEVEL] = 0; $this->testSuiteTests[self::FILE_LEVEL] = 0; $this->testSuiteTimes[self::FILE_LEVEL] = 0; $this->testSuiteErrors[self::FILE_LEVEL] = 0; $this->testSuiteFailures[self::FILE_LEVEL] = 0; $this->testSuiteSkipped[self::FILE_LEVEL] = 0; $this->testSuiteLevel = self::FILE_LEVEL; $this->currentFile = $filename; $this->currentFileSuite = $this->document->createElement('testsuite'); if ($test instanceof Reported) { $reportFields = $test->getReportFields(); $class = isset($reportFields['class']) ? $reportFields['class'] : $reportFields['name']; $this->currentFileSuite->setAttribute('name', $class); } else { $this->currentFileSuite->setAttribute('name', get_class($test)); } $this->currentFileSuite->setAttribute('file', $filename); $this->testSuites[self::SUITE_LEVEL]->appendChild($this->currentFileSuite); $this->testSuites[self::FILE_LEVEL] = $this->currentFileSuite; } if (!$test instanceof Reported) { parent::startTest($test); return; } $this->currentTestCase = $this->document->createElement('testcase'); $isStrict = Configuration::config()['settings']['strict_xml']; foreach ($test->getReportFields() as $attr => $value) { if ($isStrict and !in_array($attr, $this->strictAttributes)) { continue; } $this->currentTestCase->setAttribute($attr, $value); } } public function endTest(\PHPUnit\Framework\Test $test, float $time):void { if ($this->currentTestCase !== null && $test instanceof Test) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } if ($test instanceof TestCase) { parent::endTest($test, $time); return; } // In PhpUnit 7.4.*, parent::endTest ignores tests that aren't instances of TestCase // so I copied this code from PhpUnit 7.3.5 $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $this->currentTestCase = null; } /** * Cleans the mess caused by test suite manipulation in startTest */ public function endTestSuite(TestSuite $suite): void { if ($suite->getName()) { if ($this->currentFile) { //close last file in the test suite parent::endTestSuite(new TestSuite()); $this->currentFile = null; } $this->testSuiteLevel = self::SUITE_LEVEL; } parent::endTestSuite($suite); } } currentTestCase = $this->document->createElement('testcase'); $isStrict = Configuration::config()['settings']['strict_xml']; foreach ($test->getReportFields() as $attr => $value) { if ($isStrict and !in_array($attr, $this->strictAttributes)) { continue; } $this->currentTestCase->setAttribute($attr, $value); } } public function endTest(\PHPUnit\Framework\Test $test, float $time):void { if ($this->currentTestCase !== null and $test instanceof Test) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } if ($test instanceof TestCase) { parent::endTest($test, $time); return; } // since PhpUnit 7.4.0, parent::endTest ignores tests that aren't instances of TestCase // so I copied this code from PhpUnit 7.3.5 $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $this->currentTestCase = null; } } testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR; $this->failed++; } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE; $this->failed++; } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING; $this->warned++; } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * * @since Method available since Release 4.0.0 */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY; $this->risky++; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * * @since Method available since Release 3.0.0 */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } public function startTest(\PHPUnit\Framework\Test $test) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace { if (!class_exists('PHPUnit_Util_String')) { /** * String helpers. */ class PHPUnit_Util_String { /** * Converts a string to UTF-8 encoding. * * @param string $string * * @return string */ public static function convertToUtf8($string) { return mb_convert_encoding($string, 'UTF-8'); } /** * Checks a string for UTF-8 encoding. * * @param string $string * * @return bool */ protected static function isUtf8($string) { $length = strlen($string); for ($i = 0; $i < $length; $i++) { if (ord($string[$i]) < 0x80) { $n = 0; } elseif ((ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } } } } namespace PHPUnit\Util\Log { /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Codeception\Test\Descriptor; /** * A TestListener that generates JSON messages. */ if (!class_exists('\PHPUnit\Util\Log\JSON')) { class JSON extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { /** * @var string */ protected $currentTestSuiteName = ''; /** * @var string */ protected $currentTestName = ''; /** * @var bool */ protected $currentTestPass = true; /** * @var array */ protected $logEvents = []; /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->writeCase( 'warning', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure( \PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time ): void{ $this->writeCase( 'fail', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Incomplete Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Risky Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Skipped Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * A testsuite started. * * @param \PHPUnit\Framework\TestSuite $suite */ public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->currentTestSuiteName = $suite->getName(); $this->currentTestName = ''; $this->addLogEvent( [ 'event' => 'suiteStart', 'suite' => $this->currentTestSuiteName, 'tests' => count($suite) ] ); } /** * A testsuite ended. * * @param \PHPUnit\Framework\TestSuite $suite */ public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->currentTestSuiteName = ''; $this->currentTestName = ''; $this->writeArray($this->logEvents); } /** * A test started. * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test): void { $this->currentTestName = \PHPUnit\Util\Test::describe($test); $this->currentTestPass = true; $this->addLogEvent( [ 'event' => 'testStart', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName ] ); } /** * A test ended. * * @param \PHPUnit\Framework\Test $test * @param float $time */ public function endTest(\PHPUnit\Framework\Test $test, float $time): void { if ($this->currentTestPass) { $this->writeCase('pass', $time, [], '', $test); } } /** * @param string $status * @param float $time * @param array $trace * @param string $message * @param \PHPUnit\Framework\TestCase|null $test */ protected function writeCase($status, float $time, array $trace = [], $message = '', $test = null): void { $output = ''; // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) { $output = $test->getActualOutput(); } $this->addLogEvent( [ 'event' => 'test', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName, 'status' => $status, 'time' => $time, 'trace' => $trace, 'message' => \PHPUnit_Util_String::convertToUtf8($message), 'output' => $output, ] ); } /** * @param array $event_data */ protected function addLogEvent($event_data = []): void { if (count($event_data)) { array_push($this->logEvents, $event_data); } } /** * @param array $buffer */ public function writeArray($buffer) { array_walk_recursive( $buffer, function (&$input){ if (is_string($input)) { $input = \PHPUnit_Util_String::convertToUtf8($input); } } ); $this->write(json_encode($buffer, JSON_PRETTY_PRINT)); } } } /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!class_exists('\PHPUnit\Util\Log\TAP')) { /** * A TestListener that generates a logfile of the * test execution using the Test Anything Protocol (TAP). */ class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { /** * @var int */ protected $testNumber = 0; /** * @var int */ protected $testSuiteLevel = 0; /** * @var bool */ protected $testSuccessful = true; /** * Constructor. * * @param mixed $out * * @throws \PHPUnit\Framework\Throwable */ public function __construct($out = null) { parent::__construct($out); $this->write("TAP version 13\n"); } /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeNotOk($test, 'Error'); } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->writeNotOk($test, 'Warning'); } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure( \PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time ): void{ $this->writeNotOk($test, 'Failure'); $message = explode( "\n", \PHPUnit\Framework\TestFailure::exceptionToString($e) ); $diagnostic = [ 'message' => $message[0], 'severity' => 'fail' ]; if ($e instanceof \PHPUnit\Framework\ExpectationFailedThrowable) { $cf = $e->getComparisonFailure(); if ($cf !== null) { $diagnostic['data'] = [ 'got' => $cf->getActual(), 'expected' => $cf->getExpected() ]; } } $yaml = new \Symfony\Component\Yaml\Dumper; $this->write( sprintf( " ---\n%s ...\n", $yaml->dump($diagnostic, 2, 2) ) ); } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeNotOk($test, '', 'TODO Incomplete Test'); } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->write( sprintf( "ok %d - # RISKY%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->write( sprintf( "ok %d - # SKIP%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * A testsuite started. * * @param \PHPUnit\Framework\TestSuite $suite */ public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->testSuiteLevel++; } /** * A testsuite ended. * * @param \PHPUnit\Framework\TestSuite $suite */ public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->testSuiteLevel--; if ($this->testSuiteLevel == 0) { $this->write(sprintf("1..%d\n", $this->testNumber)); } } /** * A test started. * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test): void { $this->testNumber++; $this->testSuccessful = true; } /** * A test ended. * * @param \PHPUnit\Framework\Test $test * @param float $time */ public function endTest(\PHPUnit\Framework\Test $test, float $time): void { if ($this->testSuccessful === true) { $this->write( sprintf( "ok %d - %s\n", $this->testNumber, Descriptor::getTestSignature($test) ) ); } $this->writeDiagnostics($test); } /** * @param \PHPUnit\Framework\Test $test * @param string $prefix * @param string $directive */ protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '') { $this->write( sprintf( "not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describeAsString($test), $directive != '' ? ' # ' . $directive : '' ) ); $this->testSuccessful = false; } /** * @param \PHPUnit\Framework\Test $test */ private function writeDiagnostics(\PHPUnit\Framework\Test $test) { if (!$test instanceof \PHPUnit\Framework\TestCase) { return; } if (!$test->hasOutput()) { return; } foreach (explode("\n", trim($test->getActualOutput())) as $line) { $this->write( sprintf( "# %s\n", $line ) ); } } } } } // @codingStandardsIgnoreEnd false, 'phpunit-xml' => false, 'html' => false, 'tap' => false, 'json' => false, 'report' => false ]; protected $config = []; protected $logDir = null; public function __construct() { $this->config = Configuration::config(); $this->logDir = Configuration::outputDir(); // prepare log dir $this->phpUnitOverriders(); parent::__construct(); } public function phpUnitOverriders() { require_once __DIR__ . DIRECTORY_SEPARATOR . 'Overrides/Filter.php'; } /** * @return null|\PHPUnit\TextUI\ResultPrinter */ public function getPrinter() { return $this->printer; } public function prepareSuite(\PHPUnit\Framework\Test $suite, array &$arguments) { $this->handleConfiguration($arguments); $filterAdded = false; $filterFactory = new \PHPUnit\Runner\Filter\Factory(); if ($arguments['groups']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('PHPUnit\Runner\Filter\IncludeGroupFilterIterator'), $arguments['groups'] ); } if ($arguments['excludeGroups']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('PHPUnit\Runner\Filter\ExcludeGroupFilterIterator'), $arguments['excludeGroups'] ); } if ($arguments['filter']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('Codeception\PHPUnit\FilterTest'), $arguments['filter'] ); } if ($filterAdded) { $suite->injectFilter($filterFactory); } } public function doEnhancedRun( \PHPUnit\Framework\Test $suite, \PHPUnit\Framework\TestResult $result, array $arguments = [] ) { unset($GLOBALS['app']); // hook for not to serialize globals $result->convertErrorsToExceptions(false); if (isset($arguments['report_useless_tests'])) { $result->beStrictAboutTestsThatDoNotTestAnything((bool)$arguments['report_useless_tests']); } if (isset($arguments['disallow_test_output'])) { $result->beStrictAboutOutputDuringTests((bool)$arguments['disallow_test_output']); } if (empty(self::$persistentListeners)) { $this->applyReporters($result, $arguments); } if (class_exists('\Symfony\Bridge\PhpUnit\SymfonyTestsListener')) { $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : []; $listener = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener(); $listener->globalListenerDisabled(); $arguments['listeners'][] = $listener; } $arguments['listeners'][] = $this->printer; // clean up listeners between suites foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $suite->run($result); unset($suite); foreach ($arguments['listeners'] as $listener) { $result->removeListener($listener); } return $result; } /** * @param \PHPUnit\Framework\TestResult $result * @param array $arguments * * @return array */ protected function applyReporters(\PHPUnit\Framework\TestResult $result, array $arguments) { foreach ($this->defaultListeners as $listener => $value) { if (!isset($arguments[$listener])) { $arguments[$listener] = $value; } } if ($arguments['report']) { self::$persistentListeners[] = $this->instantiateReporter('report'); } if ($arguments['html']) { codecept_debug('Printing HTML report into ' . $arguments['html']); self::$persistentListeners[] = $this->instantiateReporter( 'html', [$this->absolutePath($arguments['html'])] ); } if ($arguments['xml']) { codecept_debug('Printing JUNIT report into ' . $arguments['xml']); self::$persistentListeners[] = $this->instantiateReporter( 'xml', [$this->absolutePath($arguments['xml']), (bool)$arguments['log_incomplete_skipped']] ); } if ($arguments['phpunit-xml']) { codecept_debug('Printing PHPUNIT report into ' . $arguments['phpunit-xml']); self::$persistentListeners[] = $this->instantiateReporter( 'phpunit-xml', [$this->absolutePath($arguments['phpunit-xml']), (bool)$arguments['log_incomplete_skipped']] ); } if ($arguments['tap']) { codecept_debug('Printing TAP report into ' . $arguments['tap']); self::$persistentListeners[] = $this->instantiateReporter('tap', [$this->absolutePath($arguments['tap'])]); } if ($arguments['json']) { codecept_debug('Printing JSON report into ' . $arguments['json']); self::$persistentListeners[] = $this->instantiateReporter( 'json', [$this->absolutePath($arguments['json'])] ); } foreach (self::$persistentListeners as $listener) { if ($listener instanceof ConsolePrinter) { $this->printer = $listener; continue; } $result->addListener($listener); } } protected function instantiateReporter($name, $args = []) { if (!isset($this->config['reporters'][$name])) { throw new ConfigurationException("Reporter $name not defined"); } return (new \ReflectionClass($this->config['reporters'][$name]))->newInstanceArgs($args); } private function absolutePath($path) { if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path return $path; } return $this->logDir . $path; } } getInnerIterator()->current(); if ($test instanceof \PHPUnit\Framework\TestSuite) { return true; } $name = Descriptor::getTestSignature($test); $index = Descriptor::getTestDataSetIndex($test); if (!is_null($index)) { $name .= " with data set #{$index}"; } $accepted = preg_match($this->filter, $name, $matches); // This fix the issue when an invalid dataprovider method generate a warning // See issue https://github.com/Codeception/Codeception/issues/4888 if($test instanceof \PHPUnit\Framework\WarningTestCase) { $message = $test->getMessage(); $accepted = preg_match($this->filter, $message, $matches); } if ($accepted && isset($this->filterMax)) { $set = end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return $accepted; } } getPrevious() ? $e->getPrevious()->getTrace() : $e->getTrace(); if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) { $trace = $e->getSerializableTrace(); } $eFile = $e->getFile(); $eLine = $e->getLine(); if (!self::frameExists($trace, $eFile, $eLine)) { array_unshift( $trace, ['file' => $eFile, 'line' => $eLine] ); } foreach ($trace as $step) { if (self::classIsFiltered($step) and $filter) { continue; } if (self::fileIsFiltered($step) and $filter) { continue; } if (!$asString) { $stackTrace[] = $step; continue; } if (!isset($step['file'])) { continue; } $stackTrace .= $step['file'] . ':' . $step['line'] . "\n"; } return $stackTrace; } protected static function classIsFiltered($step) { if (!isset($step['class'])) { return false; } $className = $step['class']; foreach (self::$filteredClassesPattern as $filteredClassName) { if (strpos($className, $filteredClassName) === 0) { return true; } } return false; } protected static function fileIsFiltered($step) { if (!isset($step['file'])) { return false; } if (strpos($step['file'], 'codecept.phar/') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'phpunit') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'codeception') !== false) { return true; } $modulePath = 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR . 'Module'; if (strpos($step['file'], $modulePath) !== false) { return false; // don`t filter modules } if (strpos($step['file'], 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR) !== false) { return true; } return false; } /** * @param array $trace * @param string $file * @param int $line * * @return bool */ private static function frameExists(array $trace, $file, $line) { foreach ($trace as $frame) { if (isset($frame['file']) && $frame['file'] == $file && isset($frame['line']) && $frame['line'] == $line) { return true; } } return false; } } '*']; file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } public function prepareTests() { $this->_copyDir(__DIR__ . '/vendor/codeception/codeception/tests', __DIR__ . '/tests'); $this->_copy(__DIR__ . '/vendor/codeception/codeception/codeception.yml', __DIR__ .'/codeception.yml'); $this->_symlink(__DIR__ . '/vendor/bin/codecept', __DIR__ . '/codecept'); } public function prepareTestAutoloading() { $config = json_decode(file_get_contents(__DIR__ . '/composer.json'), true); $config['autoload-dev'] = [ 'classmap' => [ 'tests/cli/_steps', 'tests/data/DummyClass.php', 'tests/data/claypit/tests/_data' ] ]; file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } } loadSessionSnapshot('login')) return; * * // logging in * $I->amOnPage('/login'); * $I->fillField('name', 'jon'); * $I->fillField('password', '123345'); * $I->click('Login'); * * // saving snapshot * $I->saveSessionSnapshot('login'); * } * ?> * ``` * * @param $name * @return mixed */ public function saveSessionSnapshot($name); /** * Loads cookies from a saved snapshot. * Allows to reuse same session across tests without additional login. * * See [saveSessionSnapshot](#saveSessionSnapshot) * * @param $name * @return mixed */ public function loadSessionSnapshot($name); /** * Deletes session snapshot. * * See [saveSessionSnapshot](#saveSessionSnapshot) * * @param $name * @return mixed */ public function deleteSessionSnapshot($name); } getModule('{{MODULE_NAME}}')->_saveScreenshot(codecept_output_dir().'screenshot_1.png'); * ``` * @api * @param $filename */ public function _saveScreenshot($filename); } :@ondemand.saucelabs.com' * port: 80 * browser: chrome * capabilities: * platform: 'Windows 10' * ``` * * ### BrowserStack * * 1. Create an account at [BrowserStack](https://www.browserstack.com/) to get your username and access key * 2. In the module configuration use the format `username`:`access_key`@hub.browserstack.com' for `host` * 3. Configure `os` and `os_version` under `capabilities` to define the operating System * 4. If your site is available only locally or via VPN you should use a tunnel app. In this case add `browserstack.local` capability and set it to true. * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: ':@hub.browserstack.com' * port: 80 * browser: chrome * capabilities: * os: Windows * os_version: 10 * browserstack.local: true # for local testing * ``` * * ### LambdaTest * * 1. Create an account at [LambdaTest](https://www.lambdatest.com/) to get your username and access key * 2. In the module configuration use the format `username`:`access key`@hub.lambdatest.com' for `host` * 3. Configure `os` and `os_version` under `capabilities` to define the operating System * 4. If your site is available only locally or via VPN you should use a tunnel app. In this case add capabilities.setCapability("tunnel",true);. * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: ':@hub.lambdatest.com' * build: * name: * port: 80 * browser: chrome * capabilities: * os: Windows * os_version: 10 * browser_version: 86 * resolution: 1366x768 * tunnel: true # for local testing * ``` * * ### TestingBot * * 1. Create an account at [TestingBot](https://testingbot.com/) to get your key and secret * 2. In the module configuration use the format `key`:`secret`@hub.testingbot.com' for `host` * 3. Configure `platform` under `capabilities` to define the [Operating System](https://testingbot.com/support/getting-started/browsers.html) * 4. Run [TestingBot Tunnel](https://testingbot.com/support/other/tunnel) if your site can't be accessed from Internet * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: ':@hub.testingbot.com' * port: 80 * browser: chrome * capabilities: * platform: Windows 10 * ``` * * ## Configuration * * * `url` *required* - Base URL for your app (amOnPage opens URLs relative to this setting). * * `browser` *required* - Browser to launch. * * `host` - Selenium server host (127.0.0.1 by default). * * `port` - Selenium server port (4444 by default). * * `restart` - Set to `false` (default) to use the same browser window for all tests, or set to `true` to create a new window for each test. In any case, when all tests are finished the browser window is closed. * * `start` - Autostart a browser for tests. Can be disabled if browser session is started with `_initializeSession` inside a Helper. * * `window_size` - Initial window size. Set to `maximize` or a dimension in the format `640x480`. * * `clear_cookies` - Set to false to keep cookies, or set to true (default) to delete all cookies between tests. * * `wait` (default: 0 seconds) - Whenever element is required and is not on page, wait for n seconds to find it before fail. * * `capabilities` - Sets Selenium [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities). Should be a key-value array. * * `connection_timeout` - timeout for opening a connection to remote selenium server (30 seconds by default). * * `request_timeout` - timeout for a request to return something from remote selenium server (30 seconds by default). * * `pageload_timeout` - amount of time to wait for a page load to complete before throwing an error (default 0 seconds). * * `http_proxy` - sets http proxy server url for testing a remote server. * * `http_proxy_port` - sets http proxy server port * * `ssl_proxy` - sets ssl(https) proxy server url for testing a remote server. * * `ssl_proxy_port` - sets ssl(https) proxy server port * * `debug_log_entries` - how many selenium entries to print with `debugWebDriverLogs` or on fail (0 by default). * * `log_js_errors` - Set to true to include possible JavaScript to HTML report, or set to false (default) to deactivate. * * `webdriver_proxy` - sets http proxy to tunnel requests to the remote Selenium WebDriver through * * `webdriver_proxy_port` - sets http proxy server port to tunnel requests to the remote Selenium WebDriver through * * Example (`acceptance.suite.yml`) * * ```yaml * modules: * enabled: * - WebDriver: * url: 'http://localhost/' * browser: firefox * window_size: 1024x768 * capabilities: * unexpectedAlertBehaviour: 'accept' * firefox_profile: '~/firefox-profiles/codeception-profile.zip.b64' * ``` * * ## Loading Parts from other Modules * * While all Codeception modules are designed to work stand-alone, it's still possible to load *several* modules at once. To use e.g. the [Asserts module](https://codeception.com/docs/modules/Asserts) in your acceptance tests, just load it like this in your `acceptance.suite.yml`: * * ```yaml * modules: * enabled: * - WebDriver * - Asserts * ``` * * However, when loading a framework module (e.g. [Symfony](https://codeception.com/docs/modules/Symfony)) like this, it would lead to a conflict: When you call `$I->amOnPage()`, Codeception wouldn't know if you want to access the page using WebDriver's `amOnPage()`, or Symfony's `amOnPage()`. That's why possibly conflicting modules are separated into "parts". Here's how to load just the "services" part from e.g. Symfony: * ```yaml * modules: * enabled: * - WebDriver * - Symfony: * part: services * ``` * To find out which parts each module has, look at the "Parts" header on the module's page. * * ## Usage * * ### Locating Elements * * Most methods in this module that operate on a DOM element (e.g. `click`) accept a locator as the first argument, * which can be either a string or an array. * * If the locator is an array, it should have a single element, * with the key signifying the locator type (`id`, `name`, `css`, `xpath`, `link`, or `class`) * and the value being the locator itself. * This is called a "strict" locator. * Examples: * * * `['id' => 'foo']` matches `
    ` * * `['name' => 'foo']` matches `
    ` * * `['css' => 'input[type=input][value=foo]']` matches `` * * `['xpath' => "//input[@type='submit'][contains(@value, 'foo')]"]` matches `` * * `['link' => 'Click here']` matches `Click here` * * `['class' => 'foo']` matches `
    ` * * Writing good locators can be tricky. * The Mozilla team has written an excellent guide titled [Writing reliable locators for Selenium and WebDriver tests](https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/). * * If you prefer, you may also pass a string for the locator. This is called a "fuzzy" locator. * In this case, Codeception uses a a variety of heuristics (depending on the exact method called) to determine what element you're referring to. * For example, here's the heuristic used for the `submitForm` method: * * 1. Does the locator look like an ID selector (e.g. "#foo")? If so, try to find a form matching that ID. * 2. If nothing found, check if locator looks like a CSS selector. If so, run it. * 3. If nothing found, check if locator looks like an XPath expression. If so, run it. * 4. Throw an `ElementNotFound` exception. * * Be warned that fuzzy locators can be significantly slower than strict locators. * Especially if you use Selenium WebDriver with `wait` (aka implicit wait) option. * In the example above if you set `wait` to 5 seconds and use XPath string as fuzzy locator, * `submitForm` method will wait for 5 seconds at each step. * That means 5 seconds finding the form by ID, another 5 seconds finding by CSS * until it finally tries to find the form by XPath). * If speed is a concern, it's recommended you stick with explicitly specifying the locator type via the array syntax. * * ### Get Scenario Metadata * * You can inject `\Codeception\Scenario` into your test to get information about the current configuration: * ```php * use Codeception\Scenario * public function myTest(AcceptanceTester $I, Scenario $scenario) * { * if ('firefox' === $scenario->current('browser')) { * // ... * } * } * ``` * See [Get Scenario Metadata](https://codeception.com/docs/07-AdvancedUsage#Get-Scenario-Metadata) for more information on `$scenario`. * * ## Public Properties * * * `webDriver` - instance of `\Facebook\WebDriver\Remote\RemoteWebDriver`. Can be accessed from Helper classes for complex WebDriver interactions. * * ```php * // inside Helper class * $this->getModule('WebDriver')->webDriver->getKeyboard()->sendKeys('hello, webdriver'); * ``` * */ class WebDriver extends CodeceptionModule implements WebInterface, RemoteInterface, MultiSessionInterface, SessionSnapshot, ScreenshotSaver, PageSourceSaver, ElementLocator, ConflictsWithModule, RequiresPackage { protected $requiredFields = ['browser', 'url']; protected $config = [ 'protocol' => 'http', 'host' => '127.0.0.1', 'port' => '4444', 'path' => '/wd/hub', 'start' => true, 'restart' => false, 'wait' => 0, 'clear_cookies' => true, 'window_size' => false, 'capabilities' => [], 'connection_timeout' => null, 'request_timeout' => null, 'pageload_timeout' => null, 'http_proxy' => null, 'http_proxy_port' => null, 'ssl_proxy' => null, 'ssl_proxy_port' => null, 'debug_log_entries' => 0, 'log_js_errors' => false, 'webdriver_proxy' => null, 'webdriver_proxy_port' => null, ]; protected $wdHost; protected $capabilities; protected $connectionTimeoutInMs; protected $requestTimeoutInMs; protected $test; protected $sessions = []; protected $sessionSnapshots = []; protected $webdriverProxy; protected $webdriverProxyPort; /** * @var RemoteWebDriver */ public $webDriver; /** * @var RemoteWebElement */ protected $baseElement; public function _requires() { return ['Facebook\WebDriver\Remote\RemoteWebDriver' => '"php-webdriver/webdriver": "^1.0.1"']; } /** * @return RemoteWebElement * @throws ModuleException */ protected function getBaseElement() { if (!$this->baseElement) { throw new ModuleException($this, "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"); } return $this->baseElement; } public function _initialize() { $this->wdHost = sprintf('%s://%s:%s%s', $this->config['protocol'], $this->config['host'], $this->config['port'], $this->config['path']); $this->capabilities = $this->config['capabilities']; $this->capabilities[WebDriverCapabilityType::BROWSER_NAME] = $this->config['browser']; if ($proxy = $this->getProxy()) { $this->capabilities[WebDriverCapabilityType::PROXY] = $proxy; } $this->connectionTimeoutInMs = $this->config['connection_timeout'] * 1000; $this->requestTimeoutInMs = $this->config['request_timeout'] * 1000; $this->webdriverProxy = $this->config['webdriver_proxy']; $this->webdriverProxyPort = $this->config['webdriver_proxy_port']; $this->loadFirefoxProfile(); } /** * Change capabilities of WebDriver. Should be executed before starting a new browser session. * This method expects a function to be passed which returns array or [WebDriver Desired Capabilities](https://github.com/php-webdriver/php-webdriver/blob/main/lib/Remote/DesiredCapabilities.php) object. * Additional [Chrome options](https://github.com/php-webdriver/php-webdriver/wiki/ChromeOptions) (like adding extensions) can be passed as well. * * ```php * getModule('WebDriver')->_capabilities(function($currentCapabilities) { * // or new \Facebook\WebDriver\Remote\DesiredCapabilities(); * return \Facebook\WebDriver\Remote\DesiredCapabilities::firefox(); * }); * } * ``` * * to make this work load `\Helper\Acceptance` before `WebDriver` in `acceptance.suite.yml`: * * ```yaml * modules: * enabled: * - \Helper\Acceptance * - WebDriver * ``` * * For instance, [**BrowserStack** cloud service](https://www.browserstack.com/automate/capabilities) may require a test name to be set in capabilities. * This is how it can be done via `_capabilities` method from `Helper\Acceptance`: * * ```php * getMetadata()->getName(); * $this->getModule('WebDriver')->_capabilities(function($currentCapabilities) use ($name) { * $currentCapabilities['name'] = $name; * return $currentCapabilities; * }); * } * ``` * In this case, please ensure that `\Helper\Acceptance` is loaded before WebDriver so new capabilities could be applied. * * @api * @param \Closure $capabilityFunction */ public function _capabilities(\Closure $capabilityFunction) { $this->capabilities = $capabilityFunction($this->capabilities); } public function _conflicts() { return 'Codeception\Lib\Interfaces\Web'; } public function _before(TestInterface $test) { if (!isset($this->webDriver) && $this->config['start']) { $this->_initializeSession(); } $this->setBaseElement(); $test->getMetadata()->setCurrent( [ 'browser' => $this->webDriver->getCapabilities()->getBrowserName(), 'capabilities' => $this->webDriver->getCapabilities()->toArray(), ] ); } /** * Restarts a web browser. * Can be used with `_reconfigure` to open browser with different configuration * * ```php * getModule('WebDriver')->_restart(); // just restart * $this->getModule('WebDriver')->_restart(['browser' => $browser]); // reconfigure + restart * ``` * * @param array $config * @api */ public function _restart($config = []) { $this->webDriver->quit(); if (!empty($config)) { $this->_reconfigure($config); } $this->_initializeSession(); } protected function onReconfigure() { $this->_initialize(); } protected function loadFirefoxProfile() { if (!array_key_exists('firefox_profile', $this->config['capabilities'])) { return; } $firefox_profile = $this->config['capabilities']['firefox_profile']; if (file_exists($firefox_profile) === false) { throw new ModuleConfigException( __CLASS__, "Firefox profile does not exist under given path " . $firefox_profile ); } // Set firefox profile as capability $this->capabilities['firefox_profile'] = file_get_contents($firefox_profile); } protected function initialWindowSize() { if ($this->config['window_size'] == 'maximize') { $this->maximizeWindow(); return; } $size = explode('x', $this->config['window_size']); if (count($size) == 2) { $this->resizeWindow(intval($size[0]), intval($size[1])); } } public function _after(TestInterface $test) { if ($this->config['restart']) { $this->stopAllSessions(); return; } if ($this->config['clear_cookies'] && isset($this->webDriver)) { try { $this->webDriver->manage()->deleteAllCookies(); } catch (\Exception $e) { // may cause fatal errors when not handled $this->debug("Error, can't clean cookies after a test: " . $e->getMessage()); } } } public function _failed(TestInterface $test, $fail) { $this->debugWebDriverLogs($test); $filename = preg_replace('~[^a-zA-Z0-9\x80-\xff]~', '.', Descriptor::getTestSignatureUnique($test)); $outputDir = codecept_output_dir(); $this->_saveScreenshot($report = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png'); $test->getMetadata()->addReport('png', $report); $this->_savePageSource($report = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html'); $test->getMetadata()->addReport('html', $report); $this->debug("Screenshot and page source were saved into '$outputDir' dir"); } /** * Print out latest Selenium Logs in debug mode * * @param \Codeception\TestInterface $test */ public function debugWebDriverLogs(TestInterface $test = null) { if (!isset($this->webDriver)) { $this->debug('WebDriver::debugWebDriverLogs method has been called when webDriver is not set'); return; } // don't show logs if log entries not set if (!$this->config['debug_log_entries']) { return; } try { // Dump out latest Selenium logs $logs = $this->webDriver->manage()->getAvailableLogTypes(); foreach ($logs as $logType) { $logEntries = array_slice( $this->webDriver->manage()->getLog($logType), -$this->config['debug_log_entries'] ); if (empty($logEntries)) { $this->debugSection("Selenium {$logType} Logs", " EMPTY "); continue; } $this->debugSection("Selenium {$logType} Logs", "\n" . $this->formatLogEntries($logEntries)); if ($logType === 'browser' && $this->config['log_js_errors'] && ($test instanceof ScenarioDriven) ) { $this->logJSErrors($test, $logEntries); } } } catch (\Exception $e) { $this->debug('Unable to retrieve Selenium logs : ' . $e->getMessage()); } } /** * Turns an array of log entries into a human-readable string. * Each log entry is an array with the keys "timestamp", "level", and "message". * See https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Entry_JSON_Object * * @param array $logEntries * @return string */ protected function formatLogEntries(array $logEntries) { $formattedLogs = ''; foreach ($logEntries as $logEntry) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $formattedLogs .= "{$time} {$logEntry['level']} - {$logEntry['message']}\n"; } return $formattedLogs; } /** * Logs JavaScript errors as comments. * * @param ScenarioDriven $test * @param array $browserLogEntries */ protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries) { foreach ($browserLogEntries as $logEntry) { if (true === isset($logEntry['level']) && true === isset($logEntry['message']) && $this->isJSError($logEntry['level'], $logEntry['message']) ) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $test->getScenario()->comment("{$time} {$logEntry['level']} - {$logEntry['message']}"); } } } /** * Determines if the log entry is an error. * The decision is made depending on browser and log-level. * * @param string $logEntryLevel * @param string $message * @return bool */ protected function isJSError($logEntryLevel, $message) { return ( ($this->isPhantom() && $logEntryLevel != 'INFO') // phantomjs logs errors as "WARNING" || $logEntryLevel === 'SEVERE' // other browsers log errors as "SEVERE" ) && strpos($message, 'ERR_PROXY_CONNECTION_FAILED') === false; // ignore blackhole proxy } public function _afterSuite() { // this is just to make sure webDriver is cleared after suite $this->stopAllSessions(); } protected function stopAllSessions() { foreach ($this->sessions as $session) { $this->_closeSession($session); } $this->webDriver = null; $this->baseElement = null; } public function amOnSubdomain($subdomain) { $url = $this->config['url']; $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new $this->_reconfigure(['url' => $url]); } /** * Returns URL of a host. * * @api * @return mixed * @throws ModuleConfigException */ public function _getUrl() { if (!isset($this->config['url'])) { throw new ModuleConfigException( __CLASS__, "Module connection failure. The URL for client can't bre retrieved" ); } return $this->config['url']; } protected function getProxy() { $proxyConfig = []; if ($this->config['http_proxy']) { $proxyConfig['httpProxy'] = $this->config['http_proxy']; if ($this->config['http_proxy_port']) { $proxyConfig['httpProxy'] .= ':' . $this->config['http_proxy_port']; } } if ($this->config['ssl_proxy']) { $proxyConfig['sslProxy'] = $this->config['ssl_proxy']; if ($this->config['ssl_proxy_port']) { $proxyConfig['sslProxy'] .= ':' . $this->config['ssl_proxy_port']; } } if (!empty($proxyConfig)) { $proxyConfig['proxyType'] = 'manual'; return $proxyConfig; } return null; } /** * Uri of currently opened page. * @return string * @api * @throws ModuleException */ public function _getCurrentUri() { $url = $this->webDriver->getCurrentURL(); if ($url == 'about:blank' || strpos($url, 'data:') === 0) { throw new ModuleException($this, 'Current url is blank, no page was opened'); } return Uri::retrieveUri($url); } public function _saveScreenshot($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_saveScreenshot method has been called when webDriver is not set'); return; } try { $this->webDriver->takeScreenshot($filename); } catch (\Exception $e) { $this->debug('Unable to retrieve screenshot from Selenium : ' . $e->getMessage()); return; } } public function _saveElementScreenshot($selector, $filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_saveElementScreenshot method has been called when webDriver is not set'); return; } try { $this->matchFirstOrFail($this->webDriver, $selector)->takeElementScreenshot($filename); } catch (\Exception $e) { $this->debug('Unable to retrieve element screenshot from Selenium : ' . $e->getMessage()); return; } } public function _findElements($locator) { return $this->match($this->webDriver, $locator); } /** * Saves HTML source of a page to a file * @param $filename */ public function _savePageSource($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_savePageSource method has been called when webDriver is not set'); return; } try { file_put_contents($filename, $this->webDriver->getPageSource()); } catch (\Exception $e) { $this->debug('Unable to retrieve source page from Selenium : ' . $e->getMessage()); } } /** * Takes a screenshot of the current window and saves it to `tests/_output/debug`. * * ``` php * amOnPage('/user/edit'); * $I->makeScreenshot('edit_page'); * // saved to: tests/_output/debug/edit_page.png * $I->makeScreenshot(); * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png * ``` * * @param $name */ public function makeScreenshot($name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_log_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; $this->_saveScreenshot($screenName); $this->debugSection('Screenshot Saved', "file://$screenName"); } /** * Takes a screenshot of an element of the current window and saves it to `tests/_output/debug`. * * ``` php * amOnPage('/user/edit'); * $I->makeElementScreenshot('#dialog', 'edit_page'); * // saved to: tests/_output/debug/edit_page.png * $I->makeElementScreenshot('#dialog'); * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png * ``` * * @param $name */ public function makeElementScreenshot($selector, $name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_log_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; $this->_saveElementScreenshot($selector, $screenName); $this->debugSection('Screenshot Saved', "file://$screenName"); } public function makeHtmlSnapshot($name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_output_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $fileName = $debugDir . DIRECTORY_SEPARATOR . $name . '.html'; $this->_savePageSource($fileName); $this->debugSection('Snapshot Saved', "file://$fileName"); } /** * Resize the current window. * * ``` php * resizeWindow(800, 600); * * ``` * * @param int $width * @param int $height */ public function resizeWindow($width, $height) { $this->webDriver->manage()->window()->setSize(new WebDriverDimension($width, $height)); } private function debugCookies() { $result = []; $cookies = $this->webDriver->manage()->getCookies(); foreach ($cookies as $cookie) { $result[] = is_array($cookie) ? $cookie : $cookie->toArray(); } $this->debugSection('Cookies', json_encode($result)); } public function seeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugCookies(); $this->assertContains($cookie, $cookies); } public function dontSeeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugCookies(); $this->assertNotContains($cookie, $cookies); } public function setCookie($cookie, $value, array $params = [], $showDebug = true) { $params['name'] = $cookie; $params['value'] = $value; if (isset($params['expires'])) { // PhpBrowser compatibility $params['expiry'] = $params['expires']; } // #5401 Supply defaults, otherwise chromedriver 2.46 complains. $defaults = [ 'path' => '/', 'expiry' => time() + 86400, 'secure' => false, 'httpOnly' => false, ]; foreach ($defaults as $key => $default) { if (empty($params[$key])) { $params[$key] = $default; } } $this->webDriver->manage()->addCookie($params); if ($showDebug) { $this->debugCookies(); } } public function resetCookie($cookie, array $params = []) { $this->webDriver->manage()->deleteCookieNamed($cookie); $this->debugCookies(); } public function grabCookie($cookie, array $params = []) { $params['name'] = $cookie; $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); if (empty($cookies)) { return null; } $cookie = reset($cookies); return $cookie['value']; } /** * Grabs current page source code. * * @throws ModuleException if no page was opened. * * @return string Current page source code. */ public function grabPageSource() { // Make sure that some page was opened. $this->_getCurrentUri(); return $this->webDriver->getPageSource(); } protected function filterCookies($cookies, $params = []) { foreach (['domain', 'path', 'name'] as $filter) { if (!isset($params[$filter])) { continue; } $cookies = array_filter( $cookies, function ($item) use ($filter, $params) { return $item[$filter] == $params[$filter]; } ); } return $cookies; } public function amOnUrl($url) { $host = Uri::retrieveHost($url); $this->_reconfigure(['url' => $host]); $this->debugSection('Host', $host); $this->webDriver->get($url); } public function amOnPage($page) { $url = Uri::appendPath($this->config['url'], $page); $this->debugSection('GET', $url); $this->webDriver->get($url); } public function see($text, $selector = null) { if (!$selector) { return $this->assertPageContains($text); } $this->enableImplicitWait(); $nodes = $this->matchVisible($selector); $this->disableImplicitWait(); $this->assertNodesContain($text, $nodes, $selector); } public function dontSee($text, $selector = null) { if (!$selector) { return $this->assertPageNotContains($text); } $nodes = $this->matchVisible($selector); $this->assertNodesNotContain($text, $nodes, $selector); } public function seeInSource($raw) { $this->assertPageSourceContains($raw); } public function dontSeeInSource($raw) { $this->assertPageSourceNotContains($raw); } /** * Checks that the page source contains the given string. * * ```php * seeInPageSource('assertThat( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } /** * Checks that the page source doesn't contain the given string. * * @param $text */ public function dontSeeInPageSource($text) { $this->assertThatItsNot( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } public function click($link, $context = null) { $page = $this->webDriver; if ($context) { $page = $this->matchFirstOrFail($this->webDriver, $context); } $el = $this->_findClickable($page, $link); if (!$el) { // check one more time if this was a CSS selector we didn't match try { $els = $this->match($page, $link); } catch (MalformedLocatorException $e) { throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button"); } $el = reset($els); } if (!$el) { throw new ElementNotFound($link, 'Link or Button or CSS or XPath'); } $el->click(); } /** * Locates a clickable element. * * Use it in Helpers or GroupObject or Extension classes: * * ```php * getModule('WebDriver'); * $page = $module->webDriver; * * // search a link or button on a page * $el = $module->_findClickable($page, 'Click Me'); * * // search a link or button within an element * $topBar = $module->_findElements('.top-bar')[0]; * $el = $module->_findClickable($topBar, 'Click Me'); * * ``` * @api * @param RemoteWebDriver $page WebDriver instance or an element to search within * @param $link a link text or locator to click * @return WebDriverElement */ public function _findClickable($page, $link) { if (is_array($link) or ($link instanceof WebDriverBy)) { return $this->matchFirstOrFail($page, $link); } // try to match by strict locators, CSS Ids or XPath if (Locator::isPrecise($link)) { return $this->matchFirstOrFail($page, $link); } $locator = static::xpathLiteral(trim($link)); // narrow $xpath = Locator::combine( ".//a[normalize-space(.)=$locator]", ".//button[normalize-space(.)=$locator]", ".//a/img[normalize-space(@alt)=$locator]/ancestor::a", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=$locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } // wide $xpath = Locator::combine( ".//a[./@href][((contains(normalize-space(string(.)), $locator)) or contains(./@title, $locator) or .//img[contains(./@alt, $locator)])]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, $locator)]", ".//input[./@type = 'image'][contains(./@alt, $locator)]", ".//button[contains(normalize-space(string(.)), $locator)]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = $locator or ./@title = $locator]", ".//button[./@name = $locator or ./@title = $locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } return null; } /** * @param $selector * @return WebDriverElement[] * @throws \Codeception\Exception\ElementNotFound */ protected function findFields($selector) { if ($selector instanceof WebDriverElement) { return [$selector]; } if (is_array($selector) || ($selector instanceof WebDriverBy)) { $fields = $this->match($this->webDriver, $selector); if (empty($fields)) { throw new ElementNotFound($selector); } return $fields; } $locator = static::xpathLiteral(trim($selector)); // by text or label $xpath = Locator::combine( // @codingStandardsIgnoreStart ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = $locator) or ./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator)]", ".//label[contains(normalize-space(string(.)), $locator)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" // @codingStandardsIgnoreEnd ); $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // by name $xpath = ".//*[self::input | self::textarea | self::select][@name = $locator]"; $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // try to match by CSS or XPath $fields = $this->match($this->webDriver, $selector, false); if (!empty($fields)) { return $fields; } throw new ElementNotFound($selector, "Field by name, label, CSS or XPath"); } /** * @param $selector * @return WebDriverElement * @throws \Codeception\Exception\ElementNotFound */ protected function findField($selector) { $arr = $this->findFields($selector); return reset($arr); } public function seeLink($text, $url = null) { $this->enableImplicitWait(); $nodes = $this->getBaseElement()->findElements(WebDriverBy::partialLinkText($text)); $this->disableImplicitWait(); $currentUri = $this->_getCurrentUri(); if (empty($nodes)) { $this->fail("No links containing text '$text' were found in page $currentUri"); } if ($url) { $nodes = $this->filterNodesByHref($url, $nodes); } $this->assertNotEmpty($nodes, "No links containing text '$text' and URL '$url' were found in page $currentUri"); } public function dontSeeLink($text, $url = null) { $nodes = $this->getBaseElement()->findElements(WebDriverBy::partialLinkText($text)); $currentUri = $this->_getCurrentUri(); if (!$url) { $this->assertEmpty($nodes, "Link containing text '$text' was found in page $currentUri"); } else { $nodes = $this->filterNodesByHref($url, $nodes); $this->assertEmpty($nodes, "Link containing text '$text' and URL '$url' was found in page $currentUri"); } } /** * @param string $url * @param $nodes * @return array */ private function filterNodesByHref($url, $nodes) { //current uri can be relative, merging it with configured base url gives absolute url $absoluteCurrentUrl = Uri::mergeUrls($this->_getUrl(), $this->_getCurrentUri()); $expectedUrl = Uri::mergeUrls($absoluteCurrentUrl, $url); $nodes = array_filter( $nodes, function (WebDriverElement $e) use ($expectedUrl, $absoluteCurrentUrl) { $elementHref = Uri::mergeUrls($absoluteCurrentUrl, $e->getAttribute('href')); return $elementHref === $expectedUrl; } ); return $nodes; } public function seeInCurrentUrl($uri) { $this->assertStringContainsString($uri, $this->_getCurrentUri()); } public function seeCurrentUrlEquals($uri) { $this->assertEquals($uri, $this->_getCurrentUri()); } public function seeCurrentUrlMatches($uri) { $this->assertRegExp($uri, $this->_getCurrentUri()); } public function dontSeeInCurrentUrl($uri) { $this->assertStringNotContainsString($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlEquals($uri) { $this->assertNotEquals($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlMatches($uri) { $this->assertNotRegExp($uri, $this->_getCurrentUri()); } public function grabFromCurrentUrl($uri = null) { if (!$uri) { return $this->_getCurrentUri(); } $matches = []; $res = preg_match($uri, $this->_getCurrentUri(), $matches); if (!$res) { $this->fail("Couldn't match $uri in " . $this->_getCurrentUri()); } if (!isset($matches[1])) { $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'"); } return $matches[1]; } public function seeCheckboxIsChecked($checkbox) { $this->assertTrue($this->findField($checkbox)->isSelected()); } public function dontSeeCheckboxIsChecked($checkbox) { $this->assertFalse($this->findField($checkbox)->isSelected()); } public function seeInField($field, $value) { $els = $this->findFields($field); $this->assert($this->proceedSeeInField($els, $value)); } public function dontSeeInField($field, $value) { $els = $this->findFields($field); $this->assertNot($this->proceedSeeInField($els, $value)); } public function seeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, false); } public function dontSeeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, true); } protected function proceedSeeInFormFields($formSelector, array $params, $assertNot) { $form = $this->match($this->getBaseElement(), $formSelector); if (empty($form)) { throw new ElementNotFound($formSelector, "Form via CSS or XPath"); } $form = reset($form); $els = []; foreach ($params as $name => $values) { $this->pushFormField($els, $form, $name, $values); } foreach ($els as $arrayElement) { list($el, $values) = $arrayElement; if (!is_array($values)) { $values = [$values]; } foreach ($values as $value) { $ret = $this->proceedSeeInField($el, $value); if ($assertNot) { $this->assertNot($ret); } else { $this->assert($ret); } } } } /** * Map an array element passed to seeInFormFields to its corresponding WebDriver element, * recursing through array values if the field is not found. * * @param array $els The previously found elements. * @param RemoteWebElement $form The form in which to search for fields. * @param string $name The field's name. * @param mixed $values * @return void */ protected function pushFormField(&$els, $form, $name, $values) { $el = $form->findElements(WebDriverBy::name($name)); if ($el) { $els[] = [$el, $values]; } elseif (is_array($values)) { foreach ($values as $key => $value) { $this->pushFormField($els, $form, "{$name}[$key]", $value); } } else { throw new ElementNotFound($name); } } /** * @param RemoteWebElement[] $elements * @param $value * @return array */ protected function proceedSeeInField(array $elements, $value) { $strField = reset($elements)->getAttribute('name'); if (reset($elements)->getTagName() === 'select') { $el = reset($elements); $elements = $el->findElements(WebDriverBy::xpath('.//option')); if (empty($value) && empty($elements)) { return ['True', true]; } } $currentValues = []; if (is_bool($value)) { $currentValues = [false]; } foreach ($elements as $el) { switch ($el->getTagName()) { case 'input': if ($el->getAttribute('type') === 'radio' || $el->getAttribute('type') === 'checkbox') { if ($el->getAttribute('checked')) { if (is_bool($value)) { $currentValues = [true]; break; } else { $currentValues[] = $el->getAttribute('value'); } } } else { $currentValues[] = $el->getAttribute('value'); } break; case 'option': // no break we need the trim text and the value also if (!$el->isSelected()) { break; } $currentValues[] = $el->getText(); case 'textarea': // we include trimmed and real value of textarea for check $currentValues[] = trim($el->getText()); default: $currentValues[] = $el->getAttribute('value'); // raw value break; } } return [ 'Contains', $value, $currentValues, "Failed testing for '$value' in $strField's value: '" . implode("', '", $currentValues) . "'" ]; } public function selectOption($select, $option) { $el = $this->findField($select); if ($el->getTagName() != 'select') { $els = $this->matchCheckables($select); $radio = null; foreach ($els as $el) { $radio = $this->findCheckable($el, $option, true); if ($radio) { break; } } if (!$radio) { throw new ElementNotFound($select, "Radiobutton with value or name '$option in"); } $radio->click(); return; } $wdSelect = new WebDriverSelect($el); if ($wdSelect->isMultiple()) { $wdSelect->deselectAll(); } if (!is_array($option)) { $option = [$option]; } $matched = false; if (key($option) !== 'value') { foreach ($option as $opt) { try { $wdSelect->selectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } if (key($option) !== 'text') { foreach ($option as $opt) { try { $wdSelect->selectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } // partially matching foreach ($option as $opt) { try { $optElement = $el->findElement(WebDriverBy::xpath('.//option [contains (., "' . $opt . '")]')); $matched = true; if (!$optElement->isSelected()) { $optElement->click(); } } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } /** * Manually starts a new browser session. * * ```php * getModule('WebDriver')->_initializeSession(); * ``` * * @api */ public function _initializeSession() { try { $this->sessions[] = $this->webDriver; $this->webDriver = RemoteWebDriver::create( $this->wdHost, $this->capabilities, $this->connectionTimeoutInMs, $this->requestTimeoutInMs, $this->webdriverProxy, $this->webdriverProxyPort ); if (!is_null($this->config['pageload_timeout'])) { $this->webDriver->manage()->timeouts()->pageLoadTimeout($this->config['pageload_timeout']); } $this->setBaseElement(); $this->initialWindowSize(); } catch (WebDriverCurlException $e) { codecept_debug('Curl error: ' . $e->getMessage()); throw new ConnectionException("Can't connect to WebDriver at {$this->wdHost}. Make sure that ChromeDriver, GeckoDriver or Selenium Server is running."); } } /** * Loads current RemoteWebDriver instance as a session * * @api * @param RemoteWebDriver $session */ public function _loadSession($session) { $this->webDriver = $session; $this->setBaseElement(); } /** * Returns current WebDriver session for saving * * @api * @return RemoteWebDriver */ public function _backupSession() { return $this->webDriver; } /** * Manually closes current WebDriver session. * * ```php * getModule('WebDriver')->_closeSession(); * * // close a specific session * $webDriver = $this->getModule('WebDriver')->webDriver; * $this->getModule('WebDriver')->_closeSession($webDriver); * ``` * * @api * @param $webDriver (optional) a specific webdriver session instance */ public function _closeSession($webDriver = null) { if (!$webDriver and $this->webDriver) { $webDriver = $this->webDriver; } if (!$webDriver) { return; } try { $webDriver->quit(); unset($webDriver); } catch (UnknownServerException $e) { // Session already closed so nothing to do } catch (UnknownErrorException $e) { // Session already closed so nothing to do } } /** * Unselect an option in the given select box. * * @param $select * @param $option */ public function unselectOption($select, $option) { $el = $this->findField($select); $wdSelect = new WebDriverSelect($el); if (!is_array($option)) { $option = [$option]; } $matched = false; foreach ($option as $opt) { try { $wdSelect->deselectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } try { $wdSelect->deselectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } /** * @param $context * @param $radioOrCheckbox * @param bool $byValue * @return mixed|null */ protected function findCheckable($context, $radioOrCheckbox, $byValue = false) { if ($radioOrCheckbox instanceof WebDriverElement) { return $radioOrCheckbox; } if (is_array($radioOrCheckbox) or ($radioOrCheckbox instanceof WebDriverBy)) { return $this->matchFirstOrFail($this->getBaseElement(), $radioOrCheckbox); } $locator = static::xpathLiteral($radioOrCheckbox); if ($context instanceof WebDriverElement && $context->getTagName() === 'input') { $contextType = $context->getAttribute('type'); if (!in_array($contextType, ['checkbox', 'radio'], true)) { return null; } $nameLiteral = static::xpathLiteral($context->getAttribute('name')); $typeLiteral = static::xpathLiteral($contextType); $inputLocatorFragment = "input[@type = $typeLiteral][@name = $nameLiteral]"; $xpath = Locator::combine( // @codingStandardsIgnoreStart "ancestor::form//{$inputLocatorFragment}[(@id = ancestor::form//label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator]", // @codingStandardsIgnoreEnd "ancestor::form//label[contains(normalize-space(string(.)), $locator)]//{$inputLocatorFragment}" ); if ($byValue) { $xpath = Locator::combine($xpath, "ancestor::form//{$inputLocatorFragment}[@value = $locator]"); } } else { $xpath = Locator::combine( // @codingStandardsIgnoreStart "//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator or @name = $locator]", // @codingStandardsIgnoreEnd "//label[contains(normalize-space(string(.)), $locator)]//input[@type = 'radio' or @type = 'checkbox']" ); if ($byValue) { $xpath = Locator::combine($xpath, "//input[@type = 'checkbox' or @type = 'radio'][@value = $locator]"); } } $els = $context->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } $els = $context->findElements(WebDriverBy::xpath(str_replace('ancestor::form', '', $xpath))); if (count($els)) { return reset($els); } $els = $this->match($context, $radioOrCheckbox); if (count($els)) { return reset($els); } return null; } protected function matchCheckables($selector) { $els = $this->match($this->webDriver, $selector); if (!count($els)) { throw new ElementNotFound($selector, "Element containing radio by CSS or XPath"); } return $els; } public function checkOption($option) { $field = $this->findCheckable($this->webDriver, $option); if (!$field) { throw new ElementNotFound($option, "Checkbox or Radio by Label or CSS or XPath"); } if ($field->isSelected()) { return; } $field->click(); } public function uncheckOption($option) { $field = $this->findCheckable($this->getBaseElement(), $option); if (!$field) { throw new ElementNotFound($option, "Checkbox by Label or CSS or XPath"); } if (!$field->isSelected()) { return; } $field->click(); } public function fillField($field, $value) { $el = $this->findField($field); $el->clear(); $el->sendKeys((string)$value); } /** * Clears given field which isn't empty. * * ``` php * clearField('#username'); * ``` * * @param $field */ public function clearField($field) { $el = $this->findField($field); $el->clear(); } /** * Type in characters on active element. * With a second parameter you can specify delay between key presses. * * ```php * click('#input'); * * // type text in active element * $I->type('Hello world'); * * // type text with a 1sec delay between chars * $I->type('Hello World', 1); * ``` * * This might be useful when you an input reacts to typing and you need to slow it down to emulate human behavior. * For instance, this is how Credit Card fields can be filled in. * * @param $text * @param $delay [sec] */ public function type($text, $delay = 0) { $keys = str_split($text); foreach ($keys as $key) { sleep($delay); $this->webDriver->getKeyboard()->pressKey($key); } sleep($delay); } public function attachFile($field, $filename) { $el = $this->findField($field); // in order to be compatible on different OS $filePath = codecept_data_dir() . $filename; if (!file_exists($filePath)) { throw new \InvalidArgumentException("File does not exist: $filePath"); } if (!is_readable($filePath)) { throw new \InvalidArgumentException("File is not readable: $filePath"); } // in order for remote upload to be enabled $el->setFileDetector(new LocalFileDetector()); // skip file detector for phantomjs if ($this->isPhantom()) { $el->setFileDetector(new UselessFileDetector()); } $el->sendKeys(realpath($filePath)); } /** * Grabs all visible text from the current page. * * @return string */ protected function getVisibleText() { if ($this->getBaseElement() instanceof RemoteWebElement) { return $this->getBaseElement()->getText(); } $els = $this->getBaseElement()->findElements(WebDriverBy::cssSelector('body')); if (isset($els[0])) { return $els[0]->getText(); } return ''; } public function grabTextFrom($cssOrXPathOrRegex) { $els = $this->match($this->getBaseElement(), $cssOrXPathOrRegex, false); if (count($els)) { return $els[0]->getText(); } if (@preg_match($cssOrXPathOrRegex, $this->webDriver->getPageSource(), $matches)) { return $matches[1]; } throw new ElementNotFound($cssOrXPathOrRegex, 'CSS or XPath or Regex'); } public function grabAttributeFrom($cssOrXpath, $attribute) { $el = $this->matchFirstOrFail($this->getBaseElement(), $cssOrXpath); return $el->getAttribute($attribute); } public function grabValueFrom($field) { $el = $this->findField($field); // value of multiple select is the value of the first selected option if ($el->getTagName() == 'select') { $select = new WebDriverSelect($el); return $select->getFirstSelectedOption()->getAttribute('value'); } return $el->getAttribute('value'); } public function grabMultiple($cssOrXpath, $attribute = null) { $els = $this->match($this->getBaseElement(), $cssOrXpath); return array_map( function (WebDriverElement $e) use ($attribute) { if ($attribute) { return $e->getAttribute($attribute); } return $e->getText(); }, $els ); } protected function filterByAttributes($els, array $attributes) { foreach ($attributes as $attr => $value) { $els = array_filter( $els, function (WebDriverElement $el) use ($attr, $value) { return $el->getAttribute($attr) == $value; } ); } return $els; } public function seeElement($selector, $attributes = []) { $this->enableImplicitWait(); $els = $this->matchVisible($selector); $this->disableImplicitWait(); $els = $this->filterByAttributes($els, $attributes); $this->assertNotEmpty($els); } public function dontSeeElement($selector, $attributes = []) { $els = $this->matchVisible($selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } /** * Checks that the given element exists on the page, even it is invisible. * * ``` php * seeElementInDOM('//form/input[type=hidden]'); * ?> * ``` * * @param $selector * @param array $attributes */ public function seeElementInDOM($selector, $attributes = []) { $this->enableImplicitWait(); $els = $this->match($this->getBaseElement(), $selector); $els = $this->filterByAttributes($els, $attributes); $this->disableImplicitWait(); $this->assertNotEmpty($els); } /** * Opposite of `seeElementInDOM`. * * @param $selector * @param array $attributes */ public function dontSeeElementInDOM($selector, $attributes = []) { $els = $this->match($this->getBaseElement(), $selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } public function seeNumberOfElements($selector, $expected) { $counted = count($this->matchVisible($selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeNumberOfElementsInDOM($selector, $expected) { $counted = count($this->match($this->getBaseElement(), $selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertNotEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function dontSeeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesNotContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function seeInTitle($title) { $this->assertStringContainsString($title, $this->webDriver->getTitle()); } public function dontSeeInTitle($title) { $this->assertStringNotContainsString($title, $this->webDriver->getTitle()); } /** * Accepts the active JavaScript native popup window, as created by `window.alert`|`window.confirm`|`window.prompt`. * Don't confuse popups with modal windows, * as created by [various libraries](http://jster.net/category/windows-modals-popups). */ public function acceptPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->accept(); } /** * Dismisses the active JavaScript popup, as created by `window.alert`, `window.confirm`, or `window.prompt`. */ public function cancelPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->dismiss(); } /** * Checks that the active JavaScript popup, * as created by `window.alert`|`window.confirm`|`window.prompt`, contains the given string. * * @param $text * * @throws \Codeception\Exception\ModuleException */ public function seeInPopup($text) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $alert = $this->webDriver->switchTo()->alert(); try { $this->assertStringContainsString($text, $alert->getText()); } catch (\PHPUnit\Framework\AssertionFailedError $e) { $alert->dismiss(); throw $e; } } /** * Checks that the active JavaScript popup, * as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string. * * @param $text * * @throws \Codeception\Exception\ModuleException */ public function dontSeeInPopup($text) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $alert = $this->webDriver->switchTo()->alert(); try { $this->assertStringNotContainsString($text, $alert->getText()); } catch (\PHPUnit\Framework\AssertionFailedError $e) { $alert->dismiss(); throw $e; } } /** * Enters text into a native JavaScript prompt popup, as created by `window.prompt`. * * @param $keys * * @throws \Codeception\Exception\ModuleException */ public function typeInPopup($keys) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->sendKeys($keys); } /** * Reloads the current page. */ public function reloadPage() { $this->webDriver->navigate()->refresh(); } /** * Moves back in history. */ public function moveBack() { $this->webDriver->navigate()->back(); $this->debug($this->_getCurrentUri()); } /** * Moves forward in history. */ public function moveForward() { $this->webDriver->navigate()->forward(); $this->debug($this->_getCurrentUri()); } protected function getSubmissionFormFieldName($name) { if (substr($name, -2) === '[]') { return substr($name, 0, -2); } return $name; } /** * Submits the given form on the page, optionally with the given form * values. Give the form fields values as an array. Note that hidden fields * can't be accessed. * * Skipped fields will be filled by their values from the page. * You don't need to click the 'Submit' button afterwards. * This command itself triggers the request to form's action. * * You can optionally specify what button's value to include * in the request with the last parameter as an alternative to * explicitly setting its value in the second parameter, as * button values are not otherwise included in the request. * * Examples: * * ``` php * submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ]); * // or * $I->submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ], 'submitButtonName'); * * ``` * * For example, given this sample "Sign Up" form: * * ``` html *
    * Login: *
    * Password: *
    * Do you agree to our terms? *
    * Select pricing plan: * * *
    * ``` * * You could write the following to submit it: * * ``` php * submitForm( * '#userForm', * [ * 'user[login]' => 'Davert', * 'user[password]' => '123456', * 'user[agree]' => true * ], * 'submitButton' * ); * ``` * Note that "2" will be the submitted value for the "plan" field, as it is * the selected option. * * Also note that this differs from PhpBrowser, in that * ```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment. * Named array keys *must* be included in the name as above. * * Pair this with seeInFormFields for quick testing magic. * * ``` php * 'value', * 'field2' => 'another value', * 'checkbox1' => true, * // ... * ]; * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed * $I->seeInFormFields('//form[@id=my-form]', $form); * ?> * ``` * * Parameter values must be set to arrays for multiple input fields * of the same name, or multi-select combo boxes. For checkboxes, * either the string value can be used, or boolean values which will * be replaced by the checkbox's value in the DOM. * * ``` php * submitForm('#my-form', [ * 'field1' => 'value', * 'checkbox' => [ * 'value of first checkbox', * 'value of second checkbox', * ], * 'otherCheckboxes' => [ * true, * false, * false, * ], * 'multiselect' => [ * 'first option value', * 'second option value', * ] * ]); * ?> * ``` * * Mixing string and boolean values for a checkbox's value is not supported * and may produce unexpected results. * * Field names ending in "[]" must be passed without the trailing square * bracket characters, and must contain an array for its value. This allows * submitting multiple values with the same name, consider: * * ```php * $I->submitForm('#my-form', [ * 'field[]' => 'value', * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` * * The solution is to pass an array value: * * ```php * // this way both values are submitted * $I->submitForm('#my-form', [ * 'field' => [ * 'value', * 'another value', * ] * ]); * ``` * * The `$button` parameter can be either a string, an array or an instance * of Facebook\WebDriver\WebDriverBy. When it is a string, the * button will be found by its "name" attribute. If $button is an * array then it will be treated as a strict selector and a WebDriverBy * will be used verbatim. * * For example, given the following HTML: * * ``` html * * ``` * * `$button` could be any one of the following: * - 'submitButton' * - ['name' => 'submitButton'] * - WebDriverBy::name('submitButton') * * @param $selector * @param $params * @param $button */ public function submitForm($selector, array $params, $button = null) { $form = $this->matchFirstOrFail($this->getBaseElement(), $selector); $fields = $form->findElements( WebDriverBy::cssSelector('input:enabled,textarea:enabled,select:enabled,input[type=hidden]') ); foreach ($fields as $field) { $fieldName = $this->getSubmissionFormFieldName($field->getAttribute('name')); if (!isset($params[$fieldName])) { continue; } $value = $params[$fieldName]; if (is_array($value) && $field->getTagName() !== 'select') { if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { $found = false; foreach ($value as $index => $val) { if (!is_bool($val) && $val === $field->getAttribute('value')) { array_splice($params[$fieldName], $index, 1); $value = $val; $found = true; break; } } if (!$found && !empty($value) && is_bool(reset($value))) { $value = array_pop($params[$fieldName]); } } else { $value = array_pop($params[$fieldName]); } } if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { if ($value === true || $value === $field->getAttribute('value')) { $this->checkOption($field); } else { $this->uncheckOption($field); } } elseif ($field->getAttribute('type') === 'button' || $field->getAttribute('type') === 'submit') { continue; } elseif ($field->getTagName() === 'select') { $this->selectOption($field, $value); } else { $this->fillField($field, $value); } } $this->debugSection( 'Uri', $form->getAttribute('action') ? $form->getAttribute('action') : $this->_getCurrentUri() ); $this->debugSection('Method', $form->getAttribute('method') ? $form->getAttribute('method') : 'GET'); $this->debugSection('Parameters', json_encode($params)); $submitted = false; if (!empty($button)) { if (is_array($button)) { $buttonSelector = $this->getStrictLocator($button); } elseif ($button instanceof WebDriverBy) { $buttonSelector = $button; } else { $buttonSelector = WebDriverBy::name($button); } $els = $form->findElements($buttonSelector); if (!empty($els)) { $el = reset($els); $el->click(); $submitted = true; } } if (!$submitted) { $form->submit(); } $this->debugSection('Page', $this->_getCurrentUri()); } /** * Waits up to $timeout seconds for the given element to change. * Element "change" is determined by a callback function which is called repeatedly * until the return value evaluates to true. * * ``` php * waitForElementChange('#menu', function(WebDriverElement $el) { * return $el->isDisplayed(); * }, 100); * ?> * ``` * * @param $element * @param \Closure $callback * @param int $timeout seconds * @throws \Codeception\Exception\ElementNotFound */ public function waitForElementChange($element, \Closure $callback, $timeout = 30) { $el = $this->matchFirstOrFail($this->getBaseElement(), $element); $checker = function () use ($el, $callback) { return $callback($el); }; $this->webDriver->wait($timeout)->until($checker); } /** * Waits up to $timeout seconds for an element to appear on the page. * If the element doesn't appear, a timeout exception is thrown. * * ``` php * waitForElement('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElement($element, $timeout = 10) { $condition = WebDriverExpectedCondition::presenceOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to be visible on the page. * If element doesn't appear, a timeout exception is thrown. * * ``` php * waitForElementVisible('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::visibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to become invisible. * If element stays visible, a timeout exception is thrown. * * ``` php * waitForElementNotVisible('#agree_button', 30); // secs * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementNotVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::invisibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to be clickable. * If element doesn't become clickable, a timeout exception is thrown. * * ``` php * waitForElementClickable('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementClickable($element, $timeout = 10) { $condition = WebDriverExpectedCondition::elementToBeClickable($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given string to appear on the page. * * Can also be passed a selector to search in, be as specific as possible when using selectors. * waitForText() will only watch the first instance of the matching selector / text provided. * If the given text doesn't appear, a timeout exception is thrown. * * ``` php * waitForText('foo', 30); // secs * $I->waitForText('foo', 30, '.title'); // secs * ?> * ``` * * @param string $text * @param int $timeout seconds * @param string $selector optional * @throws \Exception */ public function waitForText($text, $timeout = 10, $selector = null) { $message = sprintf( 'Waited for %d secs but text %s still not found', $timeout, Locator::humanReadableString($text) ); if (!$selector) { $condition = WebDriverExpectedCondition::textToBePresentInElement(WebDriverBy::xpath('//body'), $text); $this->webDriver->wait($timeout)->until($condition, $message); return; } $condition = WebDriverExpectedCondition::textToBePresentInElement($this->getLocator($selector), $text); $this->webDriver->wait($timeout)->until($condition, $message); } /** * Wait for $timeout seconds. * * @param int|float $timeout secs * @throws \Codeception\Exception\TestRuntimeException */ public function wait($timeout) { if ($timeout >= 1000) { throw new TestRuntimeException( " Waiting for more then 1000 seconds: 16.6667 mins\n Please note that wait method accepts number of seconds as parameter." ); } usleep($timeout * 1000000); } /** * Low-level API method. * If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly: * * ``` php * $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $webdriver->get('http://google.com'); * }); * ``` * * This runs in the context of the * [RemoteWebDriver class](https://github.com/php-webdriver/php-webdriver/blob/master/lib/remote/RemoteWebDriver.php). * Try not to use this command on a regular basis. * If Codeception lacks a feature you need, please implement it and submit a patch. * * @param callable $function */ public function executeInSelenium(\Closure $function) { return $function($this->webDriver); } /** * Switch to another window identified by name. * * The window can only be identified by name. If the $name parameter is blank, the parent window will be used. * * Example: * ``` html * * ``` * * ``` php * click("Open window"); * # switch to another window * $I->switchToWindow("another_window"); * # switch to parent window * $I->switchToWindow(); * ?> * ``` * * If the window has no name, match it by switching to next active tab using `switchToNextTab` method. * * Or use native Selenium functions to get access to all opened windows: * * ``` php * executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $handles=$webdriver->getWindowHandles(); * $last_window = end($handles); * $webdriver->switchTo()->window($last_window); * }); * ?> * ``` * * @param string|null $name */ public function switchToWindow($name = null) { $this->webDriver->switchTo()->window($name); } /** * Switch to another iframe on the page. * * Example: * ``` html *