diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml new file mode 100644 index 0000000..a46ffeb --- /dev/null +++ b/.github/workflows/php-cs-fixer.yml @@ -0,0 +1,31 @@ +name: Check styling + +on: [pull_request] + +permissions: + contents: read + +jobs: + style: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP for checking styling + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + extensions: mbstring, pdo, xml, xdebug + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install PHP dependencies for checking styling + run: composer install --no-cache --no-interaction --no-progress --ignore-platform-reqs + + - name: Check styling + run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --dry-run --stop-on-violation --using-cache=no --path-mode=intersection ./src ./tests diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..0ca89c6 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,36 @@ +name: PHPStan + +on: + pull_request: + paths: + - '**.php' + - 'phpstan.neon.dist' + +permissions: + contents: read + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP for PHPStan + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: mbstring, pdo, xml + + - name: Install PHPStan dependencies + run: composer install --no-cache --no-interaction --no-progress --ignore-platform-reqs + + - name: Run PHPStan + run: | + ./vendor/bin/phpstan --error-format=github analyse src/ + exit_code=$? + if [ $exit_code -ne 0 ]; then + echo "PHPStan analysis failed with exit code $exit_code" + exit $exit_code + fi diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..7b59834 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,35 @@ +name: Run tests + +on: [pull_request] + +permissions: + contents: read + +jobs: + php-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + php: [8.3, 8.2, 8.1, 8.0, 7.4] + dependency-version: [prefer-lowest, prefer-stable] + os: [ubuntu-latest] + + name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -t lib_webtopay_ci --build-arg PHP_VER=${{ matrix.php }} . + + - name: Run tests in Docker container + run: | + docker run --rm \ + -u root \ + -v $GITHUB_WORKSPACE:/var/www \ + lib_webtopay_ci \ + /bin/bash -c "mkdir -p /var/www/vendor && composer install --no-cache --no-interaction --no-progress --ignore-platform-reqs && composer run phpunit" + + diff --git a/.gitignore b/.gitignore index 8b7ef35..1e43c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /vendor -composer.lock +.composer +.phpunit.result.cache +coverage +.bash_history +.php-cs-fixer.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..fa6dfde --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +in([ + __DIR__ . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'tests', + ]) +; + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + '@PHP82Migration' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + 'declare_strict_types' => true, + ]) + ->setFinder($finder) +; diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7e1c7..dfe14fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ Version history =============== +Version 3.0.0 - 2024-04-30 + + * increased minimal PHP version to 7.4 + * the code was brought up to date with the latest PHP versions and standards + * removed deprecated methods + * removed unused code + * fixed some bugs + Version 2.0.1 - 2023-10-19 * fixed versioning texts at changelog diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b9f02e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +ARG PHP_VER +FROM php:${PHP_VER}-fpm + +RUN groupmod -g 1000 www-data && usermod -u 1000 -g 1000 www-data + +RUN pecl install pcov \ + && docker-php-ext-enable pcov + +ADD https://github.com/mlocati/docker-php-extension-installer/releases/download/2.1.2/install-php-extensions /usr/local/bin/ + +RUN chmod +x /usr/local/bin/install-php-extensions +RUN install-php-extensions \ + xdebug \ + @composer + +RUN echo "\n[PHP]" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "error_reporting=E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "memory_limit=512M" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "upload_max_filesize=16M" >> /usr/local/etc/php/conf.d/docker-fpm.ini \ + && echo "max_post_size=16M" >> /usr/local/etc/php/conf.d/docker-fpm.ini + +RUN echo "\n[xdebug]" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "zend_extension=xdebug.so" > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.mode=develop,debug,coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_discovery_header=\"\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_host=\"host.docker.internal\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.discover_client_host=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.output_dir = \"/var/log/nginx/xdebug.log\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.remote_cookie_expire_time=3600" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.start_with_request=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.max_nesting_level=512" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ + && echo "xdebug.log_level=0" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + +USER 1000 +WORKDIR /var/www diff --git a/Dockerfile_build b/Dockerfile_build new file mode 100644 index 0000000..462e9db --- /dev/null +++ b/Dockerfile_build @@ -0,0 +1,13 @@ +FROM php:7.4-fpm-buster + +RUN groupmod -g 1000 www-data && usermod -u 1000 -g 1000 www-data + +ADD https://github.com/mlocati/docker-php-extension-installer/releases/download/2.1.2/install-php-extensions /usr/local/bin/ + +RUN chmod +x /usr/local/bin/install-php-extensions +RUN install-php-extensions \ + xdebug \ + @composer + +USER 1000 +WORKDIR /var/www diff --git a/README.md b/README.md index 49889e5..247b65d 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Example: $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRequest($data) { - if (!isset($data['sign_password']) || !isset($data['projectid'])) { - throw new WebToPayException('sign_password or projectid is not provided'); - } + public static function buildRequest(array $data): array + { + self::checkRequiredParameters($data); + $password = $data['sign_password']; $projectId = $data['projectid']; unset($data['sign_password']); unset($data['projectid']); - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRequest($data); } - /** * Builds request and redirects user to payment window with generated request data * * Possible array keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request. + * @param array $data Information about current payment request. * @param boolean $exit if true, exits after sending Location header; default false * * @throws WebToPayException on data validation error */ - public static function redirectToPayment($data, $exit = false) { - if (!isset($data['sign_password']) || !isset($data['projectid'])) { - throw new WebToPayException('sign_password or projectid is not provided'); - } + public static function redirectToPayment(array $data, bool $exit = false): void + { + self::checkRequiredParameters($data); + $password = $data['sign_password']; $projectId = $data['projectid']; unset($data['sign_password']); unset($data['projectid']); - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $url = $factory->getRequestBuilder() ->buildRequestUrlFromData($data); - if (headers_sent()) { + if (WebToPay_Functions::headers_sent()) { echo ''; } else { header("Location: $url", true); @@ -122,7 +127,9 @@ public static function redirectToPayment($data, $exit = false) { htmlentities($url, ENT_QUOTES, 'UTF-8') ); if ($exit) { + // @codeCoverageIgnoreStart exit(); + // @codeCoverageIgnoreEnd } } @@ -136,13 +143,14 @@ public static function redirectToPayment($data, $exit = false) { * keys are described here: * https://developers.paysera.com/en/checkout/integrations/integration-specification * - * @param array $data Information about current payment request + * @param array $data Information about current payment request * - * @return array + * @return array * * @throws WebToPayException on data validation error */ - public static function buildRepeatRequest($data) { + public static function buildRepeatRequest(array $data): array + { if (!isset($data['sign_password']) || !isset($data['projectid']) || !isset($data['orderid'])) { throw new WebToPayException('sign_password, projectid or orderid is not provided'); } @@ -150,95 +158,59 @@ public static function buildRepeatRequest($data) { $projectId = $data['projectid']; $orderId = $data['orderid']; - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $requestBuilder = $factory->getRequestBuilder(); + return $requestBuilder->buildRepeatRequest($orderId); } /** * Returns payment url. Argument is same as lang parameter in request data * - * @param string $language + * @param string $language * @return string $url */ - public static function getPaymentUrl($language = 'LIT') { - return (in_array($language, array('lt', 'lit', 'LIT'))) - ? self::PAY_URL - : self::PAYSERA_PAY_URL; - } - - /** - * Parses response from WebToPay server and validates signs. - * - * This function accepts both micro and macro responses. - * - * First parameter usualy should be $_GET array. - * - * Description about response can be found here: - * makro: https://developers.paysera.com/en/checkout/integrations/integration-specification - * mikro: https://developers.paysera.com/en/sms-keywords/current#sms_keywords_specification - * - * If response is not correct, WebToPayException will be raised. - * - * @param array $query Response array - * @param array $userData - * - * @return array - * - * @throws WebToPayException - * @deprecated use validateAndParseData() and check status code yourself - */ - public static function checkResponse($query, $userData = array()) { - $projectId = isset($userData['projectid']) ? $userData['projectid'] : null; - $password = isset($userData['sign_password']) ? $userData['sign_password'] : null; - $logFile = isset($userData['log']) ? $userData['log'] : null; - - try { - $data = self::validateAndParseData($query, $projectId, $password); - if ($data['type'] == 'macro' && $data['status'] != 1) { - throw new WebToPayException('Expected status code 1', WebToPayException::E_DEPRECATED_USAGE); - } - - if ($logFile) { - self::log('OK', http_build_query($data, '', '&'), $logFile); - } - return $data; - - } catch (WebToPayException $exception) { - if ($logFile && $exception->getCode() != WebToPayException::E_DEPRECATED_USAGE) { - self::log('ERR', $exception . "\nQuery: " . http_build_query($query, '', '&'), $logFile); - } - throw $exception; - } + public static function getPaymentUrl(string $language = 'LIT'): string + { + return (in_array($language, ['lt', 'lit', 'LIT'], true)) + ? self::PAY_URL + : self::PAYSERA_PAY_URL; } /** * Parses request (query) data and validates its signature. * - * @param array $query usually $_GET - * @param integer $projectId - * @param string $password + * @param array $query usually $_GET + * @param int|null $projectId + * @param string|null $password * - * @return array + * @return array * * @throws WebToPayException + * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration */ - public static function validateAndParseData(array $query, $projectId, $password) { - $factory = new WebToPay_Factory(array('projectId' => $projectId, 'password' => $password)); + public static function validateAndParseData(array $query, ?int $projectId, ?string $password): array + { + $factory = new WebToPay_Factory(['projectId' => $projectId, 'password' => $password]); $validator = $factory->getCallbackValidator(); - $data = $validator->validateAndParseData($query); - return $data; + + return $validator->validateAndParseData($query); } /** * Sends SMS answer * - * @param array $userData + * @param array $userData * * @throws WebToPayException * @throws WebToPay_Exception_Validation + * + * @deprecated + * @codeCoverageIgnore */ - public static function smsAnswer($userData) { + public static function smsAnswer(array $userData): void + { if (!isset($userData['id']) || !isset($userData['msg']) || !isset($userData['sign_password'])) { throw new WebToPay_Exception_Validation('id, msg and sign_password are required'); } @@ -246,11 +218,11 @@ public static function smsAnswer($userData) { $smsId = $userData['id']; $text = $userData['msg']; $password = $userData['sign_password']; - $logFile = isset($userData['log']) ? $userData['log'] : null; + $logFile = $userData['log'] ?? null; try { - $factory = new WebToPay_Factory(array('password' => $password)); + $factory = new WebToPay_Factory(['password' => $password]); $factory->getSmsAnswerSender()->sendAnswer($smsId, $text); if ($logFile) { @@ -263,10 +235,8 @@ public static function smsAnswer($userData) { } throw $e; } - } - /** * Gets available payment methods for project. Gets methods min and max amounts in specified currency. * @@ -278,8 +248,13 @@ public static function smsAnswer($userData) { * * @throws WebToPayException */ - public static function getPaymentMethodList($projectId, $amount, $currency = 'EUR') { - $factory = new WebToPay_Factory(array('projectId' => $projectId)); + public static function getPaymentMethodList( + int $projectId, + float $amount, + string $currency = 'EUR' + ): WebToPay_PaymentMethodList { + $factory = new WebToPay_Factory(['projectId' => $projectId]); + return $factory->getPaymentMethodListProvider()->getPaymentMethodList($amount, $currency); } @@ -289,20 +264,24 @@ public static function getPaymentMethodList($projectId, $amount, $currency = 'EU * @param string $type * @param string $msg * @param string $logfile + * + * @deprecated + * @codeCoverageIgnore */ - protected static function log($type, $msg, $logfile) { + protected static function log(string $type, string $msg, string $logfile): void + { $fp = @fopen($logfile, 'a'); if (!$fp) { return; } - $logline = array( + $logline = [ $type, - isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-', + $_SERVER['REMOTE_ADDR'] ?? '-', date('[Y-m-d H:i:s O]'), 'v' . self::VERSION . ':', - $msg - ); + $msg, + ]; $logline = implode(' ', $logline)."\n"; fwrite($fp, $logline); @@ -314,153 +293,163 @@ protected static function log($type, $msg, $logfile) { unlink($logfile); } } -} - + /** + * @param array $data + * + * @throws WebToPayException + */ + protected static function checkRequiredParameters(array $data): void + { + if (!isset($data['sign_password']) || !isset($data['projectid'])) { + throw new WebToPayException('sign_password or projectid is not provided'); + } + } +} /** * Base exception class for all exceptions in this library */ -class WebToPayException extends Exception { - +class WebToPayException extends Exception +{ /** * Missing field. */ - const E_MISSING = 1; + public const E_MISSING = 1; /** * Invalid field value. */ - const E_INVALID = 2; + public const E_INVALID = 2; /** * Max length exceeded. */ - const E_MAXLEN = 3; + public const E_MAXLEN = 3; /** * Regexp for field value doesn't match. */ - const E_REGEXP = 4; + public const E_REGEXP = 4; /** * Missing or invalid user given parameters. */ - const E_USER_PARAMS = 5; + public const E_USER_PARAMS = 5; /** * Logging errors */ - const E_LOG = 6; + public const E_LOG = 6; /** * SMS answer errors */ - const E_SMS_ANSWER = 7; + public const E_SMS_ANSWER = 7; /** * Macro answer errors */ - const E_STATUS = 8; + public const E_STATUS = 8; /** * Library errors - if this happens, bug-report should be sent; also you can check for newer version */ - const E_LIBRARY = 9; + public const E_LIBRARY = 9; /** * Errors in remote service - it returns some invalid data */ - const E_SERVICE = 10; - + public const E_SERVICE = 10; + /** * Deprecated usage errors */ - const E_DEPRECATED_USAGE = 11; + public const E_DEPRECATED_USAGE = 11; - /** - * @var string|boolean - */ - protected $fieldName = false; + protected ?string $fieldName = null; /** * Sets field which failed - * - * @param string $fieldName */ - public function setField($fieldName) { + public function setField(?string $fieldName): void + { $this->fieldName = $fieldName; } /** * Gets field which failed - * - * @return string|boolean false */ - public function getField() { + public function getField(): ?string + { return $this->fieldName; } } + /** * Class to hold information about payment method */ -class WebToPay_PaymentMethod { +class WebToPay_PaymentMethod +{ /** * Assigned key for this payment method - * - * @var string */ - protected $key; + protected string $key; + + protected ?int $minAmount; + + protected ?int $maxAmount; + + protected ?string $currency; /** * Logo url list by language. Usually logo is same for all languages, but exceptions exist * - * @var array + * @var array */ - protected $logoList; + protected array $logoList; /** * Title list by language * - * @var array + * @var array */ - protected $titleTranslations; + protected array $titleTranslations; /** * Default language to use for titles - * - * @var string */ - protected $defaultLanguage; + protected string $defaultLanguage; - /** - * @var boolean - */ - protected $isIban; + protected bool $isIban; - /** - * @var string - */ - protected $baseCurrency; + protected ?string $baseCurrency; /** * Constructs object * - * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations - * @param string $defaultLanguage - * @param bool $isIban - * @param string $baseCurrency + * @param string $key + * @param integer|null $minAmount + * @param integer|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations + * @param string $defaultLanguage + * @param bool $isIban + * @param string|null $baseCurrency */ public function __construct( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $defaultLanguage = 'lt', $isIban = false, $baseCurrency = null + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + string $defaultLanguage = 'lt', + bool $isIban = false, + ?string $baseCurrency = null ) { $this->key = $key; $this->minAmount = $minAmount; @@ -468,51 +457,44 @@ public function __construct( $this->currency = $currency; $this->logoList = $logoList; $this->titleTranslations = $titleTranslations; - $this->defaultLanguage = $defaultLanguage; - $this->isIban = $isIban; - $this->baseCurrency = $baseCurrency; + $this->setDefaultLanguage($defaultLanguage); + $this->setIsIban($isIban); + $this->setBaseCurrency($baseCurrency); } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethod */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethod + { $this->defaultLanguage = $language; + return $this; } /** * Gets default language for titles - * - * @return string */ - public function getDefaultLanguage() { + public function getDefaultLanguage(): string + { return $this->defaultLanguage; } /** * Get assigned payment method key - * - * @return string */ - public function getKey() { + public function getKey(): string + { return $this->key; } /** * Gets logo url for this payment method. Uses specified language or default one. * If logotype is not found for specified language, null is returned. - * - * @param string [Optional] $languageCode - * - * @return string|null */ - public function getLogoUrl($languageCode = null) { + public function getLogoUrl(?string $languageCode = null): ?string + { if ($languageCode !== null && isset($this->logoList[$languageCode])) { return $this->logoList[$languageCode]; } elseif (isset($this->logoList[$this->defaultLanguage])) { @@ -524,12 +506,9 @@ public function getLogoUrl($languageCode = null) { /** * Gets title for this payment method. Uses specified language or default one. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { + public function getTitle(?string $languageCode = null): string + { if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { return $this->titleTranslations[$languageCode]; } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { @@ -543,20 +522,17 @@ public function getTitle($languageCode = null) { * Checks if this payment method can be used for specified amount. * Throws exception if currency checked is not the one, for which payment method list was downloaded. * - * @param integer $amount - * @param string $currency - * - * @return boolean - * * @throws WebToPayException */ - public function isAvailableForAmount($amount, $currency) { + public function isAvailableForAmount(int $amount, string $currency): bool + { if ($this->currency !== $currency) { throw new WebToPayException( 'Currencies does not match. You have to get payment types for the currency you are checking. Given currency: ' . $currency . ', available currency: ' . $this->currency ); } + return ( ($this->minAmount === null || $amount >= $this->minAmount) && ($this->maxAmount === null || $amount <= $this->maxAmount) @@ -565,56 +541,48 @@ public function isAvailableForAmount($amount, $currency) { /** * Returns min amount for this payment method. If no min amount is specified, returns empty string. - * - * @return string */ - public function getMinAmountAsString() { + public function getMinAmountAsString(): string + { return $this->minAmount === null ? '' : ($this->minAmount . ' ' . $this->currency); } /** * Returns max amount for this payment method. If no max amount is specified, returns empty string. - * - * @return string */ - public function getMaxAmountAsString() { + public function getMaxAmountAsString(): string + { return $this->maxAmount === null ? '' : ($this->maxAmount . ' ' . $this->currency); } /** * Set if this method returns IBAN number after payment - * - * @param boolean $isIban */ - public function setIsIban($isIban) { - $this->isIban = $isIban == 1; + public function setIsIban(bool $isIban): void + { + $this->isIban = $isIban; } /** * Get if this method returns IBAN number after payment - * - * @return bool */ - public function isIban() { + public function isIban(): bool + { return $this->isIban; } /** * Setter of BaseCurrency - * - * @param string $baseCurrency */ - public function setBaseCurrency($baseCurrency) + public function setBaseCurrency(?string $baseCurrency): void { $this->baseCurrency = $baseCurrency; } /** * Getter of BaseCurrency - * - * @return string */ - public function getBaseCurrency() + public function getBaseCurrency(): ?string { return $this->baseCurrency; } @@ -622,1270 +590,917 @@ public function getBaseCurrency() /** - * Creates objects. Also caches to avoid creating several instances of same objects + * Utility class */ -class WebToPay_Factory { - - const ENV_PRODUCTION = 'production'; - const ENV_SANDBOX = 'sandbox'; +class WebToPay_Util +{ + public const GCM_CIPHER = 'aes-256-gcm'; + public const GCM_AUTH_KEY_LENGTH = 16; /** - * @var array + * Decodes url-safe-base64 encoded string + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ */ - protected static $defaultConfiguration = array( - 'routes' => array( - self::ENV_PRODUCTION => array( - 'publicKey' => 'https://www.paysera.com/download/public.key', - 'payment' => 'https://bank.paysera.com/pay/', - 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', - 'smsAnswer' => 'https://bank.paysera.com/psms/respond/', - ), - self::ENV_SANDBOX => array( - 'publicKey' => 'https://sandbox.paysera.com/download/public.key', - 'payment' => 'https://sandbox.paysera.com/pay/', - 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', - 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', - ), - ) - ); + public function decodeSafeUrlBase64(string $encodedText): string + { + return (string) base64_decode(strtr($encodedText, '-_', '+/'), true); + } /** - * @var string + * Encodes string to url-safe-base64 + * Url-safe-base64 is same as base64, but + is replaced to - and / to _ */ - protected $environment; + public function encodeSafeUrlBase64(string $text): string + { + return strtr(base64_encode($text), '+/', '-_'); + } /** - * @var array + * Decrypts string with aes-256-gcm algorithm */ - protected $configuration; + public function decryptGCM(string $stringToDecrypt, string $key): ?string + { + $ivLength = (int) openssl_cipher_iv_length(self::GCM_CIPHER); + $iv = substr($stringToDecrypt, 0, $ivLength); + $ciphertext = substr($stringToDecrypt, $ivLength, -self::GCM_AUTH_KEY_LENGTH); + $tag = substr($stringToDecrypt, -self::GCM_AUTH_KEY_LENGTH); - /** - * @var WebToPay_WebClient - */ - protected $webClient = null; + $decryptedText = openssl_decrypt( + $ciphertext, + self::GCM_CIPHER, + $key, + OPENSSL_RAW_DATA, + $iv, + $tag + ); - /** - * @var WebToPay_CallbackValidator - */ - protected $callbackValidator = null; + return $decryptedText === false ? null : $decryptedText; + } /** - * @var WebToPay_RequestBuilder + * Parses HTTP query to array + * + * @param string $query + * + * @return array */ - protected $requestBuilder = null; + public function parseHttpQuery(string $query): array + { + $params = []; + parse_str($query, $params); - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer = null; + return $params; + } +} - /** - * @var WebToPay_SmsAnswerSender - */ - protected $smsAnswerSender = null; - /** - * @var WebToPay_PaymentMethodListProvider - */ - protected $paymentMethodListProvider = null; +/** + * Raised on validation error in passed data when building the request + */ +class WebToPay_Exception_Validation extends WebToPayException +{ + public function __construct( + string $message, + int $code = 0, + ?string $field = null, + ?Exception $previousException = null + ) { + parent::__construct($message, $code, $previousException); + if ($field) { + $this->setField($field); + } + } +} + + +/** + * Raised on error in callback + */ +class WebToPay_Exception_Callback extends WebToPayException +{ +} + + +/** + * Raised if configuration is incorrect + */ +class WebToPay_Exception_Configuration extends WebToPayException +{ +} + + +/** + * The class is used for manipulating with behavior of functions in the global namespace. + * It is used for testing purposes. No payload. + * + * @codeCoverageIgnore + */ +class WebToPay_Functions +{ + public static function function_exists(string $functionName): bool + { + return \function_exists($functionName); + } + + public static function headers_sent(): bool + { + return \headers_sent(); + } +} + + +/** + * Payment method configuration for some country + */ +class WebToPay_PaymentMethodCountry +{ + protected string $countryCode; /** - * @var WebToPay_Util + * Holds available payment types for this country + * + * @var WebToPay_PaymentMethodGroup[] */ - protected $util = null; + protected array $groups; /** - * @var WebToPay_UrlBuilder + * Default language for titles */ - protected $urlBuilder = null; - + protected string $defaultLanguage; /** - * Constructs object. - * Configuration keys: projectId, password - * They are required only when some object being created needs them, - * if they are not found at that moment - exception is thrown + * Translations array for this country. Holds associative array of country title by language codes. * - * @param array $configuration + * @var array */ - public function __construct(array $configuration = array()) { - - $this->configuration = array_merge(self::$defaultConfiguration, $configuration); - $this->environment = self::ENV_PRODUCTION; - } + protected array $titleTranslations; /** - * If passed true the factory will use sandbox when constructing URLs + * Constructs object * - * @param $enableSandbox - * @return self + * @param string $countryCode + * @param array $titleTranslations + * @param string $defaultLanguage */ - public function useSandbox($enableSandbox) + public function __construct(string $countryCode, array $titleTranslations, string $defaultLanguage = 'lt') { - if ($enableSandbox) { - $this->environment = self::ENV_SANDBOX; - } else { - $this->environment = self::ENV_PRODUCTION; - } - return $this; - } - - /** - * Creates or gets callback validator instance - * - * @return WebToPay_CallbackValidator - * - * @throws WebToPay_Exception_Configuration - */ - public function getCallbackValidator() { - if ($this->callbackValidator === null) { - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - - $this->callbackValidator = new WebToPay_CallbackValidator( - $this->configuration['projectId'], - $this->getSigner(), - $this->getUtil(), - isset($this->configuration['password']) ? $this->configuration['password'] : null - ); - } - - return $this->callbackValidator; - } - - /** - * Creates or gets request builder instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_RequestBuilder - */ - public function getRequestBuilder() { - if ($this->requestBuilder === null) { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); - } - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - $this->requestBuilder = new WebToPay_RequestBuilder( - $this->configuration['projectId'], - $this->configuration['password'], - $this->getUtil(), - $this->getUrlBuilder() - ); - } - return $this->requestBuilder; - } - - /** - * @return WebToPay_UrlBuilder - */ - public function getUrlBuilder() { - if ($this->urlBuilder === null) { - $this->urlBuilder = new WebToPay_UrlBuilder( - $this->configuration, - $this->environment - ); - } - return $this->urlBuilder; - } - - /** - * Creates or gets SMS answer sender instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_SmsAnswerSender - */ - public function getSmsAnswerSender() { - if ($this->smsAnswerSender === null) { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration('You have to provide project password'); - } - $this->smsAnswerSender = new WebToPay_SmsAnswerSender( - $this->configuration['password'], - $this->getWebClient(), - $this->getUrlBuilder() - ); - } - return $this->smsAnswerSender; - } - - /** - * Creates or gets payment list provider instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_PaymentMethodListProvider - */ - public function getPaymentMethodListProvider() { - if ($this->paymentMethodListProvider === null) { - if (!isset($this->configuration['projectId'])) { - throw new WebToPay_Exception_Configuration('You have to provide project ID'); - } - $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( - $this->configuration['projectId'], - $this->getWebClient(), - $this->getUrlBuilder() - ); - } - return $this->paymentMethodListProvider; - } - - /** - * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Sign_SignCheckerInterface - * - * @throws WebToPayException - */ - protected function getSigner() { - if ($this->signer === null) { - if (function_exists('openssl_pkey_get_public')) { - $webClient = $this->getWebClient(); - $publicKey = $webClient->get($this->getUrlBuilder()->buildForPublicKey()); - if (!$publicKey) { - throw new WebToPayException('Cannot download public key from WebToPay website'); - } - $this->signer = new WebToPay_Sign_SS2SignChecker($publicKey, $this->getUtil()); - } else { - if (!isset($this->configuration['password'])) { - throw new WebToPay_Exception_Configuration( - 'You have to provide project password if OpenSSL is unavailable' - ); - } - $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); - } - } - return $this->signer; - } - - /** - * Creates or gets web client instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_WebClient - */ - protected function getWebClient() { - if ($this->webClient === null) { - $this->webClient = new WebToPay_WebClient(); - } - return $this->webClient; - } - - /** - * Creates or gets util instance - * - * @throws WebToPay_Exception_Configuration - * - * @return WebToPay_Util - */ - protected function getUtil() { - if ($this->util === null) { - $this->util = new WebToPay_Util(); - } - return $this->util; - } -} - - -/** - * Wrapper class to group payment methods. Each country can have several payment method groups, each of them - * have one or more payment methods. - */ -class WebToPay_PaymentMethodGroup { - /** - * Some unique (in the scope of country) key for this group - * - * @var string - */ - protected $groupKey; - - /** - * Translations array for this group. Holds associative array of group title by country codes. - * - * @var array - */ - protected $translations; - - /** - * Holds actual payment methods - * - * @var WebToPay_PaymentMethod[] - */ - protected $paymentMethods; - - /** - * Default language for titles - * - * @var string - */ - protected $defaultLanguage; - - /** - * Constructs object - * - * @param string $groupKey - * @param array $translations - * @param string $defaultLanguage - */ - public function __construct($groupKey, array $translations = array(), $defaultLanguage = 'lt') { - $this->groupKey = $groupKey; - $this->translations = $translations; + $this->countryCode = $countryCode; $this->defaultLanguage = $defaultLanguage; - $this->paymentMethods = array(); + $this->titleTranslations = $titleTranslations; + $this->groups = []; } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodGroup */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodCountry + { $this->defaultLanguage = $language; - foreach ($this->paymentMethods as $paymentMethod) { - $paymentMethod->setDefaultLanguage($language); + foreach ($this->groups as $group) { + $group->setDefaultLanguage($language); } - return $this; - } - /** - * Gets default language for titles - * - * @return string - */ - public function getDefaultLanguage() { - return $this->defaultLanguage; + return $this; } /** * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { - if ($languageCode !== null && isset($this->translations[$languageCode])) { - return $this->translations[$languageCode]; - } elseif (isset($this->translations[$this->defaultLanguage])) { - return $this->translations[$this->defaultLanguage]; + public function getTitle(?string $languageCode = null): string + { + if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { + return $this->titleTranslations[$languageCode]; + } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { + return $this->titleTranslations[$this->defaultLanguage]; } else { - return $this->groupKey; + return $this->countryCode; } } /** - * Returns group key - * - * @return string + * Gets default language for titles */ - public function getKey() { - return $this->groupKey; + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; } /** - * Returns available payment methods for this group - * - * @return WebToPay_PaymentMethod[] + * Gets country code */ - public function getPaymentMethods() { - return $this->paymentMethods; + public function getCode(): string + { + return $this->countryCode; } - /** - * Adds new payment method for this group. - * If some other payment method with specified key was registered earlier, overwrites it. - * Returns given payment method - * - * @param WebToPay_PaymentMethod $paymentMethod - * - * @return WebToPay_PaymentMethod + * Adds new group to payment methods for this country. + * If some other group was registered earlier with same key, overwrites it. + * Returns given group */ - public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod) { - return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; + public function addGroup(WebToPay_PaymentMethodGroup $group): WebToPay_PaymentMethodGroup + { + return $this->groups[$group->getKey()] = $group; } /** - * Gets payment method object with key. If no payment method with such key is found, returns null. - * - * @param string $key - * - * @return null|WebToPay_PaymentMethod + * Gets group object with specified group key. If no group with such key is found, returns null. */ - public function getPaymentMethod($key) { - return isset($this->paymentMethods[$key]) ? $this->paymentMethods[$key] : null; + public function getGroup(string $groupKey): ?WebToPay_PaymentMethodGroup + { + return $this->groups[$groupKey] ?? null; } /** - * Returns new group instance with only those payment methods, which are available for provided amount. - * - * @param integer $amount - * @param string $currency + * Returns payment method groups registered for this country. * - * @return WebToPay_PaymentMethodGroup + * @return WebToPay_PaymentMethodGroup[] */ - public function filterForAmount($amount, $currency) { - $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); - foreach ($this->getPaymentMethods() as $paymentMethod) { - if ($paymentMethod->isAvailableForAmount($amount, $currency)) { - $group->addPaymentMethod($paymentMethod); - } - } - return $group; + public function getGroups(): array + { + return $this->groups; } /** - * Returns new country instance with only those payment methods, which are returns or not iban number after payment - * - * @param boolean $isIban + * Gets payment methods in all groups * - * @return WebToPay_PaymentMethodGroup + * @return WebToPay_PaymentMethod[] */ - public function filterForIban($isIban = true) { - $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); - foreach ($this->getPaymentMethods() as $paymentMethod) { - if ($paymentMethod->isIban() == $isIban) { - $group->addPaymentMethod($paymentMethod); - } + public function getPaymentMethods(): array + { + $paymentMethods = []; + foreach ($this->groups as $group) { + $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); } - return $group; - } - /** - * Returns whether this group has no payment methods - * - * @return boolean - */ - public function isEmpty() { - return count($this->paymentMethods) === 0; + return $paymentMethods; } /** - * Loads payment methods from given XML node - * - * @param SimpleXMLElement $groupNode + * Returns new country instance with only those payment methods, which are available for provided amount. */ - public function fromXmlNode($groupNode) { - foreach ($groupNode->payment_type as $paymentTypeNode) { - $key = (string) $paymentTypeNode->attributes()->key; - $titleTranslations = array(); - foreach ($paymentTypeNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; - } - $logoTranslations = array(); - foreach ($paymentTypeNode->logo_url as $logoNode) { - if ((string) $logoNode !== '') { - $logoTranslations[(string) $logoNode->attributes()->language] = (string) $logoNode; - } - } - $minAmount = null; - $maxAmount = null; - $currency = null; - $isIban = false; - $baseCurrency = null; - if (isset($paymentTypeNode->min)) { - $minAmount = (int) $paymentTypeNode->min->attributes()->amount; - $currency = (string) $paymentTypeNode->min->attributes()->currency; - } - if (isset($paymentTypeNode->max)) { - $maxAmount = (int) $paymentTypeNode->max->attributes()->amount; - $currency = (string) $paymentTypeNode->max->attributes()->currency; - } - - if (isset($paymentTypeNode->is_iban)) { - $isIban = (int) $paymentTypeNode->is_iban; - } - if (isset($paymentTypeNode->base_currency)) { - $baseCurrency = (string) $paymentTypeNode->base_currency; + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodCountry + { + $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); + foreach ($this->getGroups() as $group) { + $group = $group->filterForAmount($amount, $currency); + if (!$group->isEmpty()) { + $country->addGroup($group); } - $this->addPaymentMethod($this->createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoTranslations, $titleTranslations, $isIban, $baseCurrency - )); } - } - - /** - * Method to create new payment method instances. Overwrite if you have to use some other subclass. - * - * @param string $key - * @param integer $minAmount - * @param integer $maxAmount - * @param string $currency - * @param array $logoList - * @param array $titleTranslations - * @param bool $isIban - * @param null $baseCurrency - * - * @return WebToPay_PaymentMethod - */ - protected function createPaymentMethod( - $key, $minAmount, $maxAmount, $currency, array $logoList = array(), array $titleTranslations = array(), - $isIban = false, $baseCurrency = null - ) { - return new WebToPay_PaymentMethod( - $key, $minAmount, $maxAmount, $currency, $logoList, $titleTranslations, $this->defaultLanguage, - $isIban, $baseCurrency - ); - } -} - -/** - * Parses and validates callbacks - */ -class WebToPay_CallbackValidator { - - /** - * @var WebToPay_Sign_SignCheckerInterface - */ - protected $signer; - - /** - * @var WebToPay_Util - */ - protected $util; - - /** - * @var integer - */ - protected $projectId; - - /** - * @var string|null - */ - protected $password; - /** - * Constructs object - * - * @param integer $projectId - * @param WebToPay_Sign_SignCheckerInterface $signer - * @param WebToPay_Util $util - * @param string|null $password - */ - public function __construct( - $projectId, - WebToPay_Sign_SignCheckerInterface $signer, - WebToPay_Util $util, - $password = null - ) { - $this->signer = $signer; - $this->util = $util; - $this->projectId = $projectId; - $this->password = $password; + return $country; } /** - * Parses callback parameters from query parameters and checks if sign is correct. - * Request has parameter "data", which is signed and holds all callback parameters - * - * @param array $requestData - * - * @return array Parsed callback parameters - * - * @throws WebToPayException - * @throws WebToPay_Exception_Callback + * Returns new country instance with only those payment methods, which are returns or not iban number after payment */ - public function validateAndParseData(array $requestData) { - if (!isset($requestData['data'])) { - throw new WebToPay_Exception_Callback('"data" parameter not found'); - } - - $data = $requestData['data']; - - if (isset($requestData['ss1']) || isset($requestData['ss2'])) { - if (!$this->signer->checkSign($requestData)) { - throw new WebToPay_Exception_Callback('Invalid sign parameters, check $_GET length limit'); - } - - $queryString = $this->util->decodeSafeUrlBase64($data); - } else { - if (null === $this->password) { - throw new WebToPay_Exception_Configuration('You have to provide project password'); - } - - $queryString = $this->util->decryptGCM( - $this->util->decodeSafeUrlBase64($data), - $this->password - ); + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodCountry + { + $country = new WebToPay_PaymentMethodCountry( + $this->countryCode, + $this->titleTranslations, + $this->defaultLanguage + ); - if (null === $queryString) { - throw new WebToPay_Exception_Callback('Callback data decryption failed'); + foreach ($this->getGroups() as $group) { + $group = $group->filterForIban($isIban); + if (!$group->isEmpty()) { + $country->addGroup($group); } } - $request = $this->util->parseHttpQuery($queryString); - - if (!isset($request['projectid'])) { - throw new WebToPay_Exception_Callback( - 'Project ID not provided in callback', - WebToPayException::E_INVALID - ); - } - - if ((string) $request['projectid'] !== (string) $this->projectId) { - throw new WebToPay_Exception_Callback( - sprintf('Bad projectid: %s, should be: %s', $request['projectid'], $this->projectId), - WebToPayException::E_INVALID - ); - } - if (!isset($request['type']) || !in_array($request['type'], array('micro', 'macro'))) { - $micro = ( - isset($request['to']) - && isset($request['from']) - && isset($request['sms']) - ); - $request['type'] = $micro ? 'micro' : 'macro'; - } - - return $request; - } - - /** - * Checks data to have all the same parameters provided in expected array - * - * @param array $data - * @param array $expected - * - * @throws WebToPayException - */ - public function checkExpectedFields(array $data, array $expected) { - foreach ($expected as $key => $value) { - $passedValue = isset($data[$key]) ? $data[$key] : null; - if ($passedValue != $value) { - throw new WebToPayException( - sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) - ); - } - } + return $country; } -} - - -/** - * Class with all information about available payment methods for some project, optionally filtered by some amount. - */ -class WebToPay_PaymentMethodList { - /** - * Holds available payment countries - * - * @var WebToPay_PaymentMethodCountry[] - */ - protected $countries; - - /** - * Default language for titles - * - * @var string - */ - protected $defaultLanguage; - - /** - * Project ID, to which this method list is valid - * - * @var integer - */ - protected $projectId; - - /** - * Currency for min and max amounts in this list - * - * @var string - */ - protected $currency; - - /** - * If this list is filtered for some amount, this field defines it - * - * @var integer - */ - protected $amount; /** - * Constructs object - * - * @param integer $projectId - * @param string $currency currency for min and max amounts in this list - * @param string $defaultLanguage - * @param integer $amount null if this list is not filtered by amount + * Returns whether this country has no groups */ - public function __construct($projectId, $currency, $defaultLanguage = 'lt', $amount = null) { - $this->projectId = $projectId; - $this->countries = array(); - $this->defaultLanguage = $defaultLanguage; - $this->currency = $currency; - $this->amount = $amount; + public function isEmpty(): bool + { + return count($this->groups) === 0; } /** - * Sets default language for titles. - * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodList + * Loads groups from given XML node */ - public function setDefaultLanguage($language) { - $this->defaultLanguage = $language; - foreach ($this->countries as $country) { - $country->setDefaultLanguage($language); + public function fromXmlNode(SimpleXMLElement $countryNode): void + { + foreach ($countryNode->payment_group as $groupNode) { + $key = (string) $groupNode->attributes()->key; + $titleTranslations = []; + foreach ($groupNode->title as $titleNode) { + $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + } + $this->addGroup($this->createGroup($key, $titleTranslations))->fromXmlNode($groupNode); } - return $this; } /** - * Gets default language for titles + * Method to create new group instances. Overwrite if you have to use some other group subtype. * - * @return string - */ - public function getDefaultLanguage() { - return $this->defaultLanguage; - } - - /** - * Gets project ID for this payment method list + * @param string $groupKey + * @param array $translations * - * @return integer + * @return WebToPay_PaymentMethodGroup */ - public function getProjectId() { - return $this->projectId; + protected function createGroup(string $groupKey, array $translations = []): WebToPay_PaymentMethodGroup + { + return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); } +} + + +/** + * Builds and signs requests + */ +class WebToPay_RequestBuilder +{ + private const REQUEST_SPECS = [ + ['orderid', 40, true, ''], + ['accepturl', 255, true, ''], + ['cancelurl', 255, true, ''], + ['callbackurl', 255, true, ''], + ['lang', 3, false, '/^[a-z]{3}$/i'], + ['amount', 11, false, '/^\d+$/'], + ['currency', 3, false, '/^[a-z]{3}$/i'], + ['payment', 20, false, ''], + ['country', 2, false, '/^[a-z_]{2}$/i'], + ['paytext', 255, false, ''], + ['p_firstname', 255, false, ''], + ['p_lastname', 255, false, ''], + ['p_email', 255, false, ''], + ['p_street', 255, false, ''], + ['p_city', 255, false, ''], + ['p_state', 255, false, ''], + ['p_zip', 20, false, ''], + ['p_countrycode', 2, false, '/^[a-z]{2}$/i'], + ['test', 1, false, '/^[01]$/'], + ['time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'], + ]; + + protected string $projectPassword; + + protected WebToPay_Util $util; + + protected int $projectId; + + protected WebToPay_UrlBuilder $urlBuilder; /** - * Gets currency for min and max amounts in this list - * - * @return string + * Constructs object */ - public function getCurrency() { - return $this->currency; + public function __construct( + int $projectId, + string $projectPassword, + WebToPay_Util $util, + WebToPay_UrlBuilder $urlBuilder + ) { + $this->projectId = $projectId; + $this->projectPassword = $projectPassword; + $this->util = $util; + $this->urlBuilder = $urlBuilder; } /** - * Gets whether this list is already filtered for some amount + * Builds request data array. * - * @return boolean + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. + * + * @param array $data information about current payment request + * + * @return array + * + * @throws WebToPayException */ - public function isFiltered() { - return $this->amount !== null; + public function buildRequest(array $data): array + { + $this->validateRequest($data); + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + unset($data['repeat_request']); + + return $this->createRequest($data); } /** - * Returns available countries + * Builds the full request url (including the protocol and the domain) * - * @return WebToPay_PaymentMethodCountry[] + * @param array $data + * @return string + * @throws WebToPayException */ - public function getCountries() { - return $this->countries; + public function buildRequestUrlFromData(array $data): string + { + $request = $this->buildRequest($data); + + return $this->urlBuilder->buildForRequest($request); } /** - * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. - * Returns added country instance + * Builds repeat request data array. + * + * This method checks all given data and generates correct request data + * array or raises WebToPayException on failure. * - * @param WebToPay_PaymentMethodCountry $country + * @param int $orderId order id of repeated request * - * @return WebToPay_PaymentMethodCountry + * @return array + * + * @throws WebToPayException */ - public function addCountry(WebToPay_PaymentMethodCountry $country) { - return $this->countries[$country->getCode()] = $country; + public function buildRepeatRequest(int $orderId): array + { + $data['orderid'] = $orderId; + $data['version'] = WebToPay::VERSION; + $data['projectid'] = $this->projectId; + $data['repeat_request'] = '1'; + + return $this->createRequest($data); } /** - * Gets country object with specified country code. If no country with such country code is found, returns null. - * - * @param string $countryCode + * Builds the full request url for a repeated request (including the protocol and the domain) * - * @return null|WebToPay_PaymentMethodCountry + * @throws WebToPayException */ - public function getCountry($countryCode) { - return isset($this->countries[$countryCode]) ? $this->countries[$countryCode] : null; + public function buildRepeatRequestUrlFromOrderId(int $orderId): string + { + $request = $this->buildRepeatRequest($orderId); + + return $this->urlBuilder->buildForRequest($request); } /** - * Returns new payment method list instance with only those payment methods, which are available for provided - * amount. - * Returns itself, if list is already filtered and filter amount matches the given one. - * - * @param integer $amount - * @param string $currency + * Checks data to be valid by passed specification * - * @return WebToPay_PaymentMethodList + * @param array $data * - * @throws WebToPayException if this list is already filtered and not for provided amount + * @throws WebToPay_Exception_Validation */ - public function filterForAmount($amount, $currency) { - if ($currency !== $this->currency) { - throw new WebToPayException( - 'Currencies do not match. Given currency: ' . $currency . ', currency in list: ' . $this->currency - ); - } - if ($this->isFiltered()) { - if ($this->amount === $amount) { - return $this; - } else { - throw new WebToPayException('This list is already filtered, use unfiltered list instead'); + protected function validateRequest(array $data): void + { + foreach (self::REQUEST_SPECS as $spec) { + [$name, $maxlen, $required, $regexp] = $spec; + + if ($required && empty($data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' is required but missing.", $name), + WebToPayException::E_MISSING, + $name + ); } - } else { - $list = new WebToPay_PaymentMethodList($this->projectId, $currency, $this->defaultLanguage, $amount); - foreach ($this->getCountries() as $country) { - $country = $country->filterForAmount($amount, $currency); - if (!$country->isEmpty()) { - $list->addCountry($country); + + if (!empty($data[$name])) { + if (strlen((string) $data[$name]) > $maxlen) { + throw new WebToPay_Exception_Validation(sprintf( + "'%s' value is too long (%d), %d characters allowed.", + $name, + strlen((string) $data[$name]), + $maxlen + ), WebToPayException::E_MAXLEN, $name); } - } - return $list; - } - } - /** - * Loads countries from given XML node - * - * @param SimpleXMLElement $xmlNode - */ - public function fromXmlNode($xmlNode) { - foreach ($xmlNode->country as $countryNode) { - $titleTranslations = array(); - foreach ($countryNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + if ($regexp !== '' && !preg_match($regexp, (string) $data[$name])) { + throw new WebToPay_Exception_Validation( + sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), + WebToPayException::E_REGEXP, + $name + ); + } } - $this->addCountry($this->createCountry((string) $countryNode->attributes()->code, $titleTranslations)) - ->fromXmlNode($countryNode); } } /** - * Method to create new country instances. Overwrite if you have to use some other country subtype. + * Makes request data array from parameters, also generates signature * - * @param string $countryCode - * @param array $titleTranslations + * @param array $request * - * @return WebToPay_PaymentMethodCountry + * @return array */ - protected function createCountry($countryCode, array $titleTranslations = array()) { - return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); + protected function createRequest(array $request): array + { + $data = $this->util->encodeSafeUrlBase64(http_build_query($request, '', '&')); + + return [ + 'data' => $data, + 'sign' => md5($data . $this->projectPassword), + ]; } } + /** * Simple web client */ -class WebToPay_WebClient { - +class WebToPay_WebClient +{ /** * Gets page contents by specified URI. Adds query data if provided to the URI * Ignores status code of the response and header fields * * @param string $uri - * @param array $queryData + * @param array $queryData * * @return string - * * @throws WebToPayException */ - public function get($uri, array $queryData = array()) { + public function get(string $uri, array $queryData = []): string + { if (count($queryData) > 0) { $uri .= strpos($uri, '?') === false ? '?' : '&'; $uri .= http_build_query($queryData, '', '&'); } $url = parse_url($uri); - if ('https' == $url['scheme']) { - $host = 'ssl://'.$url['host']; + if ('https' === ($url['scheme'] ?? '')) { + $host = 'ssl://' . ($url['host'] ?? ''); $port = 443; } else { - $host = $url['host']; + $host = $url['host'] ?? ''; $port = 80; } - $fp = fsockopen($host, $port, $errno, $errstr, 30); + $fp = $this->openSocket($host, $port, $errno, $errstr, 30); if (!$fp) { throw new WebToPayException(sprintf('Cannot connect to %s', $uri), WebToPayException::E_INVALID); } if(isset($url['query'])) { - $data = $url['path'].'?'.$url['query']; + $data = ($url['path'] ?? '') . '?' . $url['query']; } else { - $data = $url['path']; + $data = ($url['path'] ?? ''); } $out = "GET " . $data . " HTTP/1.0\r\n"; - $out .= "Host: ".$url['host']."\r\n"; + $out .= "Host: " . ($url['host'] ?? '') . "\r\n"; $out .= "Connection: Close\r\n\r\n"; - $content = ''; + $content = $this->getContentFromSocket($fp, $out); + // Separate header and content + [$header, $content] = explode("\r\n\r\n", $content, 2); + + return trim($content); + } + + /** + * @param string $host + * @param int $port + * @param int $errno + * @param string $errstr + * @param float $timeout + * @return false|resource + */ + protected function openSocket(string $host, int $port, &$errno, &$errstr, float $timeout = 30) + { + return fsockopen($host, $port, $errno, $errstr, $timeout); + } + + /** + * @param resource $fp + * @param string $out + * + * @return string + */ + protected function getContentFromSocket($fp, string $out): string + { fwrite($fp, $out); - while (!feof($fp)) $content .= fgets($fp, 8192); + $content = (string) stream_get_contents($fp); fclose($fp); - list($header, $content) = explode("\r\n\r\n", $content, 2); + return $content; + } +} + - return trim($content); +/** + * Sends answer to SMS payment if it was not provided with response to callback + */ +class WebToPay_SmsAnswerSender +{ + protected string $password; + + protected WebToPay_WebClient $webClient; + + protected WebToPay_UrlBuilder $urlBuilder; + + /** + * Constructs object + */ + public function __construct(string $password, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder) + { + $this->password = $password; + $this->webClient = $webClient; + $this->urlBuilder = $urlBuilder; + } + + /** + * Sends answer by sms ID get from callback. Answer can be sent only if it was not provided + * when responding to the callback + * + * @throws WebToPayException + * + * @codeCoverageIgnore + */ + public function sendAnswer(int $smsId, string $text): void + { + $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), [ + 'id' => $smsId, + 'msg' => $text, + 'transaction' => md5($this->password . '|' . $smsId), + ]); + + if (strpos($content, 'OK') !== 0) { + throw new WebToPayException( + sprintf('Error: %s', $content), + WebToPayException::E_SMS_ANSWER + ); + } } } + /** - * Builds and signs requests + * Class with all information about available payment methods for some project, optionally filtered by some amount. */ -class WebToPay_RequestBuilder { +class WebToPay_PaymentMethodList +{ + /** + * Holds available payment countries + * + * @var WebToPay_PaymentMethodCountry[] + */ + protected array $countries; + + /** + * Default language for titles + */ + protected string $defaultLanguage; + + /** + * Project ID, to which this method list is valid + */ + protected int $projectId; + + /** + * Currency for min and max amounts in this list + */ + protected string $currency; /** - * @var string + * If this list is filtered for some amount, this field defines it */ - protected $projectPassword; + protected ?int $amount; /** - * @var WebToPay_Util + * Constructs object + * + * @param int $projectId + * @param string $currency currency for min and max amounts in this list + * @param string $defaultLanguage + * @param int|null $amount null if this list is not filtered by amount */ - protected $util; + public function __construct(int $projectId, string $currency, string $defaultLanguage = 'lt', ?int $amount = null) + { + $this->projectId = $projectId; + $this->countries = []; + $this->defaultLanguage = $defaultLanguage; + $this->currency = $currency; + $this->amount = $amount; + } /** - * @var integer + * Sets default language for titles. + * Returns itself for fluent interface */ - protected $projectId; + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodList + { + $this->defaultLanguage = $language; + foreach ($this->countries as $country) { + $country->setDefaultLanguage($language); + } + return $this; + } + + /** + * Gets default language for titles + */ + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; + } /** - * @var WebToPay_UrlBuilder $urlBuilder + * Gets project ID for this payment method list */ - protected $urlBuilder; + public function getProjectId(): int + { + return $this->projectId; + } /** - * Constructs object - * - * @param integer $projectId - * @param string $projectPassword - * @param WebToPay_Util $util - * @param WebToPay_UrlBuilder $urlBuilder + * Gets currency for min and max amounts in this list */ - public function __construct( - $projectId, - $projectPassword, - WebToPay_Util $util, - WebToPay_UrlBuilder $urlBuilder - ) + public function getCurrency(): string { - $this->projectId = $projectId; - $this->projectPassword = $projectPassword; - $this->util = $util; - $this->urlBuilder = $urlBuilder; + return $this->currency; } /** - * Builds request data array. - * - * This method checks all given data and generates correct request data - * array or raises WebToPayException on failure. - * - * @param array $data information about current payment request - * - * @return array - * - * @throws WebToPayException + * Gets whether this list is already filtered for some amount */ - public function buildRequest($data) { - $this->validateRequest($data, self::getRequestSpec()); - $data['version'] = WebToPay::VERSION; - $data['projectid'] = $this->projectId; - unset($data['repeat_request']); - return $this->createRequest($data); + public function isFiltered(): bool + { + return $this->amount !== null; } /** - * Builds the full request url (including the protocol and the domain) + * Returns available countries * - * @param array $data - * @return string + * @return WebToPay_PaymentMethodCountry[] */ - public function buildRequestUrlFromData($data) { - $language = isset($data['lang']) ? $data['lang'] : null; - $request = $this->buildRequest($data); - return $this->urlBuilder->buildForRequest($request, $language); + public function getCountries(): array + { + return $this->countries; } /** - * Builds repeat request data array. - * - * This method checks all given data and generates correct request data - * array or raises WebToPayException on failure. - * - * @param string $orderId order id of repeated request - * - * @return array - * - * @throws WebToPayException + * Adds new country to payment methods. If some other country with same code was registered earlier, overwrites it. + * Returns added country instance */ - public function buildRepeatRequest($orderId) { - $data['orderid'] = $orderId; - $data['version'] = WebToPay::VERSION; - $data['projectid'] = $this->projectId; - $data['repeat_request'] = '1'; - return $this->createRequest($data); + public function addCountry(WebToPay_PaymentMethodCountry $country): WebToPay_PaymentMethodCountry + { + return $this->countries[$country->getCode()] = $country; } /** - * Builds the full request url for a repeated request (including the protocol and the domain) - * - * @param string $orderId order id of repeated request - * @return string + * Gets country object with specified country code. If no country with such country code is found, returns null. */ - public function buildRepeatRequestUrlFromOrderId($orderId) { - $request = $this->buildRepeatRequest($orderId); - return $this->urlBuilder->buildForRequest($request); + public function getCountry(string $countryCode): ?WebToPay_PaymentMethodCountry + { + return $this->countries[$countryCode] ?? null; } /** - * Checks data to be valid by passed specification - * - * @param array $data - * @param array $specs + * Returns new payment method list instance with only those payment methods, which are available for provided + * amount. + * Returns itself, if list is already filtered and filter amount matches the given one. * - * @throws WebToPay_Exception_Validation + * @throws WebToPayException if this list is already filtered and not for provided amount */ - protected function validateRequest($data, $specs) { - foreach ($specs as $spec) { - list($name, $maxlen, $required, $regexp) = $spec; - if ($required && !isset($data[$name])) { - throw new WebToPay_Exception_Validation( - sprintf("'%s' is required but missing.", $name), - WebToPayException::E_MISSING, - $name - ); + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodList + { + if ($currency !== $this->currency) { + throw new WebToPayException( + 'Currencies do not match. Given currency: ' + . $currency + . ', currency in list: ' + . $this->currency + ); + } + if ($this->isFiltered()) { + if ($this->amount === $amount) { + return $this; + } else { + throw new WebToPayException('This list is already filtered, use unfiltered list instead'); } - - if (!empty($data[$name])) { - if ($maxlen && strlen($data[$name]) > $maxlen) { - throw new WebToPay_Exception_Validation(sprintf( - "'%s' value is too long (%d), %d characters allowed.", - $name, - strlen($data[$name]), - $maxlen - ), WebToPayException::E_MAXLEN, $name); - } - - if ($regexp !== '' && !preg_match($regexp, $data[$name])) { - throw new WebToPay_Exception_Validation( - sprintf("'%s' value '%s' is invalid.", $name, $data[$name]), - WebToPayException::E_REGEXP, - $name - ); + } else { + $list = new WebToPay_PaymentMethodList($this->projectId, $currency, $this->defaultLanguage, $amount); + foreach ($this->getCountries() as $country) { + $country = $country->filterForAmount($amount, $currency); + if (!$country->isEmpty()) { + $list->addCountry($country); } } + + return $list; } } /** - * Makes request data array from parameters, also generates signature - * - * @param array $request - * - * @return array + * Loads countries from given XML node */ - protected function createRequest(array $request) { - $data = $this->util->encodeSafeUrlBase64(http_build_query($request, '', '&')); - return array( - 'data' => $data, - 'sign' => md5($data . $this->projectPassword), - ); + public function fromXmlNode(SimpleXMLElement $xmlNode): void + { + foreach ($xmlNode->country as $countryNode) { + $titleTranslations = []; + foreach ($countryNode->title as $titleNode) { + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; + } + $this->addCountry($this->createCountry((string)$countryNode->attributes()->code, $titleTranslations)) + ->fromXmlNode($countryNode); + } } /** - * Returns specification of fields for request. - * - * Array structure: - * name – request item name - * maxlen – max allowed value for item - * required – is this item is required - * regexp – regexp to test item value - * - * @return array - */ - protected static function getRequestSpec() { - return array( - array('orderid', 40, true, ''), - array('accepturl', 255, true, ''), - array('cancelurl', 255, true, ''), - array('callbackurl', 255, true, ''), - array('lang', 3, false, '/^[a-z]{3}$/i'), - array('amount', 11, false, '/^\d+$/'), - array('currency', 3, false, '/^[a-z]{3}$/i'), - array('payment', 20, false, ''), - array('country', 2, false, '/^[a-z_]{2}$/i'), - array('paytext', 255, false, ''), - array('p_firstname', 255, false, ''), - array('p_lastname', 255, false, ''), - array('p_email', 255, false, ''), - array('p_street', 255, false, ''), - array('p_city', 255, false, ''), - array('p_state', 255, false, ''), - array('p_zip', 20, false, ''), - array('p_countrycode', 2, false, '/^[a-z]{2}$/i'), - array('test', 1, false, '/^[01]$/'), - array('time_limit', 19, false, '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/'), - ); + * Method to create new country instances. Overwrite if you have to use some other country subtype. + * + * @param string $countryCode + * @param array $titleTranslations + * + * @return WebToPay_PaymentMethodCountry + */ + protected function createCountry(string $countryCode, array $titleTranslations = []): WebToPay_PaymentMethodCountry + { + return new WebToPay_PaymentMethodCountry($countryCode, $titleTranslations, $this->defaultLanguage); } } + /** - * Utility class + * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions */ -class WebToPay_Util +class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { - const GCM_CIPHER = 'aes-256-gcm'; - const GCM_AUTH_KEY_LENGTH = 16; + protected string $projectPassword; /** - * Decodes url-safe-base64 encoded string - * Url-safe-base64 is same as base64, but + is replaced to - and / to _ - * - * @param string $encodedText - * - * @return string + * Constructs object */ - public function decodeSafeUrlBase64($encodedText) { - return base64_decode(strtr($encodedText, array('-' => '+', '_' => '/'))); + public function __construct(string $projectPassword) + { + $this->projectPassword = $projectPassword; } /** - * Encodes string to url-safe-base64 - * Url-safe-base64 is same as base64, but + is replaced to - and / to _ + * Check for SS1, which is not depend on openssl functions. * - * @param string $text + * @param array $request * - * @return string + * @return bool + * + * @throws WebToPay_Exception_Callback */ - public function encodeSafeUrlBase64($text) { - return strtr(base64_encode($text), array('+' => '-', '/' => '_')); + public function checkSign(array $request): bool + { + if (!isset($request['data']) || !isset($request['ss1'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + } + + return md5($request['data'] . $this->projectPassword) === $request['ss1']; } +} - /** - * Decrypts string with aes-256-gcm algorithm - * - * @param string $stringToDecrypt - * @param string $key - * - * @return string|null - */ - function decryptGCM($stringToDecrypt, $key) { - $ivLength = openssl_cipher_iv_length(self::GCM_CIPHER); - $iv = substr($stringToDecrypt, 0, $ivLength); - $ciphertext = substr($stringToDecrypt, $ivLength, -self::GCM_AUTH_KEY_LENGTH); - $tag = substr($stringToDecrypt, -self::GCM_AUTH_KEY_LENGTH); - $decryptedText = openssl_decrypt( - $ciphertext, - self::GCM_CIPHER, - $key, - OPENSSL_RAW_DATA, - $iv, - $tag - ); +/** + * Checks SS2 signature. Depends on SSL functions + */ +class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface +{ + protected string $publicKey; - return $decryptedText === false ? null : $decryptedText; - } + protected WebToPay_Util $util; /** - * Parses HTTP query to array - * - * @param string $query - * - * @return array + * Constructs object */ - public function parseHttpQuery($query) { - $params = array(); - parse_str($query, $params); - return $params; + public function __construct(string $publicKey, WebToPay_Util $util) + { + $this->publicKey = $publicKey; + $this->util = $util; } /** - * Strips slashes recursively, so this method can be used on arrays with more than one level + * Checks signature * - * @param mixed $data + * @param array $request + * + * @return bool * - * @return mixed + * @throws WebToPay_Exception_Callback */ - protected function stripSlashesRecursively($data) { - if (is_array($data)) { - $result = array(); - foreach ($data as $key => $value) { - $result[stripslashes($key)] = $this->stripSlashesRecursively($value); - } - return $result; - } else { - return stripslashes($data); + public function checkSign(array $request): bool + { + if (!isset($request['data']) || !isset($request['ss2'])) { + throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); } + + $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); + $ok = openssl_verify($request['data'], $ss2, $this->publicKey); + + return $ok === 1; } } /** - * Sends answer to SMS payment if it was not provided with response to callback + * Interface for sign checker */ -class WebToPay_SmsAnswerSender { - - /** - * @var string - */ - protected $password; - - /** - * @var WebToPay_WebClient - */ - protected $webClient; - - /** - * @var WebToPay_UrlBuilder $urlBuilder - */ - protected $urlBuilder; - - /** - * Constructs object - * - * @param string $password - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder - */ - public function __construct( - $password, - WebToPay_WebClient $webClient, - WebToPay_UrlBuilder $urlBuilder - ) { - $this->password = $password; - $this->webClient = $webClient; - $this->urlBuilder = $urlBuilder; - } - +interface WebToPay_Sign_SignCheckerInterface +{ /** - * Sends answer by sms ID get from callback. Answer can be send only if it was not provided - * when responding to callback + * Checks whether request is signed properly * - * @param integer $smsId - * @param string $text + * @param array $request * - * @throws WebToPayException + * @return boolean */ - public function sendAnswer($smsId, $text) { - $content = $this->webClient->get($this->urlBuilder->buildForSmsAnswer(), array( - 'id' => $smsId, - 'msg' => $text, - 'transaction' => md5($this->password . '|' . $smsId), - )); - if (strpos($content, 'OK') !== 0) { - throw new WebToPayException( - sprintf('Error: %s', $content), - WebToPayException::E_SMS_ANSWER - ); - } - } + public function checkSign(array $request): bool; } @@ -1893,69 +1508,54 @@ public function sendAnswer($smsId, $text) { * Loads data about payment methods and constructs payment method list object from that data * You need SimpleXML support to use this feature */ -class WebToPay_PaymentMethodListProvider { - - /** - * @var integer - */ - protected $projectId; +class WebToPay_PaymentMethodListProvider +{ + protected int $projectId; - /** - * @var WebToPay_WebClient - */ - protected $webClient; + protected WebToPay_WebClient $webClient; /** * Holds constructed method lists by currency * * @var WebToPay_PaymentMethodList[] */ - protected $methodListCache = array(); + protected array $methodListCache = []; /** * Builds various request URLs - * - * @var WebToPay_UrlBuilder $urlBuilder */ - protected $urlBuilder; + protected WebToPay_UrlBuilder $urlBuilder; /** * Constructs object * - * @param integer $projectId - * @param WebToPay_WebClient $webClient - * @param WebToPay_UrlBuilder $urlBuilder - * * @throws WebToPayException if SimpleXML is not available */ public function __construct( - $projectId, + int $projectId, WebToPay_WebClient $webClient, WebToPay_UrlBuilder $urlBuilder - ) - { + ) { $this->projectId = $projectId; $this->webClient = $webClient; $this->urlBuilder = $urlBuilder; - if (!function_exists('simplexml_load_string')) { + if (!WebToPay_Functions::function_exists('simplexml_load_string')) { throw new WebToPayException('You have to install libxml to use payment methods API'); } } /** - * Gets payment method list for specified currency - * - * @param float $amount - * @param string $currency - * - * @return WebToPay_PaymentMethodList + * Gets payment method list for specified currency * * @throws WebToPayException */ - public function getPaymentMethodList($amount, $currency) { + public function getPaymentMethodList(float $amount, string $currency): WebToPay_PaymentMethodList + { if (!isset($this->methodListCache[$currency])) { - $xmlAsString = $this->webClient->get($this->urlBuilder->buildForPaymentsMethodList($this->projectId, $amount, $currency)); + $xmlAsString = $this->webClient->get( + $this->urlBuilder->buildForPaymentsMethodList($this->projectId, (string) $amount, $currency) + ); $useInternalErrors = libxml_use_internal_errors(false); $rootNode = simplexml_load_string($xmlAsString); libxml_clear_errors(); @@ -1967,134 +1567,249 @@ public function getPaymentMethodList($amount, $currency) { $methodList->fromXmlNode($rootNode); $this->methodListCache[$currency] = $methodList; } + return $this->methodListCache[$currency]; } } + /** - * Raised if configuration is incorrect + * Creates objects. Also caches to avoid creating several instances of same objects */ -class WebToPay_Exception_Configuration extends WebToPayException { +class WebToPay_Factory +{ + public const ENV_PRODUCTION = 'production'; + public const ENV_SANDBOX = 'sandbox'; -} + /** + * @var array + */ + protected static array $defaultConfiguration = [ + 'routes' => [ + self::ENV_PRODUCTION => [ + 'publicKey' => 'https://www.paysera.com/download/public.key', + 'payment' => 'https://bank.paysera.com/pay/', + 'paymentMethodList' => 'https://www.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://bank.paysera.com/psms/respond/', + ], + self::ENV_SANDBOX => [ + 'publicKey' => 'https://sandbox.paysera.com/download/public.key', + 'payment' => 'https://sandbox.paysera.com/pay/', + 'paymentMethodList' => 'https://sandbox.paysera.com/new/api/paymentMethods/', + 'smsAnswer' => 'https://sandbox.paysera.com/psms/respond/', + ], + ], + ]; + protected string $environment; -/** - * Raised on validation error in passed data when building the request - */ -class WebToPay_Exception_Validation extends WebToPayException { + /** + * @var array + */ + protected array $configuration; - public function __construct($message, $code = 0, $field = null, Exception $previousException = null) { - parent::__construct($message, $code, $previousException); - if ($field) { - $this->setField($field); - } - } -} + protected ?WebToPay_WebClient $webClient = null; -/** - * Raised on error in callback - */ -class WebToPay_Exception_Callback extends WebToPayException { + protected ?WebToPay_CallbackValidator $callbackValidator = null; -} + protected ?WebToPay_RequestBuilder $requestBuilder = null; -/** - * Sign checker which checks SS1 signature. SS1 does not depend on SSL functions - */ -class WebToPay_Sign_SS1SignChecker implements WebToPay_Sign_SignCheckerInterface { + protected ?WebToPay_Sign_SignCheckerInterface $signer = null; + + protected ?WebToPay_SmsAnswerSender $smsAnswerSender = null; + + protected ?WebToPay_PaymentMethodListProvider $paymentMethodListProvider = null; + + protected ?WebToPay_Util $util = null; + + protected ?WebToPay_UrlBuilder $urlBuilder = null; /** - * @var string + * Constructs object. + * Configuration keys: projectId, password + * They are required only when some object being created needs them, + * if they are not found at that moment - exception is thrown + * + * @param array $configuration */ - protected $projectPassword; + public function __construct(array $configuration = []) + { + $this->configuration = array_merge(self::$defaultConfiguration, $configuration); + $this->environment = self::ENV_PRODUCTION; + } /** - * Constructs object - * - * @param string $projectPassword + * If passed true the factory will use sandbox when constructing URLs */ - public function __construct($projectPassword) { - $this->projectPassword = $projectPassword; + public function useSandbox(bool $enableSandbox): self + { + if ($enableSandbox) { + $this->environment = self::ENV_SANDBOX; + } else { + $this->environment = self::ENV_PRODUCTION; + } + + return $this; } /** - * Check for SS1, which is not depend on openssl functions. - * - * @param array $request - * - * @return boolean + * Creates or gets callback validator instance * - * @throws WebToPay_Exception_Callback + * @throws WebToPayException + * @throws WebToPay_Exception_Configuration */ - public function checkSign(array $request) { - if (!isset($request['data']) || !isset($request['ss1'])) { - throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + public function getCallbackValidator(): WebToPay_CallbackValidator + { + if ($this->callbackValidator === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + + $this->callbackValidator = new WebToPay_CallbackValidator( + (int) $this->configuration['projectId'], + $this->getSigner(), + $this->getUtil(), + $this->configuration['password'] ?? null + ); } - return md5($request['data'] . $this->projectPassword) === $request['ss1']; + return $this->callbackValidator; } -} - -/** - * Checks SS2 signature. Depends on SSL functions - */ -class WebToPay_Sign_SS2SignChecker implements WebToPay_Sign_SignCheckerInterface { /** - * @var string + * Creates or gets request builder instance + * + * @throws WebToPay_Exception_Configuration */ - protected $publicKey; + public function getRequestBuilder(): WebToPay_RequestBuilder + { + if ($this->requestBuilder === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password to sign request'); + } + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->requestBuilder = new WebToPay_RequestBuilder( + (int) $this->configuration['projectId'], + $this->configuration['password'], + $this->getUtil(), + $this->getUrlBuilder() + ); + } + + return $this->requestBuilder; + } + + public function getUrlBuilder(): WebToPay_UrlBuilder + { + if ($this->urlBuilder === null || $this->urlBuilder->getEnvironment() !== $this->environment) { + $this->urlBuilder = new WebToPay_UrlBuilder( + $this->configuration, + $this->environment + ); + } + + return $this->urlBuilder; + } /** - * @var WebToPay_Util + * Creates or gets SMS answer sender instance + * + * @throws WebToPay_Exception_Configuration */ - protected $util; + public function getSmsAnswerSender(): WebToPay_SmsAnswerSender + { + if ($this->smsAnswerSender === null) { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration('You have to provide project password'); + } + $this->smsAnswerSender = new WebToPay_SmsAnswerSender( + $this->configuration['password'], + $this->getWebClient(), + $this->getUrlBuilder() + ); + } + + return $this->smsAnswerSender; + } /** - * Constructs object + * Creates or gets payment list provider instance * - * @param string $publicKey - * @param WebToPay_Util $util + * @throws WebToPayException + * @throws WebToPay_Exception_Configuration */ - public function __construct($publicKey, WebToPay_Util $util) { - $this->publicKey = $publicKey; - $this->util = $util; + public function getPaymentMethodListProvider(): WebToPay_PaymentMethodListProvider + { + if ($this->paymentMethodListProvider === null) { + if (!isset($this->configuration['projectId'])) { + throw new WebToPay_Exception_Configuration('You have to provide project ID'); + } + $this->paymentMethodListProvider = new WebToPay_PaymentMethodListProvider( + (int) $this->configuration['projectId'], + $this->getWebClient(), + $this->getUrlBuilder() + ); + } + + return $this->paymentMethodListProvider; } /** - * Checks signature - * - * @param array $request - * - * @return boolean + * Creates or gets signer instance. Chooses SS2 signer if openssl functions are available, SS1 in other case * - * @throws WebToPay_Exception_Callback + * @throws WebToPay_Exception_Configuration + * @throws WebToPayException */ - public function checkSign(array $request) { - if (!isset($request['data']) || !isset($request['ss2'])) { - throw new WebToPay_Exception_Callback('Not enough parameters in callback. Possible version mismatch'); + protected function getSigner(): WebToPay_Sign_SignCheckerInterface + { + if ($this->signer === null) { + if (WebToPay_Functions::function_exists('openssl_pkey_get_public')) { + $webClient = $this->getWebClient(); + $publicKey = $webClient->get($this->getUrlBuilder()->buildForPublicKey()); + if (!$publicKey) { + throw new WebToPayException('Cannot download public key from WebToPay website'); + } + $this->signer = new WebToPay_Sign_SS2SignChecker($publicKey, $this->getUtil()); + } else { + if (!isset($this->configuration['password'])) { + throw new WebToPay_Exception_Configuration( + 'You have to provide project password if OpenSSL is unavailable' + ); + } + $this->signer = new WebToPay_Sign_SS1SignChecker($this->configuration['password']); + } } - $ss2 = $this->util->decodeSafeUrlBase64($request['ss2']); - $ok = openssl_verify($request['data'], $ss2, $this->publicKey); - return $ok === 1; + return $this->signer; } -} -/** - * Interface for sign checker - */ -interface WebToPay_Sign_SignCheckerInterface { + /** + * Creates or gets web client instance + */ + protected function getWebClient(): WebToPay_WebClient + { + if ($this->webClient === null) { + $this->webClient = new WebToPay_WebClient(); + } + + return $this->webClient; + } /** - * Checks whether request is signed properly - * - * @param array $request + * Creates or gets util instance * - * @return boolean + * @throws WebToPay_Exception_Configuration */ - public function checkSign(array $request); + protected function getUtil(): WebToPay_Util + { + if ($this->util === null) { + $this->util = new WebToPay_Util(); + } + + return $this->util; + } } @@ -2103,316 +1818,484 @@ public function checkSign(array $request); * * Class WebToPay_UrlBuilder */ -class WebToPay_UrlBuilder { - - const PLACEHOLDER_KEY = '[domain]'; +class WebToPay_UrlBuilder +{ + public const PLACEHOLDER_KEY = '[domain]'; /** - * @var array + * @var array */ - protected $configuration = array(); + protected array $configuration; - /** - * @var string - */ - protected $environment; + protected string $environment; /** - * @var array + * @var array */ - protected $environmentSettings; + protected array $environmentSettings; /** - * @param array $configuration + * @param array $configuration * @param string $environment */ - function __construct($configuration, $environment) + public function __construct(array $configuration, string $environment) { $this->configuration = $configuration; $this->environment = $environment; $this->environmentSettings = $this->configuration['routes'][$this->environment]; } + public function getEnvironment(): string + { + return $this->environment; + } + /** * Builds a complete request URL based on the provided parameters * - * @param $request - * @param null $language + * @param array $request + * * @return string */ - public function buildForRequest($request, $language = null) { + public function buildForRequest(array $request): string + { return $this->createUrlFromRequestAndLanguage($request); } /** * Builds a complete URL for payment list API - * - * @param int $projectId - * @param string $amount - * @param string $currency - * @return string */ - public function buildForPaymentsMethodList($projectId, $amount, $currency) { + public function buildForPaymentsMethodList(int $projectId, string $amount, string $currency): string + { $route = $this->environmentSettings['paymentMethodList']; + return $route . $projectId . '/currency:' . $currency . '/amount:' . $amount; } /** * Builds a complete URL for Sms Answer * - * @return string + * @codeCoverageIgnore */ - public function buildForSmsAnswer() { - $route = $this->environmentSettings['smsAnswer']; - return $route; + public function buildForSmsAnswer(): string + { + return $this->environmentSettings['smsAnswer']; } /** - * Build the url to the public key - * - * @return string + * Build the URL to the public key */ - public function buildForPublicKey() { - $route = $this->environmentSettings['publicKey']; - return $route; + public function buildForPublicKey(): string + { + return $this->environmentSettings['publicKey']; } /** - * Creates an URL from the request and data provided. + * Creates a URL from the request and data provided. + * + * @param array $request * - * @param array $request * @return string */ - protected function createUrlFromRequestAndLanguage($request) { + protected function createUrlFromRequestAndLanguage(array $request): string + { $url = $this->getPaymentUrl() . '?' . http_build_query($request, '', '&'); - return preg_replace('/[\r\n]+/is', '', $url); + + return preg_replace('/[\r\n]+/is', '', $url) ?? ''; } /** - * Returns payment url. Argument is same as lang parameter in request data + * Returns payment URL. Argument is same as lang parameter in request data * - * @return string $url + * @return string */ - public function getPaymentUrl() { - $route = $this->environmentSettings['payment']; - return $route; + public function getPaymentUrl(): string + { + return $this->environmentSettings['payment']; } } /** - * Payment method configuration for some country + * Wrapper class to group payment methods. Each country can have several payment method groups, each of them + * have one or more payment methods. */ -class WebToPay_PaymentMethodCountry { +class WebToPay_PaymentMethodGroup +{ /** - * @var string + * Some unique (in the scope of country) key for this group */ - protected $countryCode; + protected string $groupKey; /** - * Holds available payment types for this country + * Translations array for this group. Holds associative array of group title by country codes. * - * @var WebToPay_PaymentMethodGroup[] + * @var array */ - protected $groups; + protected array $translations; /** - * Default language for titles + * Holds actual payment methods * - * @var string + * @var WebToPay_PaymentMethod[] */ - protected $defaultLanguage; + protected array $paymentMethods; /** - * Translations array for this country. Holds associative array of country title by language codes. - * - * @var array + * Default language for titles */ - protected $titleTranslations; + protected string $defaultLanguage; /** * Constructs object * - * @param string $countryCode - * @param array $titleTranslations + * @param string $groupKey + * @param array $translations * @param string $defaultLanguage */ - public function __construct($countryCode, $titleTranslations, $defaultLanguage = 'lt') { - $this->countryCode = $countryCode; + public function __construct(string $groupKey, array $translations = [], string $defaultLanguage = 'lt') + { + $this->groupKey = $groupKey; + $this->translations = $translations; $this->defaultLanguage = $defaultLanguage; - $this->titleTranslations = $titleTranslations; - $this->groups = array(); + $this->paymentMethods = []; } /** * Sets default language for titles. * Returns itself for fluent interface - * - * @param string $language - * - * @return WebToPay_PaymentMethodCountry */ - public function setDefaultLanguage($language) { + public function setDefaultLanguage(string $language): WebToPay_PaymentMethodGroup + { $this->defaultLanguage = $language; - foreach ($this->groups as $group) { - $group->setDefaultLanguage($language); + foreach ($this->paymentMethods as $paymentMethod) { + $paymentMethod->setDefaultLanguage($language); } + return $this; } + /** + * Gets default language for titles + */ + public function getDefaultLanguage(): string + { + return $this->defaultLanguage; + } + /** * Gets title of the group. Tries to get title in specified language. If it is not found or if language is not * specified, uses default language, given to constructor. - * - * @param string [Optional] $languageCode - * - * @return string */ - public function getTitle($languageCode = null) { - if ($languageCode !== null && isset($this->titleTranslations[$languageCode])) { - return $this->titleTranslations[$languageCode]; - } elseif (isset($this->titleTranslations[$this->defaultLanguage])) { - return $this->titleTranslations[$this->defaultLanguage]; + public function getTitle(?string $languageCode = null): string + { + if ($languageCode !== null && isset($this->translations[$languageCode])) { + return $this->translations[$languageCode]; + } elseif (isset($this->translations[$this->defaultLanguage])) { + return $this->translations[$this->defaultLanguage]; } else { - return $this->countryCode; + return $this->groupKey; } } /** - * Gets default language for titles - * - * @return string + * Returns group key */ - public function getDefaultLanguage() { - return $this->defaultLanguage; + public function getKey(): string + { + return $this->groupKey; } /** - * Gets country code + * Returns available payment methods for this group * - * @return string + * @return WebToPay_PaymentMethod[] */ - public function getCode() { - return $this->countryCode; + public function getPaymentMethods(): array + { + return $this->paymentMethods; } + /** - * Adds new group to payment methods for this country. - * If some other group was registered earlier with same key, overwrites it. - * Returns given group + * Adds new payment method for this group. + * If some other payment method with specified key was registered earlier, overwrites it. + * Returns given payment method * - * @param WebToPay_PaymentMethodGroup $group + * @param WebToPay_PaymentMethod $paymentMethod * - * @return WebToPay_PaymentMethodGroup + * @return WebToPay_PaymentMethod */ - public function addGroup(WebToPay_PaymentMethodGroup $group) { - return $this->groups[$group->getKey()] = $group; + public function addPaymentMethod(WebToPay_PaymentMethod $paymentMethod): WebToPay_PaymentMethod + { + return $this->paymentMethods[$paymentMethod->getKey()] = $paymentMethod; } /** - * Gets group object with specified group key. If no group with such key is found, returns null. - * - * @param string $groupKey - * - * @return null|WebToPay_PaymentMethodGroup + * Gets payment method object with key. If no payment method with such key is found, returns null. */ - public function getGroup($groupKey) { - return isset($this->groups[$groupKey]) ? $this->groups[$groupKey] : null; + public function getPaymentMethod(string $key): ?WebToPay_PaymentMethod + { + return $this->paymentMethods[$key] ?? null; } /** - * Returns payment method groups registered for this country. + * Returns new group instance with only those payment methods, which are available for provided amount. * - * @return WebToPay_PaymentMethodGroup[] + * @throws WebToPayException */ - public function getGroups() { - return $this->groups; + public function filterForAmount(int $amount, string $currency): WebToPay_PaymentMethodGroup + { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isAvailableForAmount($amount, $currency)) { + $group->addPaymentMethod($paymentMethod); + } + } + + return $group; } /** - * Gets payment methods in all groups - * - * @return WebToPay_PaymentMethod[] + * Returns new country instance with only those payment methods, which are returns or not iban number after payment */ - public function getPaymentMethods() { - $paymentMethods = array(); - foreach ($this->groups as $group) { - $paymentMethods = array_merge($paymentMethods, $group->getPaymentMethods()); + public function filterForIban(bool $isIban = true): WebToPay_PaymentMethodGroup + { + $group = new WebToPay_PaymentMethodGroup($this->groupKey, $this->translations, $this->defaultLanguage); + foreach ($this->getPaymentMethods() as $paymentMethod) { + if ($paymentMethod->isIban() == $isIban) { + $group->addPaymentMethod($paymentMethod); + } } - return $paymentMethods; + + return $group; } /** - * Returns new country instance with only those payment methods, which are available for provided amount. - * - * @param integer $amount - * @param string $currency + * Returns whether this group has no payment methods * - * @return WebToPay_PaymentMethodCountry + * @return bool */ - public function filterForAmount($amount, $currency) { - $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); - foreach ($this->getGroups() as $group) { - $group = $group->filterForAmount($amount, $currency); - if (!$group->isEmpty()) { - $country->addGroup($group); + public function isEmpty(): bool + { + return count($this->paymentMethods) === 0; + } + + /** + * Loads payment methods from given XML node + */ + public function fromXmlNode(SimpleXMLElement $groupNode): void + { + foreach ($groupNode->payment_type as $paymentTypeNode) { + $key = (string)$paymentTypeNode->attributes()->key; + $titleTranslations = []; + foreach ($paymentTypeNode->title as $titleNode) { + $titleTranslations[(string)$titleNode->attributes()->language] = (string)$titleNode; + } + $logoTranslations = []; + foreach ($paymentTypeNode->logo_url as $logoNode) { + if ((string)$logoNode !== '') { + $logoTranslations[(string)$logoNode->attributes()->language] = (string)$logoNode; + } + } + $minAmount = null; + $maxAmount = null; + $currency = null; + $isIban = false; + $baseCurrency = null; + if (isset($paymentTypeNode->min)) { + $minAmount = (int)$paymentTypeNode->min->attributes()->amount; + $currency = (string)$paymentTypeNode->min->attributes()->currency; + } + if (isset($paymentTypeNode->max)) { + $maxAmount = (int)$paymentTypeNode->max->attributes()->amount; + $currency = (string)$paymentTypeNode->max->attributes()->currency; + } + + if (isset($paymentTypeNode->is_iban)) { + /* + * There are ONLY two ways to fetch value from a node of the SimpleXMLElement class: + * - use the `current` function: current($paymentTypeNode->is_iban); + * - implicitly use the `__toString()` magic method casting the node to a string. + * We chose the 2nd one + * + * FYI: the expression `(bool) $paymentTypeNode->is_iban` ALWAYS returns `true` + */ + $isIban = (string) $paymentTypeNode->is_iban === "1"; + } + if (isset($paymentTypeNode->base_currency)) { + $baseCurrency = (string)$paymentTypeNode->base_currency; } + $this->addPaymentMethod($this->createPaymentMethod( + $key, + $minAmount, + $maxAmount, + $currency, + $logoTranslations, + $titleTranslations, + $isIban, + $baseCurrency + )); } - return $country; } /** - * Returns new country instance with only those payment methods, which are returns or not iban number after payment + * Method to create new payment method instances. Overwrite if you have to use some other subclass. * - * @param boolean $isIban + * @param string $key + * @param int|null $minAmount + * @param int|null $maxAmount + * @param string|null $currency + * @param array $logoList + * @param array $titleTranslations + * @param bool $isIban + * @param mixed $baseCurrency * - * @return WebToPay_PaymentMethodCountry + * @return WebToPay_PaymentMethod */ - public function filterForIban($isIban = true) { - $country = new WebToPay_PaymentMethodCountry($this->countryCode, $this->titleTranslations, $this->defaultLanguage); - foreach ($this->getGroups() as $group) { - $group = $group->filterForIban($isIban); - if (!$group->isEmpty()) { - $country->addGroup($group); - } - } - return $country; + protected function createPaymentMethod( + string $key, + ?int $minAmount, + ?int $maxAmount, + ?string $currency, + array $logoList = [], + array $titleTranslations = [], + bool $isIban = false, + $baseCurrency = null + ): WebToPay_PaymentMethod { + return new WebToPay_PaymentMethod( + $key, + $minAmount, + $maxAmount, + $currency, + $logoList, + $titleTranslations, + $this->defaultLanguage, + $isIban, + $baseCurrency + ); } +} + + +/** + * Parses and validates callbacks + */ +class WebToPay_CallbackValidator +{ + protected WebToPay_Sign_SignCheckerInterface $signer; + + protected WebToPay_Util $util; + + protected int $projectId; + + protected ?string $password; /** - * Returns whether this country has no groups + * Constructs object * - * @return boolean + * @param integer $projectId + * @param WebToPay_Sign_SignCheckerInterface $signer + * @param WebToPay_Util $util + * @param string|null $password */ - public function isEmpty() { - return count($this->groups) === 0; + public function __construct( + int $projectId, + WebToPay_Sign_SignCheckerInterface $signer, + WebToPay_Util $util, + ?string $password = null + ) { + $this->signer = $signer; + $this->util = $util; + $this->projectId = $projectId; + $this->password = $password; } /** - * Loads groups from given XML node + * Parses callback parameters from query parameters and checks if sign is correct. + * Request has parameter "data", which is signed and holds all callback parameters + * + * @param array $requestData + * + * @return array Parsed callback parameters * - * @param SimpleXMLElement $countryNode + * @throws WebToPayException + * @throws WebToPay_Exception_Callback */ - public function fromXmlNode($countryNode) { - foreach ($countryNode->payment_group as $groupNode) { - $key = (string) $groupNode->attributes()->key; - $titleTranslations = array(); - foreach ($groupNode->title as $titleNode) { - $titleTranslations[(string) $titleNode->attributes()->language] = (string) $titleNode; + public function validateAndParseData(array $requestData): array + { + if (!isset($requestData['data'])) { + throw new WebToPay_Exception_Callback('"data" parameter not found'); + } + + $data = $requestData['data']; + + if (isset($requestData['ss1']) || isset($requestData['ss2'])) { + if (!$this->signer->checkSign($requestData)) { + throw new WebToPay_Exception_Callback('Invalid sign parameters, check $_GET length limit'); } - $this->addGroup($this->createGroup($key, $titleTranslations))->fromXmlNode($groupNode); + + $queryString = $this->util->decodeSafeUrlBase64($data); + } else { + if (null === $this->password) { + throw new WebToPay_Exception_Configuration('You have to provide project password'); + } + + $queryString = $this->util->decryptGCM( + $this->util->decodeSafeUrlBase64($data), + $this->password + ); + + if (null === $queryString) { + throw new WebToPay_Exception_Callback('Callback data decryption failed'); + } + } + $request = $this->util->parseHttpQuery($queryString); + + if (!isset($request['projectid'])) { + throw new WebToPay_Exception_Callback( + 'Project ID not provided in callback', + WebToPayException::E_INVALID + ); } + + if ((string) $request['projectid'] !== (string) $this->projectId) { + throw new WebToPay_Exception_Callback( + sprintf('Bad projectid: %s, should be: %s', $request['projectid'], $this->projectId), + WebToPayException::E_INVALID + ); + } + + if (!isset($request['type']) || !in_array($request['type'], ['micro', 'macro'], true)) { + $micro = ( + isset($request['to']) + && isset($request['from']) + && isset($request['sms']) + ); + $request['type'] = $micro ? 'micro' : 'macro'; + } + + return $request; } /** - * Method to create new group instances. Overwrite if you have to use some other group subtype. + * Checks data to have all the same parameters provided in expected array * - * @param string $groupKey - * @param array $translations + * @param array $data + * @param array $expected * - * @return WebToPay_PaymentMethodGroup + * @throws WebToPayException */ - protected function createGroup($groupKey, array $translations = array()) { - return new WebToPay_PaymentMethodGroup($groupKey, $translations, $this->defaultLanguage); + public function checkExpectedFields(array $data, array $expected): void + { + foreach ($expected as $key => $value) { + $passedValue = $data[$key] ?? null; + // there should be non-strict comparison here + if ($passedValue != $value) { + throw new WebToPayException( + sprintf('Field %s is not as expected (expected %s, got %s)', $key, $value, $passedValue) + ); + } + } } -} \ No newline at end of file +} diff --git a/build.sh b/build.sh index 01113e8..f5f239e 100644 --- a/build.sh +++ b/build.sh @@ -1,2 +1,15 @@ -cd build -php phing-latest.phar +#!/bin/bash + +container='lib_webtopay_build' + +if [ "$(docker ps -a -q -f name=$container)" ]; then + docker stop $container + docker rm $container +fi +docker build -t $container -f $PWD/Dockerfile_build $PWD + +docker run -d --name $container -v $PWD:/var/www -w /var/www $container +docker exec $container bash -c "php build/phing-latest.phar -f build/build.xml" +docker container stop $container +docker container rm --force $container +docker image rm $container diff --git a/build/build.xml b/build/build.xml index d0e19e9..ac68593 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,7 +1,7 @@ - + @@ -14,25 +14,34 @@ - <?php + <?php /* * This file is autogenerated, DO NOT EDIT - */ - + */ + +declare(strict_types=1); + + + + + - + - + + + + - + diff --git a/composer.json b/composer.json index 2714da5..8ccdf86 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "webtopay/libwebtopay", "description": "PHP Library for Paysera payment gateway integration", + "version": "3.0.0", "license": "LGPL-3.0", "authors": [ { @@ -9,9 +10,33 @@ } ], "autoload": { - "classmap": ["src/"] + "classmap": ["src/", "tests/"] }, "require": { - "php": "^5.5 || ^7.0 || ^8.0" + "php": ">=7.4", + "ext-simplexml": "*", + "ext-openssl": "*", + "ext-libxml": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "friendsofphp/php-cs-fixer": "~3.38.0", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "mockery/mockery": "^1.6", + "ext-xdebug": "*" + }, + "scripts": { + "phpunit": "php ./vendor/phpunit/phpunit/phpunit tests" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + }, + "platform": { + "php": "7.4.21" + } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..89bd9cb --- /dev/null +++ b/composer.lock @@ -0,0 +1,3983 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "091795cf779a06840a217f54c04215c5", + "packages": [], + "packages-dev": [ + { + "name": "composer/pcre", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-19T10:26:25+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-26T18:29:49+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.38.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "d872cdd543797ade030aaa307c0a4954a712e081" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d872cdd543797ade030aaa307c0a4954a712e081", + "reference": "d872cdd543797ade030aaa307c0a4954a712e081", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "composer/xdebug-handler": "^3.0.3", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/options-resolver": "^5.4 || ^6.0", + "symfony/polyfill-mbstring": "^1.27", + "symfony/polyfill-php80": "^1.27", + "symfony/polyfill-php81": "^1.27", + "symfony/process": "^5.4 || ^6.0", + "symfony/stopwatch": "^5.4 || ^6.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.0", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.5.3", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy": "^1.16", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "symfony/phpunit-bridge": "^6.2.3", + "symfony/yaml": "^5.4 || ^6.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.38.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2023-11-14T00:19:22+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.11", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "81a161d0b135df89951abd52296adf97deb0723d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/81a161d0b135df89951abd52296adf97deb0723d", + "reference": "81a161d0b135df89951abd52296adf97deb0723d", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-03-21T18:34:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + }, + "time": "2024-03-05T20:51:40+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + }, + "time": "2023-05-24T08:59:17+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.67", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-04-16T07:22:02+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", + "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10.3" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-php-parser": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.1.4" + }, + "time": "2023-08-05T09:02:04+00:00" + }, + { + "name": "phpstan/phpstan-mockery", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-mockery.git", + "reference": "88ae85931768efd3aaf3cce4cb9cb54c4d157d03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-mockery/zipball/88ae85931768efd3aaf3cce4cb9cb54c4d157d03", + "reference": "88ae85931768efd3aaf3cce4cb9cb54c4d157d03", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10" + }, + "require-dev": { + "mockery/mockery": "^1.2.4", + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan Mockery extension", + "support": { + "issues": "https://github.com/phpstan/phpstan-mockery/issues", + "source": "https://github.com/phpstan/phpstan-mockery/tree/1.1.2" + }, + "time": "2024-01-10T13:50:05+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.3.16", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95", + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16" + }, + "time": "2024-02-23T09:51:20+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:37:42+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.19", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-04-05T04:35:58+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", + "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.36" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-20T16:33:57+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-24T14:02:46+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7a69a85c7ea5bdd1e875806a99c51a87d3a74b38", + "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "540f4c73e87fd0c71ca44a6aa305d024ac68cb73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/540f4c73e87fd0c71ca44a6aa305d024ac68cb73", + "reference": "540f4c73e87fd0c71ca44a6aa305d024ac68cb73", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.38", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "899330a01056077271e2f614c7b28b0379a671eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/899330a01056077271e2f614c7b28b0379a671eb", + "reference": "899330a01056077271e2f614c7b28b0379a671eb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.38" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-21T08:05:07+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435", + "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:03:56+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.36" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-12T15:49:53+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-21T15:04:16+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "887762aa99ff16f65dc8b48aafead415f942d407" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/887762aa99ff16f65dc8b48aafead415f942d407", + "reference": "887762aa99ff16f65dc8b48aafead415f942d407", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4e232c83622bd8cd32b794216aa29d0d266d353b", + "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.36" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-01T08:49:30+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4", + "ext-simplexml": "*", + "ext-openssl": "*", + "ext-libxml": "*" + }, + "platform-dev": { + "ext-xdebug": "*" + }, + "platform-overrides": { + "php": "7.4.21" + }, + "plugin-api-version": "2.6.0" +} diff --git a/demo_shop/buy.php b/demo_shop/buy.php deleted file mode 100644 index 4650376..0000000 --- a/demo_shop/buy.php +++ /dev/null @@ -1,16 +0,0 @@ - $item, - 'id' => $id, -)); - diff --git a/demo_shop/includes/config.php b/demo_shop/includes/config.php deleted file mode 100644 index 325513c..0000000 --- a/demo_shop/includes/config.php +++ /dev/null @@ -1,28 +0,0 @@ - '73cdb059d0f29f275a34b370f8e4f900', // your password - 'projectid' => 6028, // your project id - - 'test' => 1, // disable in production - 'accepturl' => get_address('response.php?answer=accept'), - 'cancelurl' => get_address('response.php?answer=cancel'), - 'callbackurl' => get_address('response.php?answer=callback'), -); - -$shopItems = array( // just sample shop items; unrelated to WebToPay library - array( - 'title' => 'Item A', - 'price' => 100, - 'currency' => 'EUR', - ), - array( - 'title' => 'Item B', - 'price' => 2000, - 'currency' => 'EUR', - ), - array( - 'title' => 'Item C', - 'price' => 4990, - 'currency' => 'EUR', - ), -); \ No newline at end of file diff --git a/demo_shop/includes/helpers.php b/demo_shop/includes/helpers.php deleted file mode 100644 index 3597210..0000000 --- a/demo_shop/includes/helpers.php +++ /dev/null @@ -1,51 +0,0 @@ - $shopItems, -)); - diff --git a/demo_shop/orders.php b/demo_shop/orders.php deleted file mode 100644 index 799be50..0000000 --- a/demo_shop/orders.php +++ /dev/null @@ -1,11 +0,0 @@ - isset($data['orders']) ? $data['orders'] : array(), - 'sms' => isset($data['sms']) ? $data['sms'] : array() -)); \ No newline at end of file diff --git a/demo_shop/paymentMethod.php b/demo_shop/paymentMethod.php deleted file mode 100644 index 8a388b2..0000000 --- a/demo_shop/paymentMethod.php +++ /dev/null @@ -1,25 +0,0 @@ -setDefaultLanguage('en'); // set default language for titles (default: lt) - - -echo template('paymentMethod.html', array( - 'methods' => $methods, - 'post' => $post, -)); diff --git a/demo_shop/request.php b/demo_shop/request.php deleted file mode 100644 index c30b3a1..0000000 --- a/demo_shop/request.php +++ /dev/null @@ -1,33 +0,0 @@ - $item['price'], - 'currency' => $item['currency'], - 'orderid' => $orderid, -); - -$data['orderid'] = $orderid; -$data['orders'][$orderid] = array('item' => $item, 'status' => 'new', 'additionalData' => $post); -save_data($data); - -// this method builds request and sends Location header for redirecting to payment site -// as an alternative, you can use WebToPay::buildRequest and make auto-post form -WebToPay::redirectToPayment(array_merge( - $post, - $config, - $order -)); \ No newline at end of file diff --git a/demo_shop/response.php b/demo_shop/response.php deleted file mode 100644 index 7aee502..0000000 --- a/demo_shop/response.php +++ /dev/null @@ -1,66 +0,0 @@ -getMessage(); - } -} else if ('accept' == $answer) { - try { - $response = WebToPay::validateAndParseData($get, $config['projectid'], $config['sign_password']); - if ($response['status'] == 1 || $response['status'] == 2) { - // You can start providing services when you get confirmation with accept url - // Be sure to check if this order is not yet confirmed - user can refresh page anytime - // status 2 means that payment has been got but it's not yet confirmed - // @todo: get order by $response['orderid'], validate test (!), amount and currency - echo 'Your payment has been got successfuly, it will be confirmed shortly
'; - } - } catch (Exception $e) { - echo 'Your payment is not yet confirmed, system error
'; - } - echo template('Thank you for buying
Orders'); -} else { - echo template('
Payment rejected.
'); -} - diff --git a/demo_shop/sms.php b/demo_shop/sms.php deleted file mode 100644 index 4e295fe..0000000 --- a/demo_shop/sms.php +++ /dev/null @@ -1,20 +0,0 @@ -getMessage(); -} - -$data = load_data(); -$data['sms'][] = array( - '_GET' => $get, - 'parsedData' => $parsedData, -); -save_data($data); \ No newline at end of file diff --git a/demo_shop/style.css b/demo_shop/style.css deleted file mode 100644 index 8b0d851..0000000 --- a/demo_shop/style.css +++ /dev/null @@ -1,33 +0,0 @@ -form { - margin: 30px; -} -h2 { - margin-top: 50px; -} -h3 { - color: #333; -} -label { - font-weight: bold; - display: block; - margin-bottom: 10px; - width: 500px; -} -label input { - width: 300px; - float: right; -} -label input.radio { - width: auto; - float: none; -} -input[type=submit] { - margin: 16px 0; - font-weight: bold; - font-size: 120%; -} -div.error { - border: 2px solid #F00; - padding: 8px; - margin: 16px 0; -} diff --git a/demo_shop/templates/base.html.php b/demo_shop/templates/base.html.php deleted file mode 100644 index cd59d77..0000000 --- a/demo_shop/templates/base.html.php +++ /dev/null @@ -1,12 +0,0 @@ - - - - - libwebtopay demo - - - - - - - diff --git a/demo_shop/templates/buy.html.php b/demo_shop/templates/buy.html.php deleted file mode 100644 index 7923232..0000000 --- a/demo_shop/templates/buy.html.php +++ /dev/null @@ -1,16 +0,0 @@ -

Buy item

- -

-Price:
- -
- - - - - - - - - -
diff --git a/demo_shop/templates/list.html.php b/demo_shop/templates/list.html.php deleted file mode 100644 index 2f84122..0000000 --- a/demo_shop/templates/list.html.php +++ /dev/null @@ -1,7 +0,0 @@ -

Shop items

- - $item): ?> -

- Price:
- Buy - \ No newline at end of file diff --git a/demo_shop/templates/orders.html.php b/demo_shop/templates/orders.html.php deleted file mode 100644 index f1585f2..0000000 --- a/demo_shop/templates/orders.html.php +++ /dev/null @@ -1,44 +0,0 @@ -

Orders

- - $order): ?> -

- Price:
- Status:
- Additional parameters:
    - $value): ?> -
  • - -
- - Server response:
    - $value): ?> -
  • - -
- - - Additional server response:
    - $value): ?> -
  • - -
- - - -

SMS log

- - Request:
    - $value): ?> -
  • - -
- - Data:
    - $value): ?> -
  • - -
- - Data:
- - \ No newline at end of file diff --git a/demo_shop/templates/paymentMethod.html.php b/demo_shop/templates/paymentMethod.html.php deleted file mode 100644 index 4d8a685..0000000 --- a/demo_shop/templates/paymentMethod.html.php +++ /dev/null @@ -1,23 +0,0 @@ -

Select payment method

-
- getCountries() as $country): ?> -

getTitle()); ?>

- getGroups() as $group): ?> -

getTitle()); ?>

- getPaymentMethods() as $paymentMethod): ?> - getLogoUrl()): ?> - - - - - - - $value): ?> - - - -
diff --git a/demo_shop/var/__empty__ b/demo_shop/var/__empty__ deleted file mode 100755 index e69de29..0000000 diff --git a/phpunit.xml b/phpunit.xml index 2526d42..5cab81d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,7 @@ convertErrorsToExceptions = "true" convertNoticesToExceptions = "true" convertWarningsToExceptions = "true" - processIsolation = "false" + processIsolation = "true" stopOnFailure = "false" bootstrap = "src/includes.php"> @@ -17,6 +17,11 @@ tests + + + ./src + +