diff --git a/.gitignore b/.gitignore index fac1b114..5637374f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,7 @@ config.xml !/vendor/composer/ !/vendor/fig/ !/vendor/league/ +!/vendor/monolog/ !/vendor/psr/ !/vendor/sunrise/ +!/vendor/symfony/ diff --git a/CHANGELOG b/CHANGELOG index 49bb1f06..fea95f5f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +4.2.7 + - FN-13956 Minor fixes and improvements. + 4.2.6 - FN-13815 Enhanced error handling and security improvements, simplified logger instantiation, added unified error processing, improved documentation, updated external dependencies. @@ -42,6 +45,9 @@ 4.0.0 - FN-11773 Complete plugin refactoring: redesigned plugin architecture, improved errors handling, stability and reliability, code clean up (lowest supported PHP version is 7.1). +3.5.5 +- FN-12012 Fix bug with empty host in API calls from widget logic, fix bug with product category filters, integration with new widget banner/calculator based on iframe. End of official support for plugins 3.x version compatible with PHP 5.6 and PrestaShop versions below 1.6.1.11. + 3.5.4 - FN-11832 Documentation updated. diff --git a/CHANGELOG.md b/CHANGELOG.md index f2056400..4d052eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [4.2.7](https://github.com/comfino/PrestaShop/tree/4.2.7) (2026-01-09) + +[Full Changelog](https://github.com/comfino/PrestaShop/compare/4.2.6...4.2.7) + +**Merged pull requests:** + +- FN-13956 Minor fixes and improvements. [\#111](https://github.com/comfino/PrestaShop/pull/111) ([akozubskicr](https://github.com/akozubskicr)) + ## [4.2.6](https://github.com/comfino/PrestaShop/tree/4.2.6) (2025-12-30) [Full Changelog](https://github.com/comfino/PrestaShop/compare/4.2.5...4.2.6) @@ -114,6 +122,14 @@ - FN-11773 Complete plugin refactoring: redesigned plugin architecture, improved errors handling, stability and reliability, code clean up (lowest supported PHP version is 7.1). [\#92](https://github.com/comfino/PrestaShop/pull/92) ([akozubskicr](https://github.com/akozubskicr)) +## [3.5.5](https://github.com/comfino/PrestaShop/tree/3.5.5) (2025-08-14) + +[Full Changelog](https://github.com/comfino/PrestaShop/compare/3.5.4...3.5.5) + +**Merged pull requests:** + +- FN-12012 Fixed bug with empty host in API calls from widget logic, fixed bugs in product category filters logic and payment transaction processing, integration with new widget banner/calculator based on iframe. End of official support for plugins 3.x version compatible with PHP 5.6 and PrestaShop versions below 1.6.1.11. [\#91](https://github.com/comfino/PrestaShop/pull/91) ([akozubskicr](https://github.com/akozubskicr)) + ## [3.5.4](https://github.com/comfino/PrestaShop/tree/3.5.4) (2024-05-07) [Full Changelog](https://github.com/comfino/PrestaShop/compare/3.5.3...3.5.4) diff --git a/README.md b/README.md index 3734b0f7..1c811bf6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ PrestaShop payment module for Comfino deferred payments gateway - installment pa - **PrestaShop**: 1.6.x, 1.7.x, 8.x, 9.x (minimal supported version of PrestaShop is 1.6.1.11) - **PHP**: 7.1 or higher -- **PHP extensions**: curl, json, zlib +- **PHP extensions**: ctype, curl, json, zlib For legacy environments the latest version of the plugin compatible with PHP 5.6 and PrestaShop 1.6.0.14+: [3.5.5](https://github.com/comfino/PrestaShop/releases/tag/3.5.5) It can be downloaded from here: [comfino.zip](https://github.com/comfino/PrestaShop/releases/download/3.5.5/comfino.zip) @@ -32,7 +32,7 @@ We strongly recommend upgrading your store environment to at least version 1.7.8 - PHP 7.1 or higher - PrestaShop 1.6.1.11 or higher -- PHP extensions: curl, json, zlib +- PHP extensions: ctype, curl, json, zlib - Docker and Docker Compose (for local development) ### Local development setup diff --git a/comfino.php b/comfino.php index 63e36e45..64cb25f2 100644 --- a/comfino.php +++ b/comfino.php @@ -37,11 +37,11 @@ } if (!defined('COMFINO_VERSION')) { - define('COMFINO_VERSION', '4.2.6'); + define('COMFINO_VERSION', '4.2.7'); } if (!defined('COMFINO_BUILD_TS')) { - define('COMFINO_BUILD_TS', 1766606925); + define('COMFINO_BUILD_TS', 1769081918); } if (!defined('WIDGET_INIT_SCRIPT_HASH')) { @@ -63,7 +63,7 @@ public function __construct() { $this->name = 'comfino'; $this->tab = 'payments_gateways'; - $this->version = '4.2.6'; + $this->version = '4.2.7'; $this->author = 'Comfino'; $this->module_key = '3d3e14c65281e816da083e34491d5a7f'; @@ -77,7 +77,6 @@ public function __construct() $this->controllers = [ 'cacheinvalidate', 'configuration', - 'configurationrepair', 'error', 'payment', 'paymentstate', @@ -131,11 +130,7 @@ public function __construct() */ public function install() { - if (!$this->checkEnvironment(true)) { - return false; - } - - if (!parent::install()) { + if (!$this->checkEnvironment(true) || !parent::install()) { return false; } @@ -147,25 +142,7 @@ public function install() */ public function uninstall() { - if (parent::uninstall()) { - if (!COMFINO_PS_17) { - $this->unregisterHook('payment'); - $this->unregisterHook('displayPaymentEU'); - } - - $this->unregisterHook('paymentOptions'); - $this->unregisterHook('paymentReturn'); - $this->unregisterHook('displayBackofficeComfinoForm'); - $this->unregisterHook('actionOrderStatusPostUpdate'); - $this->unregisterHook('actionValidateCustomerAddressForm'); - $this->unregisterHook('header'); - $this->unregisterHook('actionAdminControllerSetMedia'); - $this->unregisterHook('displayBackOfficeHeader'); - - return !class_exists('\Comfino\Main') || Comfino\Main::uninstall(); - } - - return false; + return Comfino\Main::uninstall($this) && parent::uninstall(); } /** diff --git a/composer.json b/composer.json index cad0038b..b0f2c818 100644 --- a/composer.json +++ b/composer.json @@ -67,6 +67,7 @@ }, "require": { "php": ">=7.1", + "ext-ctype": "*", "ext-curl": "*", "ext-json": "*", "ext-zlib": "*", diff --git a/composer.lock b/composer.lock index 6f022edf..3d88705e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7f9f49a6a84f20d26db6891b32f7c97f", + "content-hash": "24635c797d2fe66e451bae21f49f2f81", "packages": [ { "name": "cache/adapter-common", @@ -344,12 +344,12 @@ "source": { "type": "git", "url": "git@github.com:comfino/shop-plugins-shared.git", - "reference": "bfb5e9c65416d6b4a04352c60cb28c18732af864" + "reference": "dbf46b5bd29989c047254c147fd3c9adfb8637dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/comfino/shop-plugins-shared/zipball/bfb5e9c65416d6b4a04352c60cb28c18732af864", - "reference": "bfb5e9c65416d6b4a04352c60cb28c18732af864", + "url": "https://api.github.com/repos/comfino/shop-plugins-shared/zipball/dbf46b5bd29989c047254c147fd3c9adfb8637dc", + "reference": "dbf46b5bd29989c047254c147fd3c9adfb8637dc", "shasum": "" }, "require": { @@ -399,7 +399,7 @@ "source": "https://github.com/comfino/shop-plugins-shared/tree/master", "issues": "https://github.com/comfino/shop-plugins-shared/issues" }, - "time": "2025-12-24T20:07:47+00:00" + "time": "2026-01-22T11:35:36+00:00" }, { "name": "fig/http-message-util", @@ -4778,16 +4778,16 @@ }, { "name": "symfony/console", - "version": "v6.4.30", + "version": "v6.4.31", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e" + "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1b2813049506b39eb3d7e64aff033fd5ca26c97e", - "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e", + "url": "https://api.github.com/repos/symfony/console/zipball/f9f8a889f54c264f9abac3fc0f7a371ffca51997", + "reference": "f9f8a889f54c264f9abac3fc0f7a371ffca51997", "shasum": "" }, "require": { @@ -4852,7 +4852,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.30" + "source": "https://github.com/symfony/console/tree/v6.4.31" }, "funding": [ { @@ -4872,7 +4872,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T13:47:41+00:00" + "time": "2025-12-22T08:30:34+00:00" }, { "name": "symfony/event-dispatcher", @@ -5106,16 +5106,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.27", + "version": "v6.4.31", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b" + "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/a1b6aa435d2fba50793b994a839c32b6064f063b", - "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b", + "url": "https://api.github.com/repos/symfony/finder/zipball/5547f2e1f0ca8e2e7abe490156b62da778cfbe2b", + "reference": "5547f2e1f0ca8e2e7abe490156b62da778cfbe2b", "shasum": "" }, "require": { @@ -5150,7 +5150,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.27" + "source": "https://github.com/symfony/finder/tree/v6.4.31" }, "funding": [ { @@ -5170,7 +5170,7 @@ "type": "tidelift" } ], - "time": "2025-10-15T18:32:00+00:00" + "time": "2025-12-11T14:52:17+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5506,16 +5506,16 @@ }, { "name": "symfony/process", - "version": "v6.4.26", + "version": "v6.4.31", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8" + "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/48bad913268c8cafabbf7034b39c8bb24fbc5ab8", - "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8", + "url": "https://api.github.com/repos/symfony/process/zipball/8541b7308fca001320e90bca8a73a28aa5604a6e", + "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e", "shasum": "" }, "require": { @@ -5547,7 +5547,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.26" + "source": "https://github.com/symfony/process/tree/v6.4.31" }, "funding": [ { @@ -5567,7 +5567,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T09:57:09+00:00" + "time": "2025-12-15T19:26:35+00:00" }, { "name": "symfony/service-contracts", @@ -5876,6 +5876,7 @@ "prefer-lowest": false, "platform": { "php": ">=7.1", + "ext-ctype": "*", "ext-curl": "*", "ext-json": "*", "ext-zlib": "*" diff --git a/controllers/front/payment.php b/controllers/front/payment.php index 2be997f5..ad17b860 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -28,6 +28,7 @@ use Comfino\Api\ApiService; use Comfino\Api\Dto\Payment\LoanTypeEnum; use Comfino\Common\Backend\Factory\OrderFactory; +use Comfino\Configuration\ConfigManager; use Comfino\Configuration\SettingsManager; use Comfino\DebugLogger; use Comfino\ErrorLogger; @@ -36,6 +37,7 @@ use Comfino\Order\OrderManager; use Comfino\Shop\Order\Order; use Comfino\Shop\Order\OrderInterface; +use Comfino\View\FrontendManager; if (!defined('_PS_VERSION_')) { exit; @@ -131,7 +133,7 @@ public function postProcess(): void $psOrder = new \Order($this->module->currentOrder); try { - if (\ValidateCore::isLoadedObject($psOrder)) { + if (\Validate::isLoadedObject($psOrder)) { $shopCart = OrderManager::getShopCartFromOrder($psOrder, $priceModifier, true); } else { $shopCart = OrderManager::getShopCart($cart, $priceModifier, true); @@ -139,26 +141,13 @@ public function postProcess(): void } catch (\Exception $e) { $this->errors = [$this->module->l('There was an error creating your cart. Please try again.')]; - DebugLogger::logEvent( - '[PAYMENT]', + FrontendManager::processError( 'Shop cart creation error', - [ - 'exception' => get_class($e), - 'error_message' => $e->getMessage(), - 'error_code' => $e->getCode(), - 'cart_id' => $cart->id, - ] - ); - - ErrorLogger::sendError( $e, - 'Shop cart creation error', - (string) $e->getCode(), - $e->getMessage(), null, null, - null, - $e->getTraceAsString() + ['cart_id' => $cart->id], + '[PAYMENT]' ); if (COMFINO_PS_17) { @@ -226,7 +215,7 @@ public function postProcess(): void // Validation passed - create the PrestaShop order (this clears the cart). $this->module->validateOrder( (int) $cart->id, - (int) Configuration::get('COMFINO_CREATED'), + (int) \Configuration::get('COMFINO_CREATED'), (float) ($shopCart->getTotalValue() / 100), $this->module->displayName, null, @@ -236,7 +225,27 @@ public function postProcess(): void $customer->secure_key ); - $orderId = (string) $this->module->currentOrder; + // Get order ID or reference based on configuration. + if ($useOrderReference = ConfigManager::getConfigurationValue('COMFINO_USE_ORDER_REFERENCE', false)) { + if (!\Validate::isLoadedObject($psOrder)) { + $psOrder = new \Order((int) $this->module->currentOrder); + } + + $orderId = !empty($psOrder->reference) ? $psOrder->reference : (string) $this->module->currentOrder; + } else { + $orderId = (string) $this->module->currentOrder; + } + + DebugLogger::logEvent( + '[PAYMENT]', + 'Order identifier for Comfino API', + [ + 'use_reference' => $useOrderReference, + 'order_id' => $orderId, + 'reference' => $psOrder->reference ?? 'not_set', + ] + ); + $returnUrl = Tools::getHttpHost(true) . __PS_BASE_URI__ . 'index.php?' . http_build_query([ 'controller' => 'order-confirmation', 'id_cart' => $cart->id, diff --git a/docs/comfino.en.md b/docs/comfino.en.md index 7372b4fb..90a8975d 100644 --- a/docs/comfino.en.md +++ b/docs/comfino.en.md @@ -2,7 +2,7 @@ - **PrestaShop**: 1.6.x, 1.7.x, 8.x, 9.x (minimal supported version of PrestaShop is 1.6.1.11) - **PHP**: 7.1 or higher -- **PHP extensions**: curl, json, zlib +- **PHP extensions**: ctype, curl, json, zlib For legacy environments the latest version of the plugin compatible with PHP 5.6 and PrestaShop 1.6.0.14+: [3.5.5](https://github.com/comfino/PrestaShop/releases/tag/3.5.5) It can be downloaded from here: [comfino.zip](https://github.com/comfino/PrestaShop/releases/download/3.5.5/comfino.zip) @@ -25,13 +25,13 @@ In this input choose module's file and click `"Send module button"`. If the modu ![Configuration](images/en/modules_ps_16.png "Configuration") ![Configuration](images/en/modules_ps_16_panel.png "Configuration") -PrestaShop 1.7, PrestaShop 8 +PrestaShop 1.7, PrestaShop 8, PrestaShop 9 ------- -To install the module, please go to `"Modules -> Modules Manager"` tab, and then click `"Upload module"` button. +To install the module, please go to `"Modules -> Module Manager"` tab, and then click `"Upload a module"` button. To the field that appears, put module's file. Module will be installed automatically. -![Configuration](images/en/modules_ps_17.png "Configuration") +![Configuration](images/en/modules_ps_9.png "Configuration") ## CONFIGURATION @@ -50,6 +50,7 @@ Configuration parameter fields: * **Production environment API key** — a unique access key that allows the module to communicate with the Comfino API (you will receive it from a Comfino representative) * **Payment text** — text displayed on the list of payment methods (default `"(Raty | Kup Teraz, Zapłać Póżniej | Finansowanie dla Firm)"`) * **Minimal amount in cart** — the value of the cart from which Comfino payment is available (default: 30 PLN) +* **Use order reference as external ID** — Use customer-visible order reference instead of numeric order ID for Comfino API integration. New orders only. ![Configuration](images/en/configuration1.png "Configuration") @@ -68,8 +69,9 @@ By default, Comfino payments are available unconditionally for all product types **Basic Settings** * **Widget is active?** — promotional widget activation/deactivation switch on the product page -* **Widget type** — way of presenting the widget [`Installment calculator`, `Extended calculator - products`] +* **Widget type** — way of presenting the widget [`Standard widget`, `Classic widget`] * **Offer types** — types of financing offers promoted [`Zero percent installments`, `Convenient installments`, `Pay later`, `Installments for companies`, `Deferred payments for companies`, `Leasing`] +* **Show logos of financial services providers** — switch enabling to display the logo of each available financial provider The availability of offer types on the list depends on the individual contract and may differ from that described in the documentation. @@ -110,9 +112,44 @@ Before launching payments on the production store, disable developer mode to blo **PLUGIN DIAGNOSTICS** The tab contains technical information about the plugin and the environment (plugin version, store version, PHP and web server version, etc.).\ + +![Configuration](images/en/configuration5a.png "Configuration") + It also contains a list of recent errors with a preview of the local error log and a list of the plugin's internal operations recorded in debug mode (debug mode log). +There is also an option to clear the error log as well as the list of internal operations in debug mode. + +![Configuration](images/en/configuration5b.png "Configuration") + +The **Module reset** section allows you to restore the module to its initial configuration without losing any data or individual business settings.\ +The reset operation performs the following actions: +* Adds missing configuration options – supplements the configuration with missing settings while retaining existing values. +* Registers all PrestaShop hooks – ensures the module is properly connected to the store's system mechanisms. +* Restores custom order statuses – restores statuses used by the module if they have been deleted or corrupted. +* Clears the module cache – clears the cache, eliminating issues resulting from outdated data. + +**Note**: Resetting the module does not delete existing configuration or data (e.g., settings, mappings, transaction history). + +To perform a reset, click the "Reset module" button. + +![Konfiguracja](images/en/configuration5b1.png "Konfiguracja") +![Konfiguracja](images/en/configuration5b2.png "Konfiguracja") + +This tab also contains a record of operations performed during module installation, update and uninstallation. + +**When to view logs:** + +Installation log: +* **After initial module installation – verifying correct configuration** +* **When problems occur with order statuses or hooks** + +Update log: +* **After updating the module to a newer version** +* **When unexpected errors occur after updating** + +Uninstallation log: +* **After uninstallation – verifying that the process completed correctly** -![Configuration](images/en/configuration5.png "Configuration") +![Konfiguracja](images/en/configuration5c.png "Konfiguracja") Information about developer mode activity is displayed in the tabs `"PAYMENT SETTINGS"` and `"PLUGIN DIAGNOSTICS"`. In this mode, the plugin uses the key from the `"DEVELOPER SETTINGS"` tab to communicate with the Comfino test API. You will also receive a test environment key from a Comfino representative. diff --git a/docs/comfino.pl.md b/docs/comfino.pl.md index 9022bfda..d5d7e008 100644 --- a/docs/comfino.pl.md +++ b/docs/comfino.pl.md @@ -2,7 +2,7 @@ - **PrestaShop**: 1.6.x, 1.7.x, 8.x, 9.x (minimalna wspierana wersja PrestaShop to 1.6.1.11) - **PHP**: 7.1 lub nowszy -- **Rozszerzenia PHP**: curl, json, zlib +- **Rozszerzenia PHP**: ctype, curl, json, zlib W przypadku starszych środowisk najnowsza wersja wtyczki zgodna z PHP 5.6 i PrestaShop 1.6.0.14+: [3.5.5](https://github.com/comfino/PrestaShop/releases/tag/3.5.5) Może zostać pobrana stąd: [comfino.zip](https://github.com/comfino/PrestaShop/releases/download/3.5.5/comfino.zip) @@ -25,17 +25,18 @@ W tym polu należy wybrać plik z modułem, a następnie kliknąć `"Prześlij m ![Konfiguracja](images/pl/modules_ps_16.png "Konfiguracja") ![Konfiguracja](images/pl/modules_ps_16_panel.png "Konfiguracja") -PrestaShop 1.7, PrestaShop 8 +PrestaShop 1.7, PrestaShop 8, PrestaShop 9 ------- -Przejdź do zakładki `"Moduły -> Manager modułów"`, następnie kliknij przycisk `"Załaduj moduł"`. Z wyświetlonego pola do przesyłania modułów, należy wybrać odpowiedni plik z modułem. Wtyczka zainstaluje się automatycznie. +Przejdź do zakładki `"Moduły -> Menedżer modułów"`, następnie kliknij przycisk `"Załaduj moduł"`. Z wyświetlonego pola do przesyłania modułów, należy wybrać odpowiedni plik z modułem. Wtyczka zainstaluje się automatycznie. -![Konfiguracja](images/pl/modules_ps_17.png "Konfiguracja") +![Konfiguracja](images/pl/modules_ps_9.png "Konfiguracja") ## KONFIGURACJA Parametry konfiguracyjne modułu są pogrupowane kategoriami odpowiadającymi zakładkom panelu konfiguracyjnego: `"USTAWIENIA PŁATNOŚCI"`, `"USTAWIENIA SPRZEDAŻY"`, `"USTAWIENIA WIDGETU"`, `"USTAWIENIA DEWELOPERSKIE"`. -Ostatnia zakładka `"DIAGNOSTYKA WTYCZKI"` nie zawiera żadnych parametrów do ustawienia i pełni funkcję informacyjno-diagnostyczną. Zawiera między innymi lokalny dziennik błędów (log błędów). +Ostatnia zakładka `"DIAGNOSTYKA WTYCZKI"` nie zawiera żadnych parametrów do ustawienia i pełni funkcję informacyjno-diagnostyczną. +Zawiera między innymi informacje o zainstalowanej wtyczce, lokalny dziennik błędów (log błędów) oraz logi instalacji, aktualizacji i dezinstalacji. Przed użyciem płatności Comfino, moduł musi zostać skonfigurowany. Możesz to zrobić, klikając `"Konfiguruj"` w panelu informacyjnym modułu. @@ -49,6 +50,7 @@ Pola parametrów konfiguracyjnych: * **Klucz API środowiska produkcyjnego** — unikalny klucz dostępowy umożliwiający komunikację modułu z API Comfino (otrzymasz go od przedstawiciela Comfino) * **Tekst płatności** — tekst wyświetlany na liście metod płatności (domyślnie `"(Raty | Kup Teraz, Zapłać Później | Finansowanie dla Firm)"`) * **Minimalna kwota w koszyku** — wartość koszyka, od której dostępna jest płatność Comfino (domyślnie: 30 zł) +* **Użyj numeru zamówienia jako zewnętrznego ID** — Używaj numeru zamówienia widocznego dla klienta zamiast numerycznego ID zamówienia w komunikacji z API Comfino. Dotyczy tylko nowych zamówień. ![Konfiguracja](images/pl/configuration1.png "Konfiguracja") @@ -67,8 +69,9 @@ Domyślnie płatności Comfino są dostępne bezwarunkowo dla wszystkich typów **Ustawienia podstawowe** * **Widget włączony?** — przełącznik aktywacji/deaktywacji widgetu promocyjnego na stronie produktu -* **Typ widgetu** — sposób prezentacji widgetu [`Kalkulator rat`, `Rozszerzony kalkulator - produkty`] +* **Typ widgetu** — sposób prezentacji widgetu [`Widget standardowy`, `Widget klasyczny`] * **Typy ofert** — typy promowanych ofert finansowania [`Raty zero procent`, `Niskie raty`, `Zapłać później`, `Raty dla firm`, `Odroczone płatności dla firm`, `Leasing`] +* **Pokaż loga dostawców usług finansowych** — przełącznik umożliwiający pokazanie loga każdego z dostępnych dostawców finansowych Dostępność typów ofert na liście jest uzależniona od indywidualnej umowy i może różnić się od tej opisanej w dokumentacji. @@ -109,9 +112,44 @@ Przed uruchomieniem płatności na sklepie produkcyjnym, wyłącz tryb deweloper **DIAGNOSTYKA WTYCZKI** Zakładka zawiera informacje techniczne o wtyczce i środowisku (wersja wtyczki, wersja sklepu, wersja PHP i serwera www, itp.).\ + +![Konfiguracja](images/pl/configuration5a.png "Konfiguracja") + Zawiera też listę ostatnich błędów wraz z podglądem lokalnego dziennika błędów (log błędów) oraz listę z zapisanymi w trybie debug operacjami wewnętrznymi wtyczki (log trybu debugowania). +Jest również opcja wyczyszczenia dziennika błędów, jak i listy operacji wewnętrznych w trybie debug. + +![Konfiguracja](images/pl/configuration5b.png "Konfiguracja") + +Sekcja **Reset modułu** służy do przywrócenia modułu do stanu początkowej konfiguracji bez utraty danych ani indywidualnych ustawień biznesowych.\ +Operacja resetu wykonuje następujące czynności: +* Dodaje brakujące opcje konfiguracyjne – uzupełnia konfigurację o brakujące ustawienia, zachowując już istniejące wartości. +* Ponownie rejestruje wszystkie hooki PrestaShop – zapewnia poprawne podpięcie modułu do mechanizmów systemowych sklepu. +* Odtwarza niestandardowe statusy zamówień – przywraca statusy wykorzystywane przez moduł, jeśli zostały usunięte lub uszkodzone. +* Czyści pamięć podręczną modułu – usuwa cache, co pozwala wyeliminować problemy wynikające z nieaktualnych danych. + +**Uwaga**: Reset modułu nie usuwa istniejącej konfiguracji ani danych (np. ustawień, mapowań, historii operacji). + +Aby wykonać reset, należy użyć przycisku „Zresetuj moduł”. + +![Konfiguracja](images/pl/configuration5b1.png "Konfiguracja") +![Konfiguracja](images/pl/configuration5b2.png "Konfiguracja") + +Zakładka zawiera dodatkowo zapis operacji wykonanych podczas instalacji, aktualizacji i dezinstalacji modułu. + +**Kiedy przeglądać logi:** + +Log instalacyjny: +* **Po pierwszej instalacji modułu – weryfikacja poprawnej konfiguracji** +* **Gdy występują problemy ze statusami zamówień lub hook-ami** + +Log aktualizacji: +* **Po aktualizacji modułu do nowszej wersji** +* **Gdy po aktualizacji pojawiają się nieoczekiwane błędy** + +Log dezinstalacji: +* **Po odinstalowaniu – weryfikacja, czy proces zakończył się prawidłowo** -![Konfiguracja](images/pl/configuration5.png "Konfiguracja") +![Konfiguracja](images/pl/configuration5c.png "Konfiguracja") Informacja o aktywności trybu deweloperskiego jest wyświetlana w zakładkach `"USTAWIENIA PŁATNOŚCI"` i `"DIAGNOSTYKA WTYCZKI"`. W trybie tym wtyczka używa klucza z zakładki `"USTAWIENIA DEWELOPERSKIE"` do komunikacji z testowym API Comfino. Klucz środowiska testowego również otrzymasz od przedstawiciela Comfino. diff --git a/docs/images/en/configuration1.png b/docs/images/en/configuration1.png index 40fd14d6..140ff703 100644 Binary files a/docs/images/en/configuration1.png and b/docs/images/en/configuration1.png differ diff --git a/docs/images/en/configuration2.png b/docs/images/en/configuration2.png index d6f9e711..5fd469e4 100644 Binary files a/docs/images/en/configuration2.png and b/docs/images/en/configuration2.png differ diff --git a/docs/images/en/configuration3a.png b/docs/images/en/configuration3a.png index 778fd3f4..970cb1b8 100644 Binary files a/docs/images/en/configuration3a.png and b/docs/images/en/configuration3a.png differ diff --git a/docs/images/en/configuration3b.png b/docs/images/en/configuration3b.png index 7cb6bbcf..f3f7e0b4 100644 Binary files a/docs/images/en/configuration3b.png and b/docs/images/en/configuration3b.png differ diff --git a/docs/images/en/configuration4.png b/docs/images/en/configuration4.png index 4d2ccc4e..cac2ab31 100644 Binary files a/docs/images/en/configuration4.png and b/docs/images/en/configuration4.png differ diff --git a/docs/images/en/configuration5.png b/docs/images/en/configuration5.png deleted file mode 100644 index fa46977d..00000000 Binary files a/docs/images/en/configuration5.png and /dev/null differ diff --git a/docs/images/en/configuration5a.png b/docs/images/en/configuration5a.png new file mode 100644 index 00000000..72ababb4 Binary files /dev/null and b/docs/images/en/configuration5a.png differ diff --git a/docs/images/en/configuration5b.png b/docs/images/en/configuration5b.png new file mode 100644 index 00000000..8e776eac Binary files /dev/null and b/docs/images/en/configuration5b.png differ diff --git a/docs/images/en/configuration5b1.png b/docs/images/en/configuration5b1.png new file mode 100644 index 00000000..f49aba45 Binary files /dev/null and b/docs/images/en/configuration5b1.png differ diff --git a/docs/images/en/configuration5b2.png b/docs/images/en/configuration5b2.png new file mode 100644 index 00000000..2045cfd5 Binary files /dev/null and b/docs/images/en/configuration5b2.png differ diff --git a/docs/images/en/configuration5c.png b/docs/images/en/configuration5c.png new file mode 100644 index 00000000..17bc69cd Binary files /dev/null and b/docs/images/en/configuration5c.png differ diff --git a/docs/images/en/modules_ps_17.png b/docs/images/en/modules_ps_17.png deleted file mode 100644 index f9a47bf4..00000000 Binary files a/docs/images/en/modules_ps_17.png and /dev/null differ diff --git a/docs/images/en/modules_ps_9.png b/docs/images/en/modules_ps_9.png new file mode 100644 index 00000000..2c81f4f7 Binary files /dev/null and b/docs/images/en/modules_ps_9.png differ diff --git a/docs/images/pl/configuration1.png b/docs/images/pl/configuration1.png index 3167599c..92e19728 100644 Binary files a/docs/images/pl/configuration1.png and b/docs/images/pl/configuration1.png differ diff --git a/docs/images/pl/configuration2.png b/docs/images/pl/configuration2.png index 9ff1cddd..c281131d 100644 Binary files a/docs/images/pl/configuration2.png and b/docs/images/pl/configuration2.png differ diff --git a/docs/images/pl/configuration3a.png b/docs/images/pl/configuration3a.png index 09182447..9906636c 100644 Binary files a/docs/images/pl/configuration3a.png and b/docs/images/pl/configuration3a.png differ diff --git a/docs/images/pl/configuration3b.png b/docs/images/pl/configuration3b.png index fee93cfc..df4edd98 100644 Binary files a/docs/images/pl/configuration3b.png and b/docs/images/pl/configuration3b.png differ diff --git a/docs/images/pl/configuration4.png b/docs/images/pl/configuration4.png index 2b451aec..3bf69d93 100644 Binary files a/docs/images/pl/configuration4.png and b/docs/images/pl/configuration4.png differ diff --git a/docs/images/pl/configuration5.png b/docs/images/pl/configuration5.png deleted file mode 100644 index fcab32d6..00000000 Binary files a/docs/images/pl/configuration5.png and /dev/null differ diff --git a/docs/images/pl/configuration5a.png b/docs/images/pl/configuration5a.png new file mode 100644 index 00000000..94bcd1a5 Binary files /dev/null and b/docs/images/pl/configuration5a.png differ diff --git a/docs/images/pl/configuration5b.png b/docs/images/pl/configuration5b.png new file mode 100644 index 00000000..25e5e823 Binary files /dev/null and b/docs/images/pl/configuration5b.png differ diff --git a/docs/images/pl/configuration5b1.png b/docs/images/pl/configuration5b1.png new file mode 100644 index 00000000..5b3b7903 Binary files /dev/null and b/docs/images/pl/configuration5b1.png differ diff --git a/docs/images/pl/configuration5b2.png b/docs/images/pl/configuration5b2.png new file mode 100644 index 00000000..75677414 Binary files /dev/null and b/docs/images/pl/configuration5b2.png differ diff --git a/docs/images/pl/configuration5c.png b/docs/images/pl/configuration5c.png new file mode 100644 index 00000000..eddcc26c Binary files /dev/null and b/docs/images/pl/configuration5c.png differ diff --git a/docs/images/pl/modules_ps_17.png b/docs/images/pl/modules_ps_17.png deleted file mode 100644 index 65a2fadb..00000000 Binary files a/docs/images/pl/modules_ps_17.png and /dev/null differ diff --git a/docs/images/pl/modules_ps_9.png b/docs/images/pl/modules_ps_9.png new file mode 100644 index 00000000..25055e56 Binary files /dev/null and b/docs/images/pl/modules_ps_9.png differ diff --git a/src/Api/ApiClient.php b/src/Api/ApiClient.php index 76851438..4f2bba42 100644 --- a/src/Api/ApiClient.php +++ b/src/Api/ApiClient.php @@ -174,7 +174,7 @@ public static function processApiError(string $errorPrefix, \Throwable $exceptio ErrorLogger::sendError( $exception, $errorPrefix, - $exception->getCode(), + (string) $exception->getCode(), $exception->getMessage(), $url !== '' ? $url : null, $requestBody !== '' ? $requestBody : null, diff --git a/src/Api/ApiService.php b/src/Api/ApiService.php index a49ffb36..bc50e472 100644 --- a/src/Api/ApiService.php +++ b/src/Api/ApiService.php @@ -29,7 +29,6 @@ use Comfino\Common\Backend\Factory\ApiServiceFactory; use Comfino\Common\Backend\RestEndpoint\CacheInvalidate; use Comfino\Common\Backend\RestEndpoint\Configuration; -use Comfino\Common\Backend\RestEndpoint\ConfigurationRepair; use Comfino\Common\Backend\RestEndpoint\StatusNotification; use Comfino\Common\Backend\RestEndpointManager; use Comfino\Common\Shop\Order\StatusManager; @@ -85,15 +84,6 @@ public static function init(): void CacheManager::getCachePool() ) ); - - self::getEndpointManager()->registerEndpoint( - new ConfigurationRepair( - 'configurationRepair', - self::getControllerUrl('configurationrepair', [], false), - [ConfigManager::class, 'validateConfigurationIntegrity'], - [ConfigManager::class, 'repairMissingConfigurationOptions'] - ) - ); } public static function getControllerUrl( diff --git a/src/Configuration/ConfigManager.php b/src/Configuration/ConfigManager.php index 94974cee..53751df7 100644 --- a/src/Configuration/ConfigManager.php +++ b/src/Configuration/ConfigManager.php @@ -52,6 +52,7 @@ final class ConfigManager 'COMFINO_API_KEY' => ConfigurationManager::OPT_VALUE_TYPE_STRING, 'COMFINO_PAYMENT_TEXT' => ConfigurationManager::OPT_VALUE_TYPE_STRING, 'COMFINO_MINIMAL_CART_AMOUNT' => ConfigurationManager::OPT_VALUE_TYPE_FLOAT, + 'COMFINO_USE_ORDER_REFERENCE' => ConfigurationManager::OPT_VALUE_TYPE_BOOL, ], 'sale_settings' => [ 'COMFINO_PRODUCT_CATEGORY_FILTERS' => ConfigurationManager::OPT_VALUE_TYPE_JSON, @@ -103,6 +104,7 @@ final class ConfigManager // Payment settings 'COMFINO_PAYMENT_TEXT', 'COMFINO_MINIMAL_CART_AMOUNT', + 'COMFINO_USE_ORDER_REFERENCE', // Sale settings 'COMFINO_PRODUCT_CATEGORY_FILTERS', // Widget settings @@ -416,7 +418,7 @@ public static function deleteConfigurationValues(?array $configurationOptions = } } else { foreach (self::CONFIG_OPTIONS as $options) { - foreach ($options as $optionName) { + foreach (array_keys($options) as $optionName) { $result &= \Configuration::deleteByName($optionName); } } @@ -443,7 +445,7 @@ public static function updateWidgetCode(?string $lastWidgetCodeHash = null): boo ErrorLogger::sendError( $e, 'Widget code update', - $e->getCode(), + (string) $e->getCode(), $e->getMessage(), null, null, @@ -543,6 +545,7 @@ public static function getDefaultConfigurationValues(): array return [ 'COMFINO_PAYMENT_TEXT' => '(Raty | Kup Teraz, Zapłać Później | Finansowanie dla Firm)', 'COMFINO_MINIMAL_CART_AMOUNT' => 30, + 'COMFINO_USE_ORDER_REFERENCE' => false, 'COMFINO_IS_SANDBOX' => false, 'COMFINO_DEBUG' => false, 'COMFINO_SERVICE_MODE' => false, @@ -580,14 +583,18 @@ public static function getDefaultConfigurationValues(): array ]; } - public static function initConfigurationValues(): void + public static function initConfigurationValues(): bool { + $result = true; + foreach (self::getDefaultConfigurationValues() as $optName => $optValue) { // Avoid overwriting of existing configuration options if plugin is reinstalled/upgraded. if (!\Configuration::hasKey($optName)) { - \Configuration::updateValue($optName, $optValue); + $result &= \Configuration::updateValue($optName, $optValue); } } + + return $result; } /** @@ -604,9 +611,7 @@ public static function initConfigurationValues(): void */ public static function repairMissingConfigurationOptions(): array { - ErrorLogger::init(); - - $stats = [ + $resultStats = [ 'checked' => 0, 'missing' => 0, 'repaired' => 0, @@ -615,56 +620,28 @@ public static function repairMissingConfigurationOptions(): array 'options_failed' => [], ]; - $defaultValues = self::getDefaultConfigurationValues(); - - foreach ($defaultValues as $optName => $optValue) { - $stats['checked']++; + foreach (self::getDefaultConfigurationValues() as $optName => $optValue) { + $resultStats['checked']++; if (!\Configuration::hasKey($optName) && \Configuration::get($optName) !== $optValue) { - $stats['missing']++; + $resultStats['missing']++; try { if (\Configuration::updateValue($optName, $optValue)) { - $stats['repaired']++; - $stats['options_repaired'][] = $optName; + $resultStats['repaired']++; + $resultStats['options_repaired'][] = $optName; } else { - $stats['failed']++; - $stats['options_failed'][] = $optName; + $resultStats['failed']++; + $resultStats['options_failed'][] = $optName; } } catch (\Throwable $e) { - $stats['failed']++; - $stats['options_failed'][] = $optName; + $resultStats['failed']++; + $resultStats['options_failed'][] = $optName; } } } - return $stats; - } - - /** - * Validates that all required configuration options exist. - * - * @return array Array with keys: - * - 'valid': boolean indicating if all options exist - * - 'missing_options': array of missing option names - * - 'total_options': total number of expected options - */ - public static function validateConfigurationIntegrity(): array - { - $defaultValues = self::getDefaultConfigurationValues(); - $missingOptions = []; - - foreach ($defaultValues as $optName => $optValue) { - if (!\Configuration::hasKey($optName) && \Configuration::get($optName) !== $optValue) { - $missingOptions[] = $optName; - } - } - - return [ - 'valid' => empty($missingOptions), - 'missing_options' => $missingOptions, - 'total_options' => count($defaultValues), - ]; + return $resultStats; } /** diff --git a/src/Main.php b/src/Main.php index a07b0d3b..64f721e0 100644 --- a/src/Main.php +++ b/src/Main.php @@ -29,12 +29,14 @@ use Comfino; use Comfino\Api\ApiClient; use Comfino\Api\ApiService; +use Comfino\Common\Backend\FileUtils; use Comfino\Configuration\ConfigManager; use Comfino\Configuration\SettingsManager; use Comfino\FinancialProduct\ProductTypesListTypeEnum; use Comfino\Order\OrderManager; use Comfino\Order\ShopStatusManager; use Comfino\PluginShared\CacheManager; +use Comfino\View\FrontendManager; use Comfino\View\SettingsForm; use Comfino\View\TemplateManager; @@ -44,6 +46,25 @@ final class Main { + private const INSTALL_LOG_FILENAME = 'install.log'; + private const UPGRADE_LOG_FILENAME = 'upgrade.log'; + private const UNINSTALL_LOG_FILENAME = 'uninstall.log'; + + private const HOOKS = [ + 'paymentOptions', + 'paymentReturn', + 'displayBackofficeComfinoForm', + 'actionOrderStatusPostUpdate', + 'header', + 'actionAdminControllerSetMedia', + 'displayBackOfficeHeader', + ]; + + private const PS16_HOOKS = [ + 'payment', + 'displayPaymentEU', + ]; + /** @var bool */ private static $initialized = false; @@ -70,34 +91,152 @@ public static function install(\Comfino $module): bool { ErrorLogger::init(); - ConfigManager::initConfigurationValues(); - ShopStatusManager::addCustomOrderStatuses(); + $resultStats = [ + 'statuses_created' => 0, + 'statuses_updated' => 0, + 'statuses_create_failed' => 0, + 'statuses_update_failed' => 0, + 'hooks_registered' => 0, + 'hooks_failed' => 0, + 'operations' => [], + ]; - if (!COMFINO_PS_17) { - // Register PrestaShop 1.6 hooks. - $module->registerHook('payment'); - $module->registerHook('displayPaymentEU'); + if (ConfigManager::initConfigurationValues()) { + $resultStats['operations'][] = ['name' => 'init_configuration_options', 'success' => true]; + } else { + $resultStats['operations'][] = ['name' => 'init_configuration_options', 'success' => false]; } - $module->registerHook('paymentOptions'); - $module->registerHook('paymentReturn'); - $module->registerHook('displayBackofficeComfinoForm'); - $module->registerHook('actionOrderStatusPostUpdate'); - $module->registerHook('header'); - $module->registerHook('actionAdminControllerSetMedia'); - $module->registerHook('displayBackOfficeHeader'); + $customStatusStats = ShopStatusManager::addCustomOrderStatuses($module); + $resultStats = array_merge($resultStats, array_diff_key($customStatusStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $customStatusStats['operations']); - return true; + $hookStats = self::registerHooks($module); + $resultStats = array_merge($resultStats, array_diff_key($hookStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $hookStats['operations']); + + self::createInstallLog(print_r($resultStats, true)); + + return $resultStats['statuses_create_failed'] === 0 + && $resultStats['statuses_update_failed'] === 0 + && $resultStats['hooks_failed'] === 0; } - public static function uninstall(): bool + public static function uninstall(\Comfino $module): bool { - ConfigManager::deleteConfigurationValues(); + ErrorLogger::init(); + + $resultStats = [ + 'statuses_removed' => 0, + 'statuses_updated' => 0, + 'statuses_remove_failed' => 0, + 'statuses_update_failed' => 0, + 'hooks_unregistered' => 0, + 'hooks_failed' => 0, + 'operations' => [], + ]; + + $resultStats['operations'][] = [ + 'name' => 'hooks_registration', + 'success' => $resultStats['hooks_failed'] === 0, + 'registered' => $resultStats['hooks_registered'], + 'failed' => $resultStats['hooks_failed'], + ]; + + if (ConfigManager::deleteConfigurationValues()) { + $resultStats['operations'][] = ['name' => 'configuration_options_delete', 'success' => true]; + } else { + $resultStats['operations'][] = ['name' => 'configuration_options_delete', 'success' => false]; + } + + $customStatusStats = ShopStatusManager::removeCustomOrderStatuses(); + $resultStats = array_merge($resultStats, array_diff_key($customStatusStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $customStatusStats['operations']); + + $hookStats = self::unregisterHooks($module); + $resultStats = array_merge($resultStats, array_diff_key($hookStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $hookStats['operations']); + + if (ApiClient::getInstance()->notifyPluginRemoval()) { + $resultStats['operations'][] = ['name' => 'uninstall_notification_sent' , 'success' => true]; + } else { + $resultStats['operations'][] = ['name' => 'uninstall_notification_sent' , 'success' => false]; + } + + self::createUninstallLog(print_r($resultStats, true)); + return $resultStats['statuses_remove_failed'] === 0 + && $resultStats['statuses_update_failed'] === 0 + && $resultStats['hooks_failed'] === 0; + } + + /** + * Resets module to initial state without uninstalling. + * + * This method: + * - Adds missing configuration options. + * - Re-registers all PrestaShop hooks. + * - Recreates custom order statuses. + * - Clears configuration and frontend cache. + * + * @return array Reset operation statistics + */ + public static function reset(\Comfino $module): array + { ErrorLogger::init(); - ApiClient::getInstance()->notifyPluginRemoval(); - return true; + $resultStats = [ + 'config_repaired' => 0, + 'config_failed' => 0, + 'hooks_registered' => 0, + 'hooks_failed' => 0, + 'statuses_created' => 0, + 'statuses_updated' => 0, + 'statuses_create_failed' => 0, + 'statuses_update_failed' => 0, + 'operations' => [], + ]; + + // 1. Repair missing configuration options. + try { + $repairStats = ConfigManager::repairMissingConfigurationOptions(); + + $resultStats['config_repaired'] = $repairStats['repaired']; + $resultStats['config_failed'] = $repairStats['failed']; + $resultStats['operations'][] = [ + 'name' => 'configuration_repair', + 'success' => $repairStats['failed'] === 0, + 'details' => $repairStats, + ]; + } catch (\Exception $e) { + $resultStats['operations'][] = [ + 'name' => 'configuration_repair', + 'success' => false, + 'error' => $e->getMessage(), + ]; + } + + // 2. Re-register all PrestaShop hooks. + $hookStats = self::registerHooks($module); + $resultStats = array_merge($resultStats, array_diff_key($hookStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $hookStats['operations']); + + $resultStats['operations'][] = [ + 'name' => 'hooks_registration', + 'success' => $resultStats['hooks_failed'] === 0, + 'registered' => $resultStats['hooks_registered'], + 'failed' => $resultStats['hooks_failed'], + ]; + + // 3. Reinitialize, repair and recreate custom order statuses. + $reinitStats = ShopStatusManager::reinitializeCustomOrderStatuses($module); + $resultStats = array_merge($resultStats, array_diff_key($reinitStats, ['operations' => []])); + $resultStats['operations'] = array_merge($resultStats['operations'], $reinitStats['operations']); + + // 4. Clear configuration and frontend cache. + CacheManager::getCachePool()->clear(); + + return $resultStats; } /** @@ -187,16 +326,7 @@ public static function renderPaywallIframe(\Comfino $module, array $params) $total = round($cart->getOrderTotal(), 2); $totalFormatted = $tools->formatPrice($total, $cart->id_currency); } catch (\Exception $e) { - ErrorLogger::sendError( - $e, - 'Paywall rendering error', - $e->getCode(), - $e->getMessage(), - null, - null, - null, - $e->getTraceAsString() - ); + FrontendManager::processError('Paywall rendering error', $e); return COMFINO_PS_17 ? [] : ''; } @@ -287,9 +417,14 @@ public static function getRequestUri(): string return isset($_SERVER['REQUEST_URI']) ? \Tools::safeOutput($_SERVER['REQUEST_URI']) : ''; } + public static function getVarPath(): string + { + return _PS_MODULE_DIR_ . COMFINO_MODULE_NAME . DIRECTORY_SEPARATOR . 'var'; + } + public static function getCacheRootPath(): string { - return dirname(__DIR__) . '/var'; + return self::getVarPath(); } public static function getCachePath(): string @@ -354,4 +489,145 @@ public static function addStyleLink(string $id, $styleUrl): void \Context::getContext()->controller->addCSS($styleUrl); } } + + public static function updateUpgradeLog(string $logContents): void + { + self::appendLog(self::UPGRADE_LOG_FILENAME, $logContents); + } + + public static function readUpgradeLog(): string + { + return self::readLog(self::UPGRADE_LOG_FILENAME); + } + + public static function readInstallLog(): string + { + return self::readLog(self::INSTALL_LOG_FILENAME); + } + + public static function readUninstallLog(): string + { + return self::readLog(self::UNINSTALL_LOG_FILENAME); + } + + private static function createInstallLog(string $logContents): void + { + self::writeLog(self::INSTALL_LOG_FILENAME, $logContents); + } + + private static function createUninstallLog(string $logContents): void + { + self::writeLog(self::UNINSTALL_LOG_FILENAME, $logContents); + } + + private static function readLog(string $fileName): string + { + $logPath = FileUtils::buildPathFromComponents([self::getVarPath(), 'log', $fileName]); + + if (FileUtils::isReadable($logPath)) { + return FileUtils::read($logPath); + } + + return ''; + } + + private static function writeLog(string $fileName, string $logContents): void + { + $logPath = FileUtils::buildPathFromComponents([self::getVarPath(), 'log', $fileName]); + + if (FileUtils::isWritable(dirname($logPath))) { + FileUtils::write($logPath, gmdate('Y-m-d H:i:s') . "\n$logContents"); + } + } + + private static function appendLog(string $fileName, string $logContents): void + { + $logPath = FileUtils::buildPathFromComponents([self::getVarPath(), 'log', $fileName]); + + if (FileUtils::isWritable($logPath)) { + FileUtils::append($logPath, gmdate('Y-m-d H:i:s') . "\n$logContents"); + } + } + + private static function registerHooks(\Comfino $module): array + { + $hooks = self::HOOKS; + + // Add PrestaShop 1.6 specific hooks. + if (!COMFINO_PS_17) { + $hooks = array_merge($hooks, self::PS16_HOOKS); + } + + $resultStats = [ + 'hooks_registered' => 0, + 'hooks_failed' => 0, + 'operations' => [], + ]; + + foreach ($hooks as $hookName) { + try { + if ($module->registerHook($hookName)) { + $resultStats['hooks_registered']++; + $resultStats['operations'][] = [ + 'name' => 'hook_registration', + 'success' => true, + 'hook' => $hookName, + ]; + } else { + $resultStats['hooks_failed']++; + $resultStats['operations'][] = [ + 'name' => 'hook_registration', + 'success' => false, + 'hook' => $hookName, + ]; + } + } catch (\Exception $e) { + $resultStats['hooks_failed']++; + $resultStats['operations'][] = [ + 'name' => 'hook_registration', + 'success' => false, + 'hook' => $hookName, + 'error' => $e->getMessage(), + ]; + } + } + + return $resultStats; + } + + private static function unregisterHooks(\Comfino $module): array + { + $hooks = self::HOOKS; + + // Add PrestaShop 1.6 specific hooks. + if (!COMFINO_PS_17) { + $hooks = array_merge($hooks, self::PS16_HOOKS); + } + + $resultStats = [ + 'hooks_unregistered' => 0, + 'hooks_failed' => 0, + 'operations' => [], + ]; + + foreach ($hooks as $hookName) { + try { + if ($module->unregisterHook($hookName)) { + ++$resultStats['hooks_unregistered']; + } else { + ++$resultStats['hooks_failed']; + } + } catch (\Exception $e) { + ++$resultStats['hooks_failed']; + $resultStats['operations'][] = [ + 'name' => 'hook_unregistration', + 'success' => false, + 'hook' => $hookName, + 'error' => $e->getMessage(), + ]; + } + } + + return $resultStats; + } } diff --git a/src/Order/OrderManager.php b/src/Order/OrderManager.php index 89ccd82d..5c320fa1 100644 --- a/src/Order/OrderManager.php +++ b/src/Order/OrderManager.php @@ -39,16 +39,35 @@ exit; } +/** + * Manages order and cart data conversion between PrestaShop entities and Comfino API structures. + * + * This class provides static methods for: + * - Converting PrestaShop carts and orders to Comfino cart objects. + * - Extracting customer information from PrestaShop entities. + * - Handling product categories, images, and tax calculations. + * - Currency validation for payment processing. + */ final class OrderManager { /** - * @param \Cart $cart PrestaShop cart entity. - * @param int $priceModifier - * @param bool $loadProductCategories Whether to load product category names into cart items. + * Converts a PrestaShop cart entity to Comfino cart structure. + * + * This method extracts all cart data including products, prices, taxes, and delivery costs, + * then transforms it into the format required by Comfino API. It handles: + * - Total value calculation with optional price modifier. + * - Product details including categories, images, and EAN codes. + * - Tax calculations for both products and delivery. + * - Net and gross price separation. * - * @return Cart Comfino cart structure. + * @param \Cart $cart PrestaShop cart entity to convert + * @param int $priceModifier Optional price modifier in cents (e.g., custom commission) + * @param bool $loadProductCategories Whether to load product category names into cart items * - * @throws \Exception|\InvalidArgumentException + * @return Cart Comfino cart structure with all product and delivery information + * + * @throws \InvalidArgumentException If total value is negative or exceeds PHP_INT_MAX + * @throws \Exception If product or carrier loading fails */ public static function getShopCart(\Cart $cart, int $priceModifier, bool $loadProductCategories = false): Cart { @@ -156,11 +175,17 @@ static function (array $product) use ($loadProductCategories): CartItemInterface } /** - * @param \Order $order PrestaShop order entity. - * @param int $priceModifier - * @param bool $loadProductCategories Whether to load product category names into cart items. + * Converts a PrestaShop order entity to Comfino cart structure. + * + * Similar to getShopCart() but operates on an already created order instead of a cart. + * This is useful for order status updates and processing existing orders. + * Uses order totals directly instead of recalculating from cart. * - * @return Cart Comfino cart structure. + * @param \Order $order PrestaShop order entity to convert + * @param int $priceModifier Optional price modifier in cents (e.g., custom commission) + * @param bool $loadProductCategories Whether to load product category names into cart items + * + * @return Cart Comfino cart structure with order products and delivery information */ public static function getShopCartFromOrder(\Order $order, int $priceModifier, bool $loadProductCategories = false): Cart { @@ -227,10 +252,16 @@ static function (array $product) use ($loadProductCategories): CartItemInterface } /** - * @param \Product $product PrestaShop product entity. - * @param bool $loadProductCategories Whether to load product category names into cart items. + * Converts a single PrestaShop product to Comfino cart structure. + * + * Creates a minimal cart containing only one product with quantity 1. + * This is primarily used for widget initialization on product pages + * where the cart context is not yet available. * - * @return Cart Comfino cart structure. + * @param \Product $product PrestaShop product entity to convert + * @param bool $loadProductCategories Whether to load product category names into cart items + * + * @return Cart Comfino cart structure with single product and no delivery costs */ public static function getShopCartFromProduct(\Product $product, bool $loadProductCategories = false): Cart { @@ -268,11 +299,21 @@ public static function getShopCartFromProduct(\Product $product, bool $loadProdu } /** - * @param \Cart $cart PrestaShop cart entity. - * @param \Customer $customer PrestaShop customer entity. - * @param \Context $context PrestaShop context. + * Extracts customer information from PrestaShop cart and customer entities. + * + * Collects customer data from multiple sources (billing address, delivery address, customer record) + * and converts it to Comfino Customer structure. Handles: + * - Customer name extraction (with fallback for missing last names). + * - Phone number prioritization (delivery address preferred). + * - Address parsing (street name and building number separation). + * - Tax ID validation for Polish NIP numbers. + * - Guest vs. registered customer detection. * - * @return Customer Comfino customer structure. + * @param \Cart $cart PrestaShop cart entity containing address information + * @param \Customer $customer PrestaShop customer entity + * @param \Context $context PrestaShop context for country code resolution + * + * @return Customer Comfino customer structure with complete customer and address data */ public static function getShopCustomerFromCart(\Cart $cart, \Customer $customer, \Context $context): Customer { @@ -357,6 +398,17 @@ public static function getShopCustomerFromCart(\Cart $cart, \Customer $customer, ); } + /** + * Validates that the cart currency is supported by the Comfino payment module. + * + * Checks if the cart's currency is enabled for the Comfino payment method + * by comparing the cart currency against the module's configured currencies. + * + * @param \Comfino $module Comfino payment module instance + * @param \Cart $cart PrestaShop cart entity to validate + * + * @return bool True if cart currency is supported, false otherwise + */ public static function checkCartCurrency(\Comfino $module, \Cart $cart): bool { $currencyOrder = new \Currency($cart->id_currency); @@ -373,6 +425,16 @@ public static function checkCartCurrency(\Comfino $module, \Cart $cart): bool return false; } + /** + * Retrieves the full URL of the product cover image. + * + * Extracts the cover image URL using PrestaShop's Link class. + * Ensures the URL uses HTTPS protocol. + * + * @param array $product Product data array containing 'id_product' and 'link_rewrite' + * + * @return string Full HTTPS URL to product cover image, or empty string if image not found + */ private static function getProductImageUrl(array $product): string { $linkRewrite = is_array($product['link_rewrite']) ? end($product['link_rewrite']) : $product['link_rewrite']; @@ -393,7 +455,14 @@ private static function getProductImageUrl(array $product): string } /** - * @return int[] + * Retrieves active category IDs for a product. + * + * Filters product categories to include only active ones. + * Used for category-based product filtering in Comfino API. + * + * @param \Product $product PrestaShop product entity + * + * @return int[] Array of active category IDs */ private static function getProductCategoryIds(\Product $product): array { @@ -411,7 +480,14 @@ private static function getProductCategoryIds(\Product $product): array } /** - * @return string[] + * Retrieves category names for a product. + * + * Returns human-readable category names filtered by product's active categories. + * Category names are retrieved from ConfigManager's cached category tree. + * + * @param \Product $product PrestaShop product entity + * + * @return string[] Array of category names indexed by category ID */ private static function getProductCategoryNames(\Product $product): array { @@ -422,6 +498,16 @@ private static function getProductCategoryNames(\Product $product): array return array_intersect_key($categories, array_flip(self::getProductCategoryIds($product))); } + /** + * Extracts and normalizes customer first and last names from address. + * + * Handles edge cases where last name is missing by splitting the first name. + * This ensures both first and last names are always available for API submission. + * + * @param \Address $address PrestaShop address entity + * + * @return array Array with two elements: [firstName, lastName] + */ private static function prepareCustomerNames(\Address $address): array { $firstName = trim($address->firstname ?? ''); diff --git a/src/Order/ShopStatusManager.php b/src/Order/ShopStatusManager.php index 846ea809..dda31d20 100644 --- a/src/Order/ShopStatusManager.php +++ b/src/Order/ShopStatusManager.php @@ -28,6 +28,8 @@ use Comfino\Api\ApiClient; use Comfino\Common\Shop\Order\StatusManager; +use Comfino\Configuration\ConfigManager; +use Comfino\DebugLogger; use Comfino\ErrorLogger; use Comfino\Main; use Comfino\View\FrontendManager; @@ -36,8 +38,28 @@ exit; } +/** + * Manages custom Comfino order statuses in PrestaShop. + * + * This class handles the complete lifecycle of custom order statuses used by the Comfino payment module: + * - Creating and registering custom order statuses during module installation. + * - Updating status definitions when upgrading the module. + * - Removing statuses during uninstallation (with soft delete for used statuses). + * - Synchronizing status changes between PrestaShop and Comfino API. + * - Handling order cancellation events via PrestaShop hooks. + * + * Custom statuses include: CREATED, ACCEPTED, REJECTED, CANCELLED, CANCELLED_BY_SHOP + */ final class ShopStatusManager { + /** + * Default mapping between Comfino statuses and PrestaShop native statuses. + * + * Maps Comfino status codes to PrestaShop configuration keys for standard order states. + * Used as fallback when custom statuses are not configured. + * + * @var array + */ public const DEFAULT_STATUS_MAP = [ StatusManager::STATUS_ACCEPTED => 'PS_OS_WS_PAYMENT', StatusManager::STATUS_CANCELLED => 'PS_OS_CANCELED', @@ -45,6 +67,18 @@ final class ShopStatusManager StatusManager::STATUS_CANCELLED_BY_SHOP => 'PS_OS_CANCELED', ]; + /** + * Definitions of custom Comfino order statuses. + * + * Each status includes: + * - name: English display name + * - name_pl: Polish display name + * - color: Hex color code for admin panel display + * - paid: Whether the order is considered paid in this status + * - deleted: Whether the status is marked as deleted (soft delete flag) + * + * @var array + */ private const CUSTOM_ORDER_STATUSES = [ 'COMFINO_' . StatusManager::STATUS_CREATED => [ 'name' => 'Order created - waiting for payment (Comfino)', @@ -68,22 +102,55 @@ final class ShopStatusManager 'deleted' => false, ], 'COMFINO_' . StatusManager::STATUS_CANCELLED => [ - 'name' => 'Cancelled (Comfino)', + 'name' => 'Canceled (Comfino)', 'name_pl' => 'Anulowano (Comfino)', 'color' => '#ba3f1d', 'paid' => false, 'deleted' => false, ], + 'COMFINO_' . StatusManager::STATUS_CANCELLED_BY_SHOP => [ + 'name' => 'Canceled by shop (Comfino)', + 'name_pl' => 'Anulowano przez sklep (Comfino)', + 'color' => '#ba3f1d', + 'paid' => false, + 'deleted' => false, + ], ]; - public static function addCustomOrderStatuses(): void + /** + * Creates custom Comfino order statuses in PrestaShop database. + * + * This method is called during module installation or reset. It: + * - Attempts to reuse existing status IDs from previous installations. + * - Creates new statuses if they don't exist. + * - Updates existing statuses if configuration keys are found. + * - Handles multi-language status names (English and Polish). + * - Stores status IDs in PrestaShop configuration. + * + * @param \Comfino $module Comfino module instance + * + * @return array Statistics about the operation containing: + * - statuses_created: Number of newly created statuses + * - statuses_updated: Number of updated statuses + * - statuses_create_failed: Number of failed creations + * - statuses_update_failed: Number of failed updates + * - operations: Detailed list of all operations performed + */ + public static function addCustomOrderStatuses(\Comfino $module): array { + $resultStats = [ + 'statuses_created' => 0, + 'statuses_updated' => 0, + 'statuses_create_failed' => 0, + 'statuses_update_failed' => 0, + 'operations' => [], + ]; + $languages = \Language::getLanguages(false); + $previousOrderStatuses = self::getPreviousOrderStatuses($module->name, $languages); foreach (self::CUSTOM_ORDER_STATUSES as $statusCode => $statusDetails) { - $comfinoStatusId = \Configuration::get($statusCode); - - if (!empty($comfinoStatusId) && \Validate::isInt($comfinoStatusId)) { + if ($comfinoStatusId = self::getStatusId($statusCode, $previousOrderStatuses, $statusDetails)) { try { $orderStatus = new \OrderState($comfinoStatusId); } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { @@ -102,19 +169,24 @@ public static function addCustomOrderStatuses(): void $orderStatus->deleted = $statusDetails['deleted']; try { - $orderStatus->update(); + $resultStats = self::updateOrderStatus($orderStatus, $resultStats, $statusCode); } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { FrontendManager::processError( sprintf('Order status update error: %d', (int) $orderStatus->id), $e, null, 'Order status update error.' ); + + $resultStats['statuses_update_failed']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_update', + 'success' => false, + 'status' => $statusCode, + 'error' => $e->getMessage(), + ]; } continue; } - } elseif ($statusDetails['deleted']) { - // Ignore deleted statuses in first time plugin installations. - continue; } // Add a new status definition. @@ -124,7 +196,7 @@ public static function addCustomOrderStatuses(): void $orderStatus->color = $statusDetails['color']; $orderStatus->unremovable = false; $orderStatus->logable = false; - $orderStatus->module_name = 'comfino'; + $orderStatus->module_name = COMFINO_MODULE_NAME; $orderStatus->paid = $statusDetails['paid']; foreach ($languages as $language) { @@ -135,30 +207,72 @@ public static function addCustomOrderStatuses(): void try { if ($orderStatus->add()) { \Configuration::updateValue($statusCode, $orderStatus->id); + + $resultStats['statuses_created']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_create', + 'success' => true, + 'status' => $statusCode, + ]; + } else { + $resultStats['statuses_create_failed']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_create', + 'success' => false, + 'status' => $statusCode, + 'error' => 'Order status create error.', + ]; } } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { FrontendManager::processError( sprintf('Order status adding error: %s, %d.', $statusCode, (int) $orderStatus->id), $e, null, 'Order status adding error.' ); + + $resultStats['statuses_create_failed']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_create', + 'success' => false, + 'status' => $statusCode, + 'error' => $e->getMessage(), + ]; } } + + return $resultStats; } - public static function updateOrderStatuses(): void + /** + * Updates all existing custom Comfino order statuses. + * + * Refreshes status definitions to match current CUSTOM_ORDER_STATUSES configuration. + * This is useful after module upgrades to ensure status names, colors, and flags are current. + * Only updates statuses that are already registered in PrestaShop configuration. + * + * @return array Statistics about the operation containing: + * - statuses_updated: Number of successfully updated statuses + * - statuses_update_failed: Number of failed updates + * - operations: Detailed list of all operations performed + */ + public static function updateCustomOrderStatuses(\Comfino $module): array { + $resultStats = [ + 'statuses_updated' => 0, + 'statuses_update_failed' => 0, + 'operations' => [], + ]; + $languages = \Language::getLanguages(false); + $previousOrderStatuses = self::getPreviousOrderStatuses($module->name, $languages); foreach (self::CUSTOM_ORDER_STATUSES as $statusCode => $statusDetails) { - $comfinoStatusId = \Configuration::get($statusCode); - - if (!empty($comfinoStatusId) && \Validate::isInt($comfinoStatusId)) { + if ($comfinoStatusId = self::getStatusId($statusCode, $previousOrderStatuses, $statusDetails)) { try { $orderStatus = new \OrderState($comfinoStatusId); } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { FrontendManager::processError( - sprintf('Order status creation error: %s, %d', $statusCode, (int) $comfinoStatusId), - $e, null, 'Order status creation error.' + sprintf('Order status loading error: %s, %d', $statusCode, (int) $comfinoStatusId), + $e, null, 'Order status loading error.' ); continue; @@ -179,20 +293,194 @@ public static function updateOrderStatuses(): void $orderStatus->deleted = $statusDetails['deleted']; try { - $orderStatus->save(); + $resultStats = self::updateOrderStatus($orderStatus, $resultStats, $statusCode); } catch (\PrestaShopException $e) { FrontendManager::processError( sprintf('Order status saving error: %s', $statusDetails['name']), $e, null, 'Order status saving error.' ); + + $resultStats['statuses_update_failed']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_update', + 'success' => false, + 'status' => $statusCode, + 'error' => $e->getMessage(), + ]; } } } } + + return $resultStats; + } + + /** + * Removes custom Comfino order statuses from PrestaShop. + * + * Called during module uninstallation. Uses a transactional approach to ensure data consistency: + * - Hard delete: Completely removes statuses if never used in order history. + * - Soft delete: Marks statuses as deleted if used to preserve database integrity. + * - Removes configuration keys for all custom statuses. + * + * The operation is atomic - either all statuses are removed/updated or none (rollback on error). + * + * @return array Statistics about the operation containing: + * - statuses_removed: Number of hard-deleted statuses + * - statuses_updated: Number of soft-deleted statuses + * - statuses_remove_failed: Number of failed removals + * - statuses_update_failed: Number of failed soft deletes + * - status_config_options_removed: Number of configuration keys removed + * - operations: Detailed list of all operations performed + */ + public static function removeCustomOrderStatuses(): array + { + $statusesUsed = false; + $resultStats = [ + 'statuses_removed' => 0, + 'statuses_updated' => 0, + 'statuses_remove_failed' => 0, + 'statuses_update_failed' => 0, + 'status_config_options_removed' => 0, + 'operations' => [], + ]; + + $db = \Db::getInstance(); + + try { + // Start transaction for atomic operations. + $db->execute('START TRANSACTION', false); + + $orderStatusIds = self::getCustomOrderStatusIds(); + + if (!($statusesUsed = self::comfinoOrderStatusesUsed($orderStatusIds))) { + // Completely remove statuses only when never used to avoid database inconsistency and corruption. + if (self::deleteComfinoOrderStatuses($orderStatusIds)) { + $resultStats['statuses_removed'] = $db->Affected_Rows(); + } else { + $resultStats['statuses_remove_failed'] = count(self::CUSTOM_ORDER_STATUSES); + + throw new \RuntimeException('Failed to delete Comfino order statuses'); + } + } else { + // Mark statuses as deleted only (soft delete). + if (self::softDeleteComfinoOrderStatuses($orderStatusIds)) { + $resultStats['statuses_updated'] = $db->Affected_Rows(); + } else { + $resultStats['statuses_update_failed'] = count(self::CUSTOM_ORDER_STATUSES); + + throw new \RuntimeException('Failed to soft delete Comfino order statuses'); + } + } + + if (ConfigManager::deleteConfigurationValues(array_keys(self::CUSTOM_ORDER_STATUSES))) { + $resultStats['status_config_options_removed'] = count(self::CUSTOM_ORDER_STATUSES); + } + + $resultStats['operations'][] = ['name' => 'custom_order_statuses_remove', 'success' => true]; + + // Commit transaction. + $db->execute('COMMIT', false); + } catch (\Throwable $e) { + // Rollback on any error. + $db->execute('ROLLBACK', false); + + FrontendManager::processError( + 'Order status removal transaction failed', + $e, + null, + 'Order status removal error.' + ); + + if ($statusesUsed) { + if ($resultStats['statuses_updated'] === 0) { + $resultStats['statuses_update_failed'] = count(self::CUSTOM_ORDER_STATUSES); + } + } else { + if ($resultStats['statuses_removed'] === 0) { + $resultStats['statuses_remove_failed'] = count(self::CUSTOM_ORDER_STATUSES); + } + } + + $resultStats['operations'][] = ['name' => 'custom_order_statuses_remove', 'success' => false]; + } + + return $resultStats; + } + + /** + * Reinitializes custom order statuses during module reset or upgrade. + * + * This method performs a comprehensive status management operation: + * 1. Attempts to load existing Comfino statuses from database. + * 2. Repairs module_name if statuses exist but aren't properly linked. + * 3. Creates missing statuses if needed. + * 4. Updates all existing statuses to current definitions. + * + * Used during module reset and upgrade processes to ensure status consistency. + * + * @param \Comfino $module Comfino module instance + * + * @return array Combined statistics from add and update operations containing: + * - statuses_created: Number of newly created statuses + * - statuses_updated: Number of updated statuses + * - statuses_create_failed: Number of failed creations + * - statuses_update_failed: Number of failed updates + * - operations: Detailed list of all operations performed + */ + public static function reinitializeCustomOrderStatuses(\Comfino $module): array + { + ErrorLogger::init(); + + if (empty($rows = self::loadComfinoOrderStatuses($module->name))) { + self::repairComfinoOrderStatuses($module->name); + + if (empty($rows = self::loadComfinoOrderStatuses($module->name))) { + return self::addCustomOrderStatuses($module); + } + } + + $resultStats = [ + 'statuses_created' => 0, + 'statuses_updated' => 0, + 'statuses_create_failed' => 0, + 'statuses_update_failed' => 0, + 'operations' => [], + ]; + + if (count(array_unique(array_column($rows, 'id_order_state'), SORT_NUMERIC)) !== count(self::CUSTOM_ORDER_STATUSES)) { + $resultStats = self::addCustomOrderStatuses($module); + } + + $updateStats = self::updateCustomOrderStatuses($module); + + $resultStats['statuses_updated'] += $updateStats['statuses_updated']; + $resultStats['statuses_update_failed'] += $updateStats['statuses_update_failed']; + $resultStats['operations'] = array_merge($resultStats['operations'], $updateStats['operations']); + + return $resultStats; } + /** + * Handles PrestaShop order status update events for Comfino orders. + * + * This method is called by PrestaShop's actionOrderStatusUpdate hook. + * When a Comfino order is manually changed to "Canceled" status in admin panel, + * this handler sends a cancellation notification to the Comfino API. + * + * Only processes orders paid via Comfino (checked via order->module). + * Supports both numeric order IDs and order references based on configuration. + * + * @param array $params Hook parameters containing: + * - id_order: Order ID + * - newOrderStatus: New OrderState object + * + * @return void + */ public static function orderStatusUpdateEventHandler(array $params): void { + DebugLogger::logEvent('[ORDER_STATUS_UPDATE]', "orderStatusUpdateEventHandler (order ID: $params[id_order])"); + try { $order = new \Order($params['id_order']); } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { @@ -204,7 +492,9 @@ public static function orderStatusUpdateEventHandler(array $params): void return; } - if (stripos($order->payment, 'comfino') !== false) { + DebugLogger::logEvent('[ORDER_STATUS_UPDATE]', "Order $order->id loaded. (payment method: \"$order->payment\")"); + + if ($order->module === COMFINO_MODULE_NAME) { // Process orders paid by Comfino only. /** @var \OrderState $newOrderState */ @@ -216,9 +506,16 @@ public static function orderStatusUpdateEventHandler(array $params): void if ($newOrderStateId === $canceledOrderStateId) { ErrorLogger::init(); + // Get order ID or reference based on configuration. + if (ConfigManager::getConfigurationValue('COMFINO_USE_ORDER_REFERENCE', false)) { + $orderId = !empty($order->reference) ? $order->reference : (string) $order->id; + } else { + $orderId = (string) $order->id; + } + try { // Send notification about canceled order paid by Comfino. - ApiClient::getInstance()->cancelOrder((string) $params['id_order']); + ApiClient::getInstance()->cancelOrder($orderId); } catch (\Throwable $e) { ApiClient::processApiError( 'Order cancellation error on page "' . Main::getRequestUri() . '" (Comfino API)', $e @@ -227,4 +524,264 @@ public static function orderStatusUpdateEventHandler(array $params): void } } } + + /** + * Retrieves all custom Comfino order status IDs from configuration. + * + * @return int[] Array of order state IDs + */ + private static function getCustomOrderStatusIds(): array + { + $orderStateIds = []; + + foreach (array_keys(self::CUSTOM_ORDER_STATUSES) as $orderStateCode) { + if (!empty($orderStateId = (int) \Configuration::get($orderStateCode))) { + $orderStateIds[] = $orderStateId; + } + } + + return $orderStateIds; + } + + /** + * Retrieves previously installed Comfino order statuses grouped by language. + * + * Used during status creation to detect and reuse existing status IDs from previous installations. + * Prevents duplicate status creation and configuration key corruption. + * + * @param string $moduleName Module name to search for (usually "comfino") + * @param array $languages Array of language definitions from PrestaShop + * + * @return array> + * Nested array structure: [language_code][status_name] => status_data + * Example: ['en']['Order created - waiting for payment (Comfino)'] => ['id_order_state' => 123, ...] + */ + private static function getPreviousOrderStatuses(string $moduleName, array $languages): array + { + $previousOrderStatuses = []; + + if (($existingStatuses = self::loadComfinoOrderStatuses($moduleName)) !== false) { + $langCodeById = array_combine(array_column($languages, 'id_lang'), array_column($languages, 'iso_code')); + + foreach ($existingStatuses as $status) { + if (isset($langCodeById[$status['id_lang']])) { + $previousOrderStatuses[$langCodeById[$status['id_lang']]][$status['name']] = $status; + } + } + } + + return $previousOrderStatuses; + } + + /** + * Retrieves or reclaims the order status ID for a given status code. + * + * Implements intelligent status ID resolution: + * 1. First checks PrestaShop configuration for existing status ID. + * 2. If not found, attempts to reclaim ID from previous installations by matching status names. + * 3. Restores configuration option if ID is successfully reclaimed. + * + * This prevents duplicate status creation when configuration keys are lost but statuses still exist. + * + * @param string $statusCode Configuration key for the status (e.g., "COMFINO_ACCEPTED") + * @param array> $previousOrderStatuses + * Previously installed statuses grouped by language and name + * @param array{name: string, name_pl: string, color: string, paid: bool, deleted: bool} $statusDetails + * Status definition containing English and Polish names + * + * @return int Order status ID if found or reclaimed, 0 if not found + */ + private static function getStatusId(string $statusCode, array $previousOrderStatuses, array $statusDetails): int + { + $comfinoStatusId = \Configuration::get($statusCode); + + if (empty($comfinoStatusId) || !\Validate::isInt($comfinoStatusId)) { + // Status ID not found in configuration or is corrupted - try to use existing ID from previous installations. + if (isset( + $previousOrderStatuses['en'][$statusDetails['name']], + $previousOrderStatuses['pl'][$statusDetails['name_pl']] + )) { + $idOrderStateEN = $previousOrderStatuses['en'][$statusDetails['name']]['id_order_state']; + $idOrderStatePL = $previousOrderStatuses['pl'][$statusDetails['name_pl']]['id_order_state']; + + if ($idOrderStateEN === $idOrderStatePL) { + // Reuse existing status definition and load it in the next step with reclaimed ID. + $comfinoStatusId = $idOrderStateEN; + + // Restore configuration option with proper status ID. + \Configuration::updateValue($statusCode, $comfinoStatusId); + } + } + } + + return (int) $comfinoStatusId; + } + + /** + * Updates an existing order status and records the operation result. + * + * Attempts to save the updated OrderState object to the database and updates + * the result statistics array with success or failure information. + * + * @param \OrderState $orderStatus PrestaShop OrderState object to update + * @param array $resultStats Current statistics array containing operation counters and details + * @param string $statusCode Configuration key for the status (e.g., "COMFINO_ACCEPTED") + * + * @return array Updated statistics array with: + * - statuses_updated: Incremented on success + * - statuses_update_failed: Incremented on failure + * - operations: Array with operation result entry added + * + * @throws \PrestaShopDatabaseException If database operation fails + * @throws \PrestaShopException If PrestaShop validation fails + */ + private static function updateOrderStatus(\OrderState $orderStatus, array $resultStats, string $statusCode): array + { + if ($orderStatus->update()) { + $resultStats['statuses_updated']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_update', + 'success' => true, + 'status' => $statusCode, + ]; + } else { + $resultStats['statuses_update_failed']++; + $resultStats['operations'][] = [ + 'name' => 'custom_status_update', + 'success' => false, + 'status' => $statusCode, + 'error' => 'Order status update error.', + ]; + } + + return $resultStats; + } + + /** + * Repairs orphaned Comfino order statuses by setting their module_name. + * + * Fixes statuses that exist in database but have incorrect or missing module_name. + * This can happen after manual database modifications or PrestaShop upgrades. + * Identifies Comfino statuses by searching for "comfino" in status names. + * + * @param string $moduleName Module name to set (usually "comfino") + * + * @return void + */ + private static function repairComfinoOrderStatuses(string $moduleName): void + { + $dbPrefix = _DB_PREFIX_; + $moduleName = pSQL($moduleName); + + \Db::getInstance()->execute(<<executeS(<<getValue(<<delete('order_state_lang', "id_order_state IN ($orderStateIdsSQL)") + && \Db::getInstance()->delete('order_state', "id_order_state IN ($orderStateIdsSQL)"); + } + + /** + * Marks Comfino order statuses as deleted without removing them (soft delete). + * + * Sets the 'deleted' flag to 1 in order_state table. + * Used when statuses have been used in order history to preserve referential integrity. + * Soft-deleted statuses remain in database but are hidden from admin interface. + * + * @param int[] $orderStatusIds Array of order state IDs to soft delete + * + * @return bool True if update succeeded, false otherwise + */ + private static function softDeleteComfinoOrderStatuses(array $orderStatusIds): bool + { + $orderStateIdsSQL = implode(',', $orderStatusIds); + + return \Db::getInstance()->update('order_state', ['deleted' => 1], "id_order_state IN ($orderStateIdsSQL)"); + } } diff --git a/src/Order/StatusAdapter.php b/src/Order/StatusAdapter.php index 95ad5ed4..007759ab 100644 --- a/src/Order/StatusAdapter.php +++ b/src/Order/StatusAdapter.php @@ -26,26 +26,112 @@ namespace Comfino\Order; +use Comfino\Api\Exception\NotFound; +use Comfino\Api\Exception\RequestValidationError; +use Comfino\Api\Exception\ServiceUnavailable; use Comfino\Common\Shop\Order\StatusManager; use Comfino\Common\Shop\OrderStatusAdapterInterface; use Comfino\Configuration\ConfigManager; +use Comfino\DebugLogger; if (!defined('_PS_VERSION_')) { exit; } +/** + * Adapter for handling order status updates from Comfino API webhooks. + * + * This class implements the OrderStatusAdapterInterface from the shared library + * and provides PrestaShop-specific logic for updating order statuses based on + * payment status changes received from Comfino API. + * + * Supports two order identification modes: + * - Legacy: Numeric order ID (e.g., "12345") + * - Modern: Order reference string (e.g., "XKTARQXWV") + * + * Status update flow: + * 1. Loads order by ID or reference. + * 2. Validates order is paid via Comfino. + * 3. Maps Comfino status to custom PrestaShop status. + * 4. Applies custom status if not already in history. + * 5. Maps to standard PrestaShop status and applies if configured. + */ class StatusAdapter implements OrderStatusAdapterInterface { + /** + * Updates PrestaShop order status based on Comfino payment status. + * + * This method is called by StatusNotification REST endpoint when receiving + * status update webhooks from Comfino API. It: + * - Determines order lookup method (by ID or reference). + * - Validates order belongs to Comfino payment module. + * - Converts Comfino status to PrestaShop custom status. + * - Applies both custom and mapped standard statuses if not already in history. + * + * The method avoids creating duplicate status history entries by checking + * if the status was already applied to the order. + * + * @param string|int $orderId Order identifier - either numeric ID or reference string + * @param string $status Comfino payment status (e.g., "ACCEPTED", "REJECTED", "CANCELLED") + * + * @return void + * + * @throws NotFound If order not found by provided ID or reference + * @throws RequestValidationError If order exists but is not a Comfino order + * @throws ServiceUnavailable If database error occurs during order loading + */ public function setStatus($orderId, $status): void { - $order = new \Order($orderId); + DebugLogger::logEvent( + '[ORDER_STATUS_UPDATE]', + 'StatusAdapter::setStatus: Order status update from Comfino API.', + ['orderId' => $orderId, 'status' => $status] + ); + + // Determine if orderId is numeric (legacy) or reference (new). + $isNumericId = is_numeric($orderId) && ctype_digit((string) $orderId); + + if ($isNumericId) { + // Legacy path: Load by numeric ID. + try { + $order = new \Order((int) $orderId); + } catch (\PrestaShopDatabaseException|\PrestaShopException $e) { + throw new ServiceUnavailable("Order $orderId loading error.", $e->getCode(), $e); + } + } else { + // New path: Load by reference using PrestaShopCollection. + try { + $orderCollection = new \PrestaShopCollection('Order'); + $orderCollection->where('reference', '=', $orderId)->where('module', '=', COMFINO_MODULE_NAME); + } catch (\PrestaShopException $e) { + throw new ServiceUnavailable("Order \"$orderId\" loading error.", $e->getCode(), $e); + } - if (!\ValidateCore::isLoadedObject($order)) { - throw new \RuntimeException(sprintf('Order not found by id: %s', $orderId)); + $order = $orderCollection->getFirst(); + } + + if (!\Validate::isLoadedObject($order)) { + throw new NotFound(sprintf('Order not found by %s: %s', $isNumericId ? 'id' : 'reference', $orderId)); + } + + if ($order->module !== COMFINO_MODULE_NAME) { + // Process orders paid by Comfino only. + throw new RequestValidationError("Order $orderId is not a valid Comfino order."); } $inputStatus = \Tools::strtoupper($status); + DebugLogger::logEvent( + '[ORDER_STATUS_UPDATE]', + sprintf( + "StatusAdapter::setStatus (order %s: %s, status: \"%s\", internal ID: %d)", + $isNumericId ? 'ID' : 'reference', + $orderId, + $inputStatus, + $order->id + ) + ); + if (in_array($inputStatus, StatusManager::STATUSES, true)) { $customStatusNew = "COMFINO_$inputStatus"; } else { @@ -55,8 +141,17 @@ public function setStatus($orderId, $status): void $currentInternalStatusId = (int) $order->getCurrentState(); $newCustomStatusId = (int) \Configuration::get($customStatusNew); + DebugLogger::logEvent( + '[ORDER_STATUS_UPDATE]', + "current internal status ID: $currentInternalStatusId, new custom status ID: $newCustomStatusId" + ); + if ($newCustomStatusId !== $currentInternalStatusId) { - $order->setCurrentState($newCustomStatusId); + $statusIdsHistory = array_unique(array_column($order->getHistory(0), 'id_order_state'), SORT_NUMERIC); + + if (!in_array($newCustomStatusId, $statusIdsHistory, true)) { + $order->setCurrentState($newCustomStatusId); + } $statusMap = ConfigManager::getStatusMap(); @@ -66,13 +161,11 @@ public function setStatus($orderId, $status): void $newInternalStatusId = (int) \Configuration::get($statusMap[$inputStatus]); - foreach ($order->getHistory(0) as $historyEntry) { - if ($historyEntry['id_order_state'] === $newInternalStatusId) { - return; - } - } + DebugLogger::logEvent('[ORDER_STATUS_UPDATE]', "new internal status ID: $newInternalStatusId"); - $order->setCurrentState($newInternalStatusId); + if (!in_array($newInternalStatusId, $statusIdsHistory, true)) { + $order->setCurrentState($newInternalStatusId); + } } } } diff --git a/src/View/FrontendManager.php b/src/View/FrontendManager.php index 98a3150e..4ed5a7c0 100644 --- a/src/View/FrontendManager.php +++ b/src/View/FrontendManager.php @@ -224,16 +224,7 @@ static function ($optionValue) use ($serializer) { ConfigManager::getWidgetVariables($productId) ); } catch (\Throwable $e) { - ErrorLogger::sendError( - $e, - 'Widget script endpoint', - $e->getCode(), - $e->getMessage(), - $e instanceof HttpErrorExceptionInterface ? $e->getUrl() : null, - $e instanceof HttpErrorExceptionInterface ? $e->getRequestBody() : null, - $e instanceof HttpErrorExceptionInterface ? $e->getResponseBody() : null, - $e->getTraceAsString() - ); + self::processError('Widget script endpoint', $e); } return ''; @@ -294,19 +285,24 @@ public static function processError( string $errorPrefix, \Throwable $exception, ?int $httpStatus = null, - ?string $userErrorMessage = null + ?string $userErrorMessage = null, + ?array $parameters = null, + string $eventPrefix = '[ERROR]' ): array { DebugLogger::logEvent( - '[ERROR]', + $eventPrefix, $errorPrefix, - [ - 'exception' => get_class($exception), - 'error_message' => $exception->getMessage(), - 'error_code' => $exception->getCode(), - 'error_file' => $exception->getFile(), - 'error_line' => $exception->getLine(), - 'error_trace' => $exception->getTraceAsString(), - ] + array_merge( + [ + 'exception' => get_class($exception), + 'error_message' => $exception->getMessage(), + 'error_code' => $exception->getCode(), + 'error_file' => $exception->getFile(), + 'error_line' => $exception->getLine(), + 'error_trace' => $exception->getTraceAsString(), + ], + $parameters ?? [] + ) ); ErrorLogger::sendError( diff --git a/src/View/SettingsForm.php b/src/View/SettingsForm.php index 20bc57d2..d55ee9bb 100644 --- a/src/View/SettingsForm.php +++ b/src/View/SettingsForm.php @@ -37,7 +37,6 @@ use Comfino\FinancialProduct\ProductTypesListTypeEnum; use Comfino\Main; use Comfino\PluginShared\CacheManager; -use Comfino\Update\UpdateManager; if (!defined('_PS_VERSION_')) { exit; @@ -50,6 +49,17 @@ final class SettingsForm public const COMFINO_SUPPORT_EMAIL = 'pomoc@comfino.pl'; public const COMFINO_SUPPORT_PHONE = '887-106-027'; + /** + * Processes form submission from module configuration page. + * + * Handles various form submissions including: + * - Module reset. + * - Error log clearing. + * - Debug log clearing. + * - Configuration updates for all settings tabs. + * + * @return array Configuration and output data for template rendering + */ public static function processForm(): array { ErrorLogger::init(); @@ -65,7 +75,67 @@ public static function processForm(): array $configurationOptions = []; - if (\Tools::isSubmit('submit_configuration')) { + // Handle module reset submission. + if (\Tools::isSubmit('submit_module_reset')) { + $activeTab = 'plugin_diagnostics'; + + try { + $resetStats = Main::reset(Main::getModule()); + $hasErrors = $resetStats['config_failed'] > 0 + || $resetStats['hooks_failed'] > 0 + || $resetStats['statuses_create_failed'] > 0 + || $resetStats['statuses_update_failed'] > 0; + + if ($hasErrors) { + $outputType = 'warning'; + $output[] = Main::translate('Module reset completed with some errors.'); + } else { + $output[] = Main::translate('Module reset completed successfully.'); + } + + $output[] = sprintf( + Main::translate('Configuration: %d repaired, %d failed'), + $resetStats['config_repaired'], + $resetStats['config_failed'] + ); + $output[] = sprintf( + Main::translate('Hooks: %d registered, %d failed'), + $resetStats['hooks_registered'], + $resetStats['hooks_failed'] + ); + $output[] = sprintf( + Main::translate('Order statuses: %d created, %d updated, %d failed'), + $resetStats['statuses_created'], + $resetStats['statuses_updated'], + $resetStats['statuses_create_failed'] + $resetStats['statuses_update_failed'] + ); + } catch (\Exception $e) { + $outputType = 'error'; + $output[] = Main::translate('Module reset failed') . ': ' . $e->getMessage(); + } + } elseif (\Tools::isSubmit('submit_clear_error_log')) { + $activeTab = 'plugin_diagnostics'; + + try { + ErrorLogger::clearLogs(); + + $output[] = Main::translate('Error log cleared successfully.'); + } catch (\Exception $e) { + $outputType = 'error'; + $output[] = Main::translate('Error log clearing failed') . ': ' . $e->getMessage(); + } + } elseif (\Tools::isSubmit('submit_clear_debug_log')) { + $activeTab = 'plugin_diagnostics'; + + try { + DebugLogger::clearLogs(); + + $output[] = Main::translate('Debug log cleared successfully.'); + } catch (\Exception $e) { + $outputType = 'error'; + $output[] = Main::translate('Debug log clearing failed') . ': ' . $e->getMessage(); + } + } elseif (\Tools::isSubmit('submit_configuration')) { $activeTab = \Tools::getValue('active_tab'); foreach (ConfigManager::CONFIG_OPTIONS[$activeTab] as $optionName => $optionType) { @@ -320,6 +390,20 @@ public static function processForm(): array ]; } + /** + * Generates form fields configuration for module settings. + * + * Builds form field definitions for different configuration tabs: + * - payment_settings: API key, payment text, minimal cart amount + * - sale_settings: Product category filters + * - widget_settings: Widget configuration and appearance + * - developer_settings: Sandbox mode, debug mode, service mode + * - plugin_diagnostics: Module reset, logs, diagnostics + * + * @param array $params Form parameters including config_tab and form_name + * + * @return array Form fields configuration for PrestaShop form rendering + */ public static function getFormFields(array $params): array { $fields = []; @@ -366,6 +450,25 @@ public static function getFormFields(array $params): array 'name' => 'COMFINO_MINIMAL_CART_AMOUNT', 'required' => true, ], + [ + 'type' => 'switch', + 'label' => Main::translate('Use order reference as external ID'), + 'name' => 'COMFINO_USE_ORDER_REFERENCE', + 'desc' => Main::translate('Use customer-visible order reference instead of numeric order ID for Comfino API integration. New orders only.'), + 'is_bool' => true, + 'values' => [ + [ + 'id' => 'active_on', + 'value' => true, + 'label' => Main::translate('Yes'), + ], + [ + 'id' => 'active_off', + 'value' => false, + 'label' => Main::translate('No'), + ], + ], + ], ], 'submit' => [ 'title' => Main::translate('Save'), @@ -452,6 +555,7 @@ public static function getFormFields(array $params): array 'type' => 'switch', 'label' => Main::translate('Widget is active?'), 'name' => 'COMFINO_WIDGET_ENABLED', + 'is_bool' => true, 'values' => [ [ 'id' => 'widget_enabled', @@ -507,6 +611,7 @@ static function ($option) { 'type' => 'switch', 'label' => Main::translate('Show logos of financial services providers'), 'name' => 'COMFINO_WIDGET_SHOW_PROVIDER_LOGOS', + 'is_bool' => true, 'values' => [ [ 'id' => 'provider_logos_enabled', @@ -637,6 +742,7 @@ static function ($option) { 'type' => 'switch', 'label' => Main::translate('Use test environment'), 'name' => 'COMFINO_IS_SANDBOX', + 'is_bool' => true, 'values' => [ [ 'id' => 'sandbox_enabled', @@ -670,6 +776,7 @@ static function ($option) { 'type' => 'switch', 'label' => Main::translate('Debug mode'), 'name' => 'COMFINO_DEBUG', + 'is_bool' => true, 'values' => [ [ 'id' => 'debug_enabled', @@ -692,6 +799,7 @@ static function ($option) { 'type' => 'switch', 'label' => Main::translate('Service mode'), 'name' => 'COMFINO_SERVICE_MODE', + 'is_bool' => true, 'values' => [ [ 'id' => 'service_mode_enabled', @@ -724,6 +832,7 @@ static function ($option) { 'type' => 'switch', 'label' => Main::translate('Use development environment variables'), 'name' => 'COMFINO_DEV_ENV_VARS', + 'is_bool' => true, 'values' => [ [ 'id' => 'dev_env_vars_enabled', @@ -762,18 +871,15 @@ static function ($option) { 'input' => [ [ 'type' => 'html', - 'label' => Main::translate('Configuration repair'), - 'name' => 'COMFINO_CONFIG_REPAIR', - 'html_content' => self::renderConfigurationRepairSection(), + 'label' => Main::translate('Module reset'), + 'name' => 'COMFINO_MODULE_RESET', + 'html_content' => self::renderModuleResetSection(), ], [ 'type' => 'html', 'label' => Main::translate('Errors log'), 'name' => 'COMFINO_WIDGET_ERRORS_LOG', - 'html_content' => - '', + 'html_content' => self::renderErrorLogSection(), ], [ 'type' => 'html', @@ -781,10 +887,15 @@ static function ($option) { 'name' => 'COMFINO_DEBUG_LOG', 'required' => false, 'readonly' => true, - 'html_content' => - '', + 'html_content' => self::renderDebugLogSection(), + ], + [ + 'type' => 'html', + 'label' => Main::translate('Installation logs'), + 'name' => 'COMFINO_INSTALLATION_LOGS', + 'required' => false, + 'readonly' => true, + 'html_content' => self::renderInstallationLogsSection(), ], ], ] @@ -882,21 +993,55 @@ static function ($val1, $val2) { return $val1['position'] - $val2['position']; } } /** - * Renders the configuration repair section with validation status and repair button. + * Renders the module reset section with reset button. + */ + private static function renderModuleResetSection(): string + { + return TemplateManager::renderModuleView( + 'module-reset', + 'admin/_configure', + [] + ); + } + + /** + * Renders the error log section with textarea and clear button. */ - private static function renderConfigurationRepairSection(): string + private static function renderErrorLogSection(): string { - $validationKey = ApiService::getValidationKey(); - $crSignature = ApiService::getCrSignature($validationKey); + return TemplateManager::renderModuleView( + 'error-log', + 'admin/_configure', + ['error_log_content' => ErrorLogger::getLoggerInstance()->getErrorLog(self::ERROR_LOG_NUM_LINES)] + ); + } + /** + * Renders the debug log section with textarea and clear button. + */ + private static function renderDebugLogSection(): string + { + return TemplateManager::renderModuleView( + 'debug-log', + 'admin/_configure', + ['debug_log_content' => DebugLogger::getLoggerInstance()->getDebugLog(self::DEBUG_LOG_NUM_LINES)] + ); + } + + /** + * Renders the installation logs section (collapsed by default). + * + * @return string Rendered HTML content + */ + private static function renderInstallationLogsSection(): string + { return TemplateManager::renderModuleView( - 'configuration-repair', + 'installation-logs', 'admin/_configure', [ - 'validation' => ConfigManager::validateConfigurationIntegrity(), - 'repair_url' => ApiService::getControllerUrl('configurationrepair', ['vkey' => $validationKey]), - 'vkey' => $validationKey, - 'cr_signature' => $crSignature, + 'install_log_content' => Main::readInstallLog(), + 'upgrade_log_content' => Main::readUpgradeLog(), + 'uninstall_log_content' => Main::readUninstallLog(), ] ); } diff --git a/translations/pl.php b/translations/pl.php index 1442b8ba..4252ea45 100644 --- a/translations/pl.php +++ b/translations/pl.php @@ -42,6 +42,8 @@ $_MODULE['<{comfino}prestashop>comfino_b9f5c797ebbf55adccdd8539a65a0241'] = 'Wyłączony'; $_MODULE['<{comfino}prestashop>comfino_e228daa9c5b7bec78933f46885101f50'] = 'Tekst płatności'; $_MODULE['<{comfino}prestashop>comfino_a0cc6d4139acea469ac9f00b8fb54cf9'] = 'Minimalna kwota w koszyku'; +$_MODULE['<{comfino}prestashop>comfino_fc06d645aa8782435bb92f3de1aac55e'] = 'Użyj numeru zamówienia jako zewnętrznego ID'; +$_MODULE['<{comfino}prestashop>comfino_58e8e35a64dcc03b5cb70353e0d658be'] = 'Używaj numeru zamówienia widocznego dla klienta zamiast numerycznego ID zamówienia w komunikacji z API Comfino. Dotyczy tylko nowych zamówień.'; $_MODULE['<{comfino}prestashop>comfino_90e7796dead41b55c2063846c52a0826'] = 'Comfino to nowatorska metoda płatności dla klientów sklepów e-commerce! To płatności ratalne, odroczone (kup teraz, zapłać później) oraz płatności dla firm dostępne na jednej platformie za pomocą szybkiej integracji. Rozwijaj swój biznes z Comfino!'; $_MODULE['<{comfino}prestashop>comfino_3e1759db51250b5b214727fbdf60f739'] = 'Płatności Comfino'; $_MODULE['<{comfino}prestashop>comfino_c58c4371152e7a7d857ca26127c91169'] = 'Czy na pewno chcesz odinstalować płatności Comfino?'; @@ -111,6 +113,12 @@ $_MODULE['<{comfino}prestashop>comfino_dc2d9bc154bafaa5710857ddf96698ee'] = 'Łączna kwota koszyka musi być większa niż zero.'; $_MODULE['<{comfino}prestashop>comfino_591bb5cc3346609be5ab321d790f06da'] = 'Płatność Comfino nie jest dostępna dla tego koszyka. Sprawdź kwotę koszyka i typy produktów.'; +$_MODULE['<{comfino}prestashop>comfino_92553589e0b4f210e91e856583052d29'] = 'Log błędów został pomyślnie wyczyszczony.'; +$_MODULE['<{comfino}prestashop>comfino_18ec1638ce465aa002dc8290d22c1ee0'] = 'Czyszczenie logu błędów nie powiodło się'; +$_MODULE['<{comfino}prestashop>comfino_0d89706534c2e473ae966343d86b9a2c'] = 'Log debugowania został pomyślnie wyczyszczony.'; +$_MODULE['<{comfino}prestashop>comfino_a6167daf1db77c6f535415c0042f2e83'] = 'Czyszczenie logu debugowania nie powiodło się'; +$_MODULE['<{comfino}prestashop>comfino_3469efb95a288b25dfbae8aff6e7c49f'] = 'Logi instalacyjne'; + $_MODULE['<{comfino}prestashop>configuration_f1a9672d88ab0cae23402867e2def274'] = 'Ustawienia płatności'; $_MODULE['<{comfino}prestashop>configuration_ad5d1a1c9746f7d0f75ae8727ee1ec12'] = 'Ustawienia sprzedaży'; $_MODULE['<{comfino}prestashop>configuration_dbdcee2a8f33547e13199af86a38688e'] = 'Ustawienia widgetu'; @@ -123,24 +131,37 @@ $_MODULE['<{comfino}prestashop>update-notice_275e590cc57b619e7c4d9a82d3278394'] = 'Nowa wersja'; $_MODULE['<{comfino}prestashop>update-notice_d8b5ccfca0da7396ddd16084d27aac3d'] = 'Zobacz notatki dotyczące wydania w serwisie GitHub'; -$_MODULE['<{comfino}prestashop>comfino_7efffb636a64b41b644ed179eb9243f8'] = 'Naprawa konfiguracji'; -$_MODULE['<{comfino}prestashop>configuration-repair_6a95d5888404f75da1d9dd0a1b26315e'] = 'Konfiguracja jest poprawna'; -$_MODULE['<{comfino}prestashop>configuration-repair_2e0b1dc1d0849884c03361e8f5e36547'] = 'Wszystkie %d opcje konfiguracyjne istnieją.'; -$_MODULE['<{comfino}prestashop>configuration-repair_b9ba0e480807be85aed3f40bd765c053'] = 'Wykryto problemy z konfiguracją'; -$_MODULE['<{comfino}prestashop>configuration-repair_bf85b4753cd863060616f0619eca587d'] = 'Brakuje %d opcji konfiguracyjnych z %d wszystkich.'; -$_MODULE['<{comfino}prestashop>configuration-repair_a22760e87d39d1cab7332b2a857f16ef'] = 'Brakujące opcje'; -$_MODULE['<{comfino}prestashop>configuration-repair_94231e22e0b8aa9b84a370da7ace58fb'] = 'Napraw konfigurację'; -$_MODULE['<{comfino}prestashop>configuration-repair_4c81749976568ac4d1862491173b373c'] = 'Naprawianie...'; -$_MODULE['<{comfino}prestashop>configuration-repair_9d35bd2b281d0bbe3cb28b1feaa5008d'] = 'Naprawianie zakończone'; -$_MODULE['<{comfino}prestashop>configuration-repair_2627637e263ef687fa978b576580b8b1'] = 'Sprawdzone'; -$_MODULE['<{comfino}prestashop>configuration-repair_2aee0be2678ee90fd327cc186826438e'] = 'Brakujące'; -$_MODULE['<{comfino}prestashop>configuration-repair_5a50d51067957e621efb88bc79d2e84a'] = 'Naprawione'; -$_MODULE['<{comfino}prestashop>configuration-repair_d7c8c85bf79bbe1b7188497c32c3b0ca'] = 'Zakończone błędem'; -$_MODULE['<{comfino}prestashop>configuration-repair_cb7296dfdf37b78dc349d92b13bb049f'] = 'Naprawione opcje'; -$_MODULE['<{comfino}prestashop>configuration-repair_cea60544baf3effa70727ee172aedb61'] = 'Nienaprawione opcje'; -$_MODULE['<{comfino}prestashop>configuration-repair_c3c30216c0f14628348f68bd29c7ff35'] = 'Odśwież stronę, aby zobaczyć zaktualizowany status konfiguracji.'; -$_MODULE['<{comfino}prestashop>configuration-repair_5e12b310071900a44b8176888de12763'] = 'Naprawa nie powiodła się: nieznany błąd.'; -$_MODULE['<{comfino}prestashop>configuration-repair_5402a4ddf0f7db4c0c846b3a97928dee'] = 'Naprawa nie powiodła się.'; +$_MODULE['<{comfino}prestashop>comfino_ab6340c9c3cff00bd1d580ba07cb8283'] = 'Reset modułu'; + +$_MODULE['<{comfino}prestashop>module-reset_ab6340c9c3cff00bd1d580ba07cb8283'] = 'Reset modułu'; +$_MODULE['<{comfino}prestashop>module-reset_6b579a895d548c054d83c0f977bef75c'] = 'Ta operacja wykona'; +$_MODULE['<{comfino}prestashop>module-reset_967ebe745197b3355caada4e9db0ab3d'] = 'Dodanie brakujących opcji konfiguracyjnych (z zachowaniem istniejących wartości).'; +$_MODULE['<{comfino}prestashop>module-reset_5d51d8f234708b63606b3cab36e77751'] = 'Ponowne zarejestrowanie wszystkich hook-ów PrestaShop.'; +$_MODULE['<{comfino}prestashop>module-reset_b6b49d7a80221e367f7db9b7494e065f'] = 'Odtworzenie niestandardowych statusów zamówień.'; +$_MODULE['<{comfino}prestashop>module-reset_526e08d5342ea49e9c6c4014159d021a'] = 'Wyczyszczenie pamięci podręcznej modułu.'; +$_MODULE['<{comfino}prestashop>module-reset_dc6cbd1f5136370699cc52309c2dc81a'] = 'Uwaga: Ta operacja NIE usuwa istniejącej konfiguracji ani danych.'; +$_MODULE['<{comfino}prestashop>module-reset_f26af8e606a623cb71d8df8c65a7b896'] = 'Czy na pewno chcesz zresetować moduł? Spowoduje to ponowną rejestrację hook-ów i odtworzenie statusów zamówień.'; +$_MODULE['<{comfino}prestashop>module-reset_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Zresetuj moduł'; + +$_MODULE['<{comfino}prestashop>error-log_2ed282f505b8586cfd3487158b956fec'] = 'Wyczyść log błędów'; +$_MODULE['<{comfino}prestashop>error-log_b9c3dc098fa924c9748cf9a283f0d70d'] = 'Czy na pewno chcesz wyczyścić log błędów?'; + +$_MODULE['<{comfino}prestashop>debug-log_62f7d7f11bfc0a72920f08feeca0d7d5'] = 'Wyczyść log debugowania'; +$_MODULE['<{comfino}prestashop>debug-log_582ab5c2ec553cb0cd7061e4f34a45d4'] = 'Czy na pewno chcesz wyczyścić log debugowania?'; + +$_MODULE['<{comfino}prestashop>installation-logs_2fbcbaccda99264847b63f1caf9b411e'] = 'Log instalacji'; +$_MODULE['<{comfino}prestashop>installation-logs_33dece4bdd632cc7d3f122173d4d34d9'] = 'Log aktualizacji'; +$_MODULE['<{comfino}prestashop>installation-logs_87de57d550255967703fc495a43dd208'] = 'Log dezinstalacji'; +$_MODULE['<{comfino}prestashop>installation-logs_f135d18c239c1d1e3f260fbbcfaf1587'] = 'Log instalacji niedostępny.'; +$_MODULE['<{comfino}prestashop>installation-logs_e2e2f88a8081fb8038c258ca60ec3971'] = 'Log aktualizacji niedostępny.'; +$_MODULE['<{comfino}prestashop>installation-logs_416eb74b2d1c711b062563c18c9e7521'] = 'Log dezinstalacji niedostępny.'; + +$_MODULE['<{comfino}prestashop>comfino_5e3868a3dae7d2055cd11efb4321083a'] = 'Resetowanie modułu zakończone z pewnymi błędami.'; +$_MODULE['<{comfino}prestashop>comfino_841eaba3ff65d8b6fa24bc74eaf41183'] = 'Resetowanie modułu zakończone pomyślnie.'; +$_MODULE['<{comfino}prestashop>comfino_3118c3d3aaca5d51ac8d52c960d76f63'] = 'Konfiguracja: %d naprawionych, %d nieudanych'; +$_MODULE['<{comfino}prestashop>comfino_2f1458780e96adb1c81f66ef40da3a4a'] = 'Hooki: %d zarejestrowanych, %d nieudanych'; +$_MODULE['<{comfino}prestashop>comfino_42ee188afab598503289d1b0de299fe2'] = 'Statusy zamówień: %d utworzonych, %d zaktualizowanych, %d nieudanych'; +$_MODULE['<{comfino}prestashop>comfino_2051440020040b387e6ba9a7c1883548'] = 'Resetowanie modułu nie powiodło się'; $_MODULE['<{comfino}prestashop>payment_0f7f7dbb9907ba5d256ef3e368d526b4'] = 'Przejdź do płatności'; $_MODULE['<{comfino}prestashop>payment_e2b7dec8fa4b498156dfee6e4c84b156'] = 'Ta metoda płatności nie jest dostępna'; diff --git a/upgrade/init-2.4.0.php b/upgrade/init-2.4.0.php index 17bf775f..4acc0188 100644 --- a/upgrade/init-2.4.0.php +++ b/upgrade/init-2.4.0.php @@ -43,7 +43,7 @@ function upgrade_module_2_4_0(Comfino $module) // Update code of widget initialization script. ConfigManager::updateWidgetCode('bde49851ffc0fd8239eb5d086c8165d4'); // Update custom order statuses. - ShopStatusManager::updateOrderStatuses(); + ShopStatusManager::updateCustomOrderStatuses(); return true; } diff --git a/upgrade/init-3.0.0.php b/upgrade/init-3.0.0.php index ec5bca1e..f2d00f6b 100644 --- a/upgrade/init-3.0.0.php +++ b/upgrade/init-3.0.0.php @@ -49,9 +49,11 @@ function upgrade_module_3_0_0(Comfino $module) $comfinoOverriddenControllerPath = _PS_OVERRIDE_DIR_ . 'controllers/admin/AdminOrdersController.php'; - if (file_exists($comfinoOverriddenControllerPath) && strpos(Tools::file_get_contents($comfinoOverriddenControllerPath), 'comfino') !== false) { - unlink($comfinoOverriddenControllerPath); - unlink(_PS_CACHE_DIR_ . 'class_index.php'); + if (file_exists($comfinoOverriddenControllerPath) + && strpos(Tools::file_get_contents($comfinoOverriddenControllerPath), COMFINO_MODULE_NAME) !== false + ) { + unlink($comfinoOverriddenControllerPath); + unlink(_PS_CACHE_DIR_ . 'class_index.php'); } } diff --git a/upgrade/init-4.2.4.php b/upgrade/init-4.2.4.php index dc10dcee..f9a64ea9 100644 --- a/upgrade/init-4.2.4.php +++ b/upgrade/init-4.2.4.php @@ -45,7 +45,7 @@ function upgrade_module_4_2_4(Comfino $module) ConfigManager::updateConfiguration( [ 'COMFINO_WIDGET_TYPE' => 'standard', - 'COMFINO_NEW_WIDGET_ACTIVE' => true, + 'COMFINO_NEW_WIDGET_ACTIVE' => true, 'COMFINO_CAT_FILTER_AVAIL_PROD_TYPES' => [ 'INSTALLMENTS_ZERO_PERCENT', 'PAY_LATER', diff --git a/upgrade/init-4.2.6.php b/upgrade/init-4.2.6.php index 021fc604..0d68c0e9 100644 --- a/upgrade/init-4.2.6.php +++ b/upgrade/init-4.2.6.php @@ -27,6 +27,7 @@ use Comfino\Configuration\ConfigManager; use Comfino\DebugLogger; use Comfino\ErrorLogger; +use Comfino\Main; use Comfino\Order\ShopStatusManager; use Comfino\PluginShared\CacheManager; @@ -61,5 +62,7 @@ function upgrade_module_4_2_6(Comfino $module) ErrorLogger::clearLogs(); DebugLogger::clearLogs(); + Main::updateUpgradeLog('Upgrade script for 4.2.6 executed.'); + return true; } diff --git a/upgrade/init-4.2.7.php b/upgrade/init-4.2.7.php new file mode 100644 index 00000000..09712bf9 --- /dev/null +++ b/upgrade/init-4.2.7.php @@ -0,0 +1,61 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +use Comfino\Configuration\ConfigManager; +use Comfino\ErrorLogger; +use Comfino\Main; +use Comfino\Order\ShopStatusManager; +use Comfino\PluginShared\CacheManager; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * @return bool + */ +function upgrade_module_4_2_7(Comfino $module) +{ + if (!$module->checkEnvironment()) { + return false; + } + + // Initialize new configuration option for order reference usage. + ConfigManager::updateConfiguration(['COMFINO_USE_ORDER_REFERENCE' => false], false); + + // Reinitialize and repair custom order statuses. + ShopStatusManager::reinitializeCustomOrderStatuses($module); + + // Clear configuration and frontend cache. + CacheManager::getCachePool()->clear(); + + // Clear plugin logs. + ErrorLogger::clearLogs(); + + Main::updateUpgradeLog('Upgrade script for 4.2.7 executed.'); + + return true; +} diff --git a/vendor/autoload.php b/vendor/autoload.php index bdc15ae5..a1ae96e3 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -20,4 +20,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit7f9f49a6a84f20d26db6891b32f7c97f::getLoader(); +return ComposerAutoloaderInit24635c797d2fe66e451bae21f49f2f81::getLoader(); diff --git a/vendor/comfino/shop-plugins-shared/src/Api/Client.php b/vendor/comfino/shop-plugins-shared/src/Api/Client.php index da48afa0..070f2953 100644 --- a/vendor/comfino/shop-plugins-shared/src/Api/Client.php +++ b/vendor/comfino/shop-plugins-shared/src/Api/Client.php @@ -47,7 +47,7 @@ /** * Comfino API client. * - * @version 1.1.0 + * @version 1.1.2 * @author Artur Kozubski */ class Client @@ -76,7 +76,7 @@ class Client * @var SerializerInterface|null */ protected $serializer; - public const CLIENT_VERSION = '1.1.0'; + public const CLIENT_VERSION = '1.1.2'; public const PRODUCTION_HOST = 'https://api-ecommerce.comfino.pl'; public const SANDBOX_HOST = 'https://api-ecommerce.craty.pl'; diff --git a/vendor/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php b/vendor/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php index 55cd41c3..2b00ec2f 100644 --- a/vendor/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php +++ b/vendor/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php @@ -11,13 +11,16 @@ class AccessDenied extends \RuntimeException implements HttpErrorExceptionInterf private $url; private $requestBody; + + private $responseBody; - public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, string $url = '', string $requestBody = '') + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, string $url = '', string $requestBody = '', string $responseBody = '') { parent::__construct($message, $code, $previous); $this->url = $url; $this->requestBody = $requestBody; + $this->responseBody = $responseBody; } public function getUrl(): string @@ -48,7 +51,7 @@ public function setRequestBody($requestBody): void public function getResponseBody(): string { - return ''; + return $this->responseBody; } /** @@ -56,6 +59,7 @@ public function getResponseBody(): string */ public function setResponseBody($responseBody): void { + $this->responseBody = $responseBody; } public function getStatusCode(): int diff --git a/vendor/comfino/shop-plugins-shared/src/Api/Exception/Conflict.php b/vendor/comfino/shop-plugins-shared/src/Api/Exception/Conflict.php new file mode 100644 index 00000000..53b43146 --- /dev/null +++ b/vendor/comfino/shop-plugins-shared/src/Api/Exception/Conflict.php @@ -0,0 +1,13 @@ +exception === null && $this->response->getStatusCode() >= 500) { throw new ServiceUnavailable( "Comfino API service is unavailable: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]", - 0, + $this->response->getStatusCode(), null, $this->request->getRequestUri(), $requestBody, @@ -151,20 +155,60 @@ final protected function initFromPsrResponse(): self $requestBody ); - case 402: case 403: + throw new Forbidden( + $this->getErrorMessage( + $this->response->getStatusCode(), + $deserializedResponseBody, + "Access denied: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]" + ), + $this->response->getStatusCode(), + null, + $this->request->getRequestUri(), + $requestBody, + $responseBody + ); + case 404: + throw new NotFound( + $this->getErrorMessage( + $this->response->getStatusCode(), + $deserializedResponseBody, + "Entity not found: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]" + ), + $this->response->getStatusCode(), + null, + $this->request->getRequestUri(), + $requestBody, + $responseBody + ); + case 405: - throw new AccessDenied( + throw new MethodNotAllowed( $this->getErrorMessage( $this->response->getStatusCode(), $deserializedResponseBody, - "Access denied: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]" + "Method not allowed: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]" ), $this->response->getStatusCode(), null, $this->request->getRequestUri(), - $requestBody + $requestBody, + $responseBody + ); + + case 409: + throw new Conflict( + $this->getErrorMessage( + $this->response->getStatusCode(), + $deserializedResponseBody, + "Entity already exists: {$this->response->getReasonPhrase()} [{$this->response->getStatusCode()}]" + ), + $this->response->getStatusCode(), + null, + $this->request->getRequestUri(), + $requestBody, + $responseBody ); default: diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Api/Client.php b/vendor/comfino/shop-plugins-shared/src/Common/Api/Client.php index 046d5c76..9c4f2ff9 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Api/Client.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Api/Client.php @@ -100,7 +100,6 @@ public function validateOrder($order): \Comfino\Api\Response\ValidateOrder * @param int $transferTimeout * @param int $connectionMaxNumAttempts * @param array $options - * @return void */ public function resetClient($connectionTimeout, $transferTimeout, $connectionMaxNumAttempts, $options = []): void { diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/ErrorLogger.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/ErrorLogger.php index 089a6f98..125ecd0f 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/ErrorLogger.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/ErrorLogger.php @@ -12,7 +12,7 @@ final class ErrorLogger extends Logger { /** - * @var \Comfino\Extended\Api\Client + * @var Client */ private $apiClient; /** @@ -32,9 +32,10 @@ final class ErrorLogger extends Logger */ private $modulePath; /** - * @var mixed[] + * @var array */ private $environment; + private const CATCHED_ERRORS_MASK = E_ERROR | E_RECOVERABLE_ERROR | E_PARSE; private const ERROR_TYPES = [ E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', @@ -62,13 +63,13 @@ final class ErrorLogger extends Logger private static $logger; /** + * @param Client $apiClient * @param string $logFilePath - * @return self - * @param \Comfino\Extended\Api\Client $apiClient * @param string $host * @param string $platform * @param string $modulePath - * @param mixed[] $environment + * @param array $environment + * @return self */ public static function getInstance($apiClient, $logFilePath, $host, $platform, $modulePath, $environment): self { @@ -79,6 +80,14 @@ public static function getInstance($apiClient, $logFilePath, $host, $platform, $ return self::$instance; } + /** + * @param Client $apiClient + * @param string $logFilePath + * @param string $host + * @param string $platform + * @param string $modulePath + * @param array $environment + */ private function __construct(Client $apiClient, string $logFilePath, string $host, string $platform, string $modulePath, array $environment) { $this->apiClient = $apiClient; @@ -91,6 +100,7 @@ private function __construct(Client $apiClient, string $logFilePath, string $hos /** * @return MonologLogger + * @throws \Exception */ public function getLogger(): MonologLogger { @@ -109,7 +119,6 @@ public function getLogger(): MonologLogger * @param string|null $apiRequest * @param string|null $apiResponse * @param string|null $stackTrace - * @return void */ public function sendError( $errorPrefix, @@ -120,11 +129,13 @@ public function sendError( $apiResponse = null, $stackTrace = null ): void { - if (preg_match('/Error .*in |Exception .*in /', $errorMessage) && strpos($errorMessage, $this->modulePath) === false) { + $formattedErrorMessage = "$errorPrefix: $errorMessage"; + + if (preg_match('/Error .*in |Exception .*in /', $formattedErrorMessage) && strpos($formattedErrorMessage, $this->modulePath) === false) { return; } - if (getenv('COMFINO_DEBUG') === 'TRUE') { + if (getenv('COMFINO_DEV_ENV') === 'TRUE' && getenv('COMFINO_FORCE_ERRORS_SENDING') !== 'TRUE') { $errorsSendingDisabled = true; } else { $errorsSendingDisabled = false; @@ -135,7 +146,7 @@ public function sendError( $this->platform, $this->environment, $errorCode, - "$errorPrefix: $errorMessage", + $formattedErrorMessage, $apiRequestUrl, $apiRequest, $apiResponse, @@ -175,7 +186,14 @@ public function sendError( */ public function logError($errorPrefix, $errorMessage): void { - $this->getLogger()->error($errorPrefix . ': ' . $errorMessage); + try { + $this->getLogger()->error("$errorPrefix: $errorMessage"); + } catch (\Exception $e) { + if (FileUtils::isWritable($this->logFilePath)) { + FileUtils::append($this->logFilePath, "$errorPrefix: $errorMessage"); + FileUtils::append($this->logFilePath, "Logger error: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}"); + } + } } /** @@ -202,16 +220,20 @@ public function clearLogs(): int } /** + * @param int $errorType + * @param string $errorMessage + * @param string $file + * @param int $line * @return false */ - public function errorHandler($errNo, $errMsg, $file, $line): bool + public function errorHandler($errorType, $errorMessage, $file, $line): bool { - $errorType = $this->getErrorTypeName($errNo); - - if (strpos($errorType, 'E_USER_') === false && strpos($errorType, 'NOTICE') === false) { - $this->sendError("Error $errorType in $file:$line", (string) $errNo, $errMsg); + if (!($errorType & self::CATCHED_ERRORS_MASK)) { + return false; } + $this->sendError("Error {$this->getErrorTypeName($errorType)} in $file:$line", (string) $errorType, $errorMessage); + return false; } @@ -229,14 +251,14 @@ public function exceptionHandler($exception): void public function init(): void { - if (getenv('COMFINO_DEBUG') === 'TRUE') { + if (getenv('COMFINO_DEV_ENV') === 'TRUE' && getenv('COMFINO_FORCE_ERRORS_HANDLING') !== 'TRUE') { return; } static $initialized = false; if (!$initialized) { - set_error_handler([$this, 'errorHandler'], E_ERROR | E_RECOVERABLE_ERROR | E_PARSE); + set_error_handler([$this, 'errorHandler'], self::CATCHED_ERRORS_MASK); set_exception_handler([$this, 'exceptionHandler']); register_shutdown_function([$this, 'shutdown']); @@ -246,15 +268,18 @@ public function init(): void public function shutdown(): void { - if (($error = error_get_last()) !== null && ($error['type'] & (E_ERROR | E_RECOVERABLE_ERROR | E_PARSE))) { - $errorType = $this->getErrorTypeName($error['type']); - $this->sendError("Error $errorType in $error[file]:$error[line]", (string) $error['type'], $error['message']); + if (($error = error_get_last()) !== null && ($error['type'] & self::CATCHED_ERRORS_MASK)) { + $this->sendError("Error {$this->getErrorTypeName($error['type'])} in $error[file]:$error[line]", (string) $error['type'], $error['message']); } restore_error_handler(); restore_exception_handler(); } + /** + * @param int $errorType + * @return string + */ private function getErrorTypeName(int $errorType): string { return diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/FileUtils.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/FileUtils.php index 4f8a9498..a02016be 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/FileUtils.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/FileUtils.php @@ -6,6 +6,14 @@ class FileUtils { + /** + * @param string[] $components + */ + public static function buildPathFromComponents($components): string + { + return implode(DIRECTORY_SEPARATOR, $components); + } + /** * @param string $filePath */ diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/Log/LoggerFactory.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/Log/LoggerFactory.php index 2e38577c..79e96731 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/Log/LoggerFactory.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/Log/LoggerFactory.php @@ -51,6 +51,7 @@ public static function createDebugLogger(string $logFilePath, string $minLevel = * @param string $logFilePath * @param bool $enableSanitization * @return Logger + * @throws \Exception */ public static function createErrorLogger(string $logFilePath, bool $enableSanitization = true): Logger { @@ -186,11 +187,11 @@ private static function ensureLogDirectory(string $logFilePath): void return; } - if (!file_exists($htaccessPath = $logDir . '/.htaccess')) { + if (!file_exists($htaccessPath = $logDir . DIRECTORY_SEPARATOR . '.htaccess')) { file_put_contents($htaccessPath, "Order deny,allow\nDeny from all\n"); } - if (!file_exists($indexPath = $logDir . '/index.php')) { + if (!file_exists($indexPath = $logDir . DIRECTORY_SEPARATOR . 'index.php')) { file_put_contents($indexPath, "sanitize($value); + $sanitized[$key] = $this->sanitize($value, $key); } elseif (is_string($value)) { $sanitized[$key] = $this->sanitizeString($value); } else { diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/Logger.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/Logger.php index cb43c31c..649d7aaf 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/Logger.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/Logger.php @@ -20,7 +20,7 @@ protected function findActualLogFile($logFilePath): ?string $filename = pathinfo($logFilePath, PATHINFO_FILENAME); $extension = pathinfo($logFilePath, PATHINFO_EXTENSION); - $files = glob($dir . '/' . $filename . '-*.' . $extension); + $files = glob($dir . DIRECTORY_SEPARATOR . $filename . '-*.' . $extension); if (empty($files)) { return null; @@ -35,14 +35,14 @@ protected function findActualLogFile($logFilePath): ?string /** * @param string $logFilePath - * @return array + * @return string[] */ protected function findAllLogFiles($logFilePath): array { $filename = pathinfo($logFilePath, PATHINFO_FILENAME); $extension = pathinfo($logFilePath, PATHINFO_EXTENSION); - $files = glob(dirname($logFilePath) . '/' . $filename . '-*.' . $extension); + $files = glob(dirname($logFilePath) . DIRECTORY_SEPARATOR . $filename . '-*.' . $extension); return $files ?: []; } diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint.php index c82c0c4c..31696342 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint.php @@ -5,6 +5,8 @@ namespace Comfino\Common\Backend; use Comfino\Api\SerializerInterface; +use Comfino\Common\Exception\InvalidEndpoint; +use Comfino\Common\Exception\InvalidRequest; use ComfinoExternal\Psr\Http\Message\ServerRequestInterface; abstract class RestEndpoint implements RestEndpointInterface @@ -12,11 +14,11 @@ abstract class RestEndpoint implements RestEndpointInterface /** * @var string */ - private $name; + protected $name; /** * @var string */ - private $endpointUrl; + protected $endpointUrl; /** * @var string[] */ @@ -111,4 +113,35 @@ protected function getParsedRequestBody($serverRequest) return $requestPayload; } + + /** + * @param \ComfinoExternal\Psr\Http\Message\ServerRequestInterface $serverRequest + * @param string|null $endpointName + */ + public function processRequest($serverRequest, $endpointName = null): ?array + { + if (!$this->endpointPathMatch($serverRequest, $endpointName)) { + throw new InvalidEndpoint('Endpoint path does not match request path.'); + } + + try { + if (!is_array($requestPayload = $this->getParsedRequestBody($serverRequest))) { + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + 'Invalid request payload.' + ); + } + } catch (\JsonException $e) { + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + sprintf('Invalid request payload: %s', $e->getMessage()), + $e->getCode(), + $e + ); + } + + return $requestPayload; + } } diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php index 20ef69ea..d5054a42 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php @@ -7,8 +7,6 @@ use ComfinoExternal\Cache\TagInterop\TaggableCacheItemPoolInterface; use Comfino\Common\Backend\Cache\ItemTypeEnum; use Comfino\Common\Backend\RestEndpoint; -use Comfino\Common\Exception\InvalidEndpoint; -use Comfino\Common\Exception\InvalidRequest; use ComfinoExternal\Psr\Cache\InvalidArgumentException; use ComfinoExternal\Psr\Http\Message\ServerRequestInterface; @@ -36,19 +34,12 @@ public function __construct( */ public function processRequest($serverRequest, $endpointName = null): ?array { - if (!$this->endpointPathMatch($serverRequest, $endpointName)) { - throw new InvalidEndpoint('Endpoint path does not match request path.'); - } - - try { - if (!is_array($requestPayload = $this->getParsedRequestBody($serverRequest))) { - throw new InvalidRequest('Invalid request payload.'); - } - } catch (\JsonException $e) { - throw new InvalidRequest(sprintf('Invalid request payload: %s', $e->getMessage()), $e->getCode(), $e); - } - - $this->cache->invalidateTags(array_intersect($requestPayload, ItemTypeEnum::values())); + $this->cache->invalidateTags( + array_intersect( + parent::processRequest($serverRequest, $endpointName), + ItemTypeEnum::values() + ) + ); return null; } diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php index 98c19ef8..f70bf7c4 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php @@ -8,7 +8,6 @@ use Comfino\Common\Backend\DebugLogger; use Comfino\Common\Backend\RestEndpoint; use Comfino\Common\Exception\InvalidEndpoint; -use Comfino\Common\Exception\InvalidRequest; use ComfinoExternal\Psr\Http\Message\ServerRequestInterface; class Configuration extends RestEndpoint @@ -123,15 +122,7 @@ public function processRequest($serverRequest, $endpointName = null): ?array ]; } - try { - if (!is_array($requestPayload = $this->getParsedRequestBody($serverRequest))) { - throw new InvalidRequest('Invalid request payload.'); - } - } catch (\JsonException $e) { - throw new InvalidRequest(sprintf('Invalid request payload: %s', $e->getMessage()), $e->getCode(), $e); - } - - $this->configurationManager->updateConfigurationOptions($requestPayload); + $this->configurationManager->updateConfigurationOptions(parent::processRequest($serverRequest, $endpointName)); $this->configurationManager->persist(); return null; diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/ConfigurationRepair.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/ConfigurationRepair.php deleted file mode 100644 index 69399187..00000000 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/ConfigurationRepair.php +++ /dev/null @@ -1,72 +0,0 @@ -validateCallback = $validateCallback; - $this->repairCallback = $repairCallback; - parent::__construct($name, $endpointUrl); - - $this->methods = ['GET', 'POST']; - } - - /** - * @param \ComfinoExternal\Psr\Http\Message\ServerRequestInterface $serverRequest - * @param string|null $endpointName - */ - public function processRequest($serverRequest, $endpointName = null): ?array - { - if (!$this->endpointPathMatch($serverRequest, $endpointName)) { - throw new InvalidEndpoint('Endpoint path does not match request path.'); - } - - $method = strtoupper($serverRequest->getMethod()); - - if ($method === 'GET') { - $validation = ($this->validateCallback)(); - - return [ - 'status' => 'ok', - 'action' => 'validate', - 'validation' => $validation, - ]; - } - - if ($method === 'POST') { - $repairStats = ($this->repairCallback)(); - - return [ - 'status' => 'ok', - 'action' => 'repair', - 'repair_stats' => $repairStats, - ]; - } - - throw new InvalidEndpoint('Invalid HTTP method for configuration repair endpoint.'); - } -} diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php index 7d00cb55..d4b65c60 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php @@ -4,8 +4,8 @@ namespace Comfino\Common\Backend\RestEndpoint; +use Comfino\Api\HttpErrorExceptionInterface; use Comfino\Common\Backend\RestEndpoint; -use Comfino\Common\Exception\InvalidEndpoint; use Comfino\Common\Exception\InvalidRequest; use Comfino\Common\Shop\Order\StatusManager; use ComfinoExternal\Psr\Http\Message\ServerRequestInterface; @@ -45,20 +45,14 @@ public function __construct( */ public function processRequest($serverRequest, $endpointName = null): ?array { - if (!$this->endpointPathMatch($serverRequest, $endpointName)) { - throw new InvalidEndpoint('Endpoint path does not match request path.'); - } - - try { - if (!is_array($requestPayload = $this->getParsedRequestBody($serverRequest))) { - throw new InvalidRequest('Invalid request payload.'); - } - } catch (\JsonException $e) { - throw new InvalidRequest(sprintf('Invalid request payload: %s', $e->getMessage()), $e->getCode(), $e); - } + $requestPayload = parent::processRequest($serverRequest, $endpointName); if (!isset($requestPayload['status'])) { - throw new InvalidRequest('Status must be set.'); + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + 'Status must be set.' + ); } if (in_array($requestPayload['status'], $this->ignoredStatuses, true)) { @@ -66,17 +60,35 @@ public function processRequest($serverRequest, $endpointName = null): ?array } if (!isset($requestPayload['externalId'])) { - throw new InvalidRequest('External ID must be set.'); + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + 'External ID must be set.' + ); } if (in_array($requestPayload['status'], $this->forbiddenStatuses, true)) { - throw new InvalidRequest('Invalid status "' . $requestPayload['status'] . '".'); + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + 'Invalid status "' . $requestPayload['status'] . '".' + ); } try { $this->statusManager->setOrderStatus($requestPayload['externalId'], $requestPayload['status']); } catch (\Throwable $e) { - throw new InvalidRequest($e->getMessage()); + if ($e instanceof HttpErrorExceptionInterface) { + throw $e; + } + + throw new InvalidRequest( + (string) $serverRequest->getUri(), + $serverRequest->getBody()->getContents(), + $e->getMessage(), + $e->getCode(), + $e + ); } return null; diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php index aa71e4b3..3d3a78e8 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php @@ -6,16 +6,15 @@ use Comfino\Api\Exception\AccessDenied; use Comfino\Api\Exception\AuthorizationError; +use Comfino\Api\HttpErrorExceptionInterface; use Comfino\Api\SerializerInterface; use Comfino\Common\Exception\InvalidEndpoint; -use Comfino\Common\Exception\InvalidRequest; use ComfinoExternal\Psr\Http\Message\ResponseFactoryInterface; use ComfinoExternal\Psr\Http\Message\ResponseInterface; use ComfinoExternal\Psr\Http\Message\ServerRequestFactoryInterface; use ComfinoExternal\Psr\Http\Message\ServerRequestInterface; use ComfinoExternal\Psr\Http\Message\StreamFactoryInterface; use ComfinoExternal\Psr\Http\Message\UriFactoryInterface; -use Random\RandomException; final class RestEndpointManager { @@ -209,18 +208,16 @@ public function processRequest(?string $endpointName = null, ?ServerRequestInter try { $this->verifyRequest($serverRequest); - } catch (AuthorizationError $e) { - return $this->getPreparedResponse($this->responseFactory->createResponse(401, $e->getMessage())); - } catch (AccessDenied $e) { - return $this->getPreparedResponse($this->responseFactory->createResponse(403, $e->getMessage())); + } catch (HttpErrorExceptionInterface $e) { + return $this->getPreparedResponse($this->responseFactory->createResponse($e->getStatusCode(), $e->getMessage())); } if (($endpointName !== null) && ($endpoint = $this->getEndpointByName($endpointName)) !== null) { try { return $this->prepareResponse($serverRequest, $endpoint->processRequest($serverRequest, $endpointName)); - } catch (InvalidRequest $e) { + } catch (HttpErrorExceptionInterface $e) { return $this->getPreparedResponse( - $this->responseFactory->createResponse(400, $e->getMessage()), + $this->responseFactory->createResponse($e->getStatusCode(), $e->getMessage()), ['error' => $e->getMessage()] ); } @@ -231,9 +228,9 @@ public function processRequest(?string $endpointName = null, ?ServerRequestInter return $this->prepareResponse($serverRequest, $endpoint->processRequest($serverRequest)); } catch (InvalidEndpoint $exception) { continue; - } catch (InvalidRequest $e) { + } catch (HttpErrorExceptionInterface $e) { return $this->getPreparedResponse( - $this->responseFactory->createResponse(400, $e->getMessage()), + $this->responseFactory->createResponse($e->getStatusCode(), $e->getMessage()), ['error' => $e->getMessage()] ); } diff --git a/vendor/comfino/shop-plugins-shared/src/Common/Exception/InvalidRequest.php b/vendor/comfino/shop-plugins-shared/src/Common/Exception/InvalidRequest.php index c14698b5..a5c7d755 100644 --- a/vendor/comfino/shop-plugins-shared/src/Common/Exception/InvalidRequest.php +++ b/vendor/comfino/shop-plugins-shared/src/Common/Exception/InvalidRequest.php @@ -4,6 +4,65 @@ namespace Comfino\Common\Exception; -class InvalidRequest extends \InvalidArgumentException +use Comfino\Api\HttpErrorExceptionInterface; + +class InvalidRequest extends \InvalidArgumentException implements HttpErrorExceptionInterface { + /** + * @var string + */ + private $url = ''; + /** + * @var string + */ + private $requestBody = ''; + public function __construct(string $url = '', string $requestBody = '', string $message = '', int $code = 0, ?\Throwable $previous = null) + { + $this->url = $url; + $this->requestBody = $requestBody; + parent::__construct($message, $code, $previous); + } + + public function getUrl(): string + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url): void + { + $this->url = $url; + } + + public function getRequestBody(): string + { + return $this->requestBody; + } + + /** + * @param string $requestBody + */ + public function setRequestBody($requestBody): void + { + $this->requestBody = $requestBody; + } + + public function getResponseBody(): string + { + return ''; + } + + /** + * @param string $responseBody + */ + public function setResponseBody($responseBody): void + { + } + + public function getStatusCode(): int + { + return 400; + } } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 04d72c3c..a8a6a4c2 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -214,11 +214,14 @@ 'Comfino\\Api\\Dto\\Payment\\LoanTypeEnum' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Dto/Payment/LoanTypeEnum.php', 'Comfino\\Api\\Exception\\AccessDenied' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php', 'Comfino\\Api\\Exception\\AuthorizationError' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/AuthorizationError.php', + 'Comfino\\Api\\Exception\\Conflict' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/Conflict.php', + 'Comfino\\Api\\Exception\\Forbidden' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/Forbidden.php', + 'Comfino\\Api\\Exception\\MethodNotAllowed' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/MethodNotAllowed.php', + 'Comfino\\Api\\Exception\\NotFound' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/NotFound.php', 'Comfino\\Api\\Exception\\RequestValidationError' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/RequestValidationError.php', 'Comfino\\Api\\Exception\\ResponseValidationError' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/ResponseValidationError.php', 'Comfino\\Api\\Exception\\ServiceUnavailable' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Exception/ServiceUnavailable.php', 'Comfino\\Api\\HttpErrorExceptionInterface' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/HttpErrorExceptionInterface.php', - 'Comfino\\Api\\PluginUpdate' => $baseDir . '/src/Api/PluginUpdate.php', 'Comfino\\Api\\Request' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Request.php', 'Comfino\\Api\\Request\\CancelOrder' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Request/CancelOrder.php', 'Comfino\\Api\\Request\\CreateOrder' => $vendorDir . '/comfino/shop-plugins-shared/src/Api/Request/CreateOrder.php', @@ -273,7 +276,6 @@ 'Comfino\\Common\\Backend\\RestEndpointManager' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php', 'Comfino\\Common\\Backend\\RestEndpoint\\CacheInvalidate' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php', 'Comfino\\Common\\Backend\\RestEndpoint\\Configuration' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php', - 'Comfino\\Common\\Backend\\RestEndpoint\\ConfigurationRepair' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/ConfigurationRepair.php', 'Comfino\\Common\\Backend\\RestEndpoint\\StatusNotification' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php', 'Comfino\\Common\\Exception\\ConnectionTimeout' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Exception/ConnectionTimeout.php', 'Comfino\\Common\\Exception\\InvalidEndpoint' => $vendorDir . '/comfino/shop-plugins-shared/src/Common/Exception/InvalidEndpoint.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 858d846a..9a1e6e08 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -1,6 +1,6 @@ setClassMapAuthoritative(true); $loader->register(false); - $filesToLoad = \Composer\Autoload\ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f::$files; + $filesToLoad = \Composer\Autoload\ComposerStaticInit24635c797d2fe66e451bae21f49f2f81::$files; $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 4153d4d9..a08f4d12 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -2,7 +2,7 @@ namespace Composer\Autoload; -class ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f +class ComposerStaticInit24635c797d2fe66e451bae21f49f2f81 { public static $files = array ( 'e90b514f516f456feec46b4f674e0f58' => __DIR__ . '/..' . '/sunrise/http-message/constants/REASON_PHRASES.php', @@ -512,11 +512,14 @@ class ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f 'Comfino\\Api\\Dto\\Payment\\LoanTypeEnum' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Dto/Payment/LoanTypeEnum.php', 'Comfino\\Api\\Exception\\AccessDenied' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/AccessDenied.php', 'Comfino\\Api\\Exception\\AuthorizationError' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/AuthorizationError.php', + 'Comfino\\Api\\Exception\\Conflict' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/Conflict.php', + 'Comfino\\Api\\Exception\\Forbidden' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/Forbidden.php', + 'Comfino\\Api\\Exception\\MethodNotAllowed' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/MethodNotAllowed.php', + 'Comfino\\Api\\Exception\\NotFound' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/NotFound.php', 'Comfino\\Api\\Exception\\RequestValidationError' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/RequestValidationError.php', 'Comfino\\Api\\Exception\\ResponseValidationError' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/ResponseValidationError.php', 'Comfino\\Api\\Exception\\ServiceUnavailable' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Exception/ServiceUnavailable.php', 'Comfino\\Api\\HttpErrorExceptionInterface' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/HttpErrorExceptionInterface.php', - 'Comfino\\Api\\PluginUpdate' => __DIR__ . '/../..' . '/src/Api/PluginUpdate.php', 'Comfino\\Api\\Request' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Request.php', 'Comfino\\Api\\Request\\CancelOrder' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Request/CancelOrder.php', 'Comfino\\Api\\Request\\CreateOrder' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Api/Request/CreateOrder.php', @@ -571,7 +574,6 @@ class ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f 'Comfino\\Common\\Backend\\RestEndpointManager' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpointManager.php', 'Comfino\\Common\\Backend\\RestEndpoint\\CacheInvalidate' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/CacheInvalidate.php', 'Comfino\\Common\\Backend\\RestEndpoint\\Configuration' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/Configuration.php', - 'Comfino\\Common\\Backend\\RestEndpoint\\ConfigurationRepair' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/ConfigurationRepair.php', 'Comfino\\Common\\Backend\\RestEndpoint\\StatusNotification' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Backend/RestEndpoint/StatusNotification.php', 'Comfino\\Common\\Exception\\ConnectionTimeout' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Exception/ConnectionTimeout.php', 'Comfino\\Common\\Exception\\InvalidEndpoint' => __DIR__ . '/..' . '/comfino/shop-plugins-shared/src/Common/Exception/InvalidEndpoint.php', @@ -642,9 +644,9 @@ class ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit7f9f49a6a84f20d26db6891b32f7c97f::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit24635c797d2fe66e451bae21f49f2f81::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit24635c797d2fe66e451bae21f49f2f81::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit24635c797d2fe66e451bae21f49f2f81::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 235f8b41..e076c770 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -354,12 +354,12 @@ "source": { "type": "git", "url": "git@github.com:comfino/shop-plugins-shared.git", - "reference": "bfb5e9c65416d6b4a04352c60cb28c18732af864" + "reference": "dbf46b5bd29989c047254c147fd3c9adfb8637dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/comfino/shop-plugins-shared/zipball/bfb5e9c65416d6b4a04352c60cb28c18732af864", - "reference": "bfb5e9c65416d6b4a04352c60cb28c18732af864", + "url": "https://api.github.com/repos/comfino/shop-plugins-shared/zipball/dbf46b5bd29989c047254c147fd3c9adfb8637dc", + "reference": "dbf46b5bd29989c047254c147fd3c9adfb8637dc", "shasum": "" }, "require": { @@ -381,7 +381,7 @@ "phpspec/prophecy-phpunit": "^1.1", "phpunit/phpunit": "^5.7" }, - "time": "2025-12-24T20:07:47+00:00", + "time": "2026-01-22T11:35:36+00:00", "default-branch": true, "type": "library", "installation-source": "dist", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index fbe49998..2b21aff7 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'comfino/prestashop', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '1250b984f1cd986e84270d7f60957123b75fa7ab', + 'reference' => '629b6bf9df5a514b9575dafee063b2bda878a184', 'type' => 'prestashop-module', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -58,7 +58,7 @@ 'comfino/prestashop' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '1250b984f1cd986e84270d7f60957123b75fa7ab', + 'reference' => '629b6bf9df5a514b9575dafee063b2bda878a184', 'type' => 'prestashop-module', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -67,7 +67,7 @@ 'comfino/shop-plugins-shared' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'bfb5e9c65416d6b4a04352c60cb28c18732af864', + 'reference' => 'dbf46b5bd29989c047254c147fd3c9adfb8637dc', 'type' => 'library', 'install_path' => __DIR__ . '/../comfino/shop-plugins-shared', 'aliases' => array( diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 00000000..16473219 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 00000000..aecc40f3 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,60 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59" + }, + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse" + }, + "config": { + "lock": false + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 00000000..015ee588 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Formatter; + +interface FormatterInterface +{ + /** + * @param array $record + * @return mixed + */ + public function format(array $record); + /** + * @param array $records + * @return mixed + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 00000000..544ae95b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Formatter; + +use ComfinoExternal\Monolog\Utils; + +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + /** + * @param string $format + * @param string $dateFormat + * @param bool $allowInlineLineBreaks + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = \false, $ignoreEmptyContextAndExtra = \false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + public function includeStacktraces($include = \true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = \true; + } + } + public function allowInlineLineBreaks($allow = \true) + { + $this->allowInlineLineBreaks = $allow; + } + public function ignoreEmptyContextAndExtra($ignore = \true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + public function format(array $record) + { + $vars = parent::format($record); + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (\false !== strpos($output, '%extra.' . $var . '%')) { + $output = str_replace('%extra.' . $var . '%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + foreach ($vars['context'] as $var => $val) { + if (\false !== strpos($output, '%context.' . $var . '%')) { + $output = str_replace('%context.' . $var . '%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + foreach ($vars as $var => $val) { + if (\false !== strpos($output, '%' . $var . '%')) { + $output = str_replace('%' . $var . '%', $this->stringify($val), $output); + } + } + + if (\false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + return $output; + } + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + return $message; + } + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + protected function normalizeException($e) + { + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got ' . gettype($e) . ' / ' . Utils::getClass($e)); + } + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', ' . Utils::getClass($previous) . '(code: ' . $previous->getCode() . '): ' . $previous->getMessage() . ' at ' . $previous->getFile() . ':' . $previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . $previousText . ')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n"; + } + return $str; + } + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, \true); + } + if (is_scalar($data)) { + return (string) $data; + } + if (version_compare(\PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, \true); + } + return str_replace('\/', '/', $this->toJson($data, \true)); + } + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + return $str; + } + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 00000000..f631286b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Formatter; + +use Exception; +use ComfinoExternal\Monolog\Utils; + +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + protected $dateFormat; + protected $maxDepth; + /** + * @param string $dateFormat + * @param int $maxDepth + */ + public function __construct($dateFormat = null, $maxDepth = 9) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + $this->maxDepth = $maxDepth; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + public function format(array $record) + { + return $this->normalize($record); + } + + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + return $records; + } + /** + * @return int + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + /** + * @param int $maxDepth + */ + public function setMaxDepth($maxDepth) + { + $this->maxDepth = $maxDepth; + } + protected function normalize($data, $depth = 0) + { + if ($depth > $this->maxDepth) { + return 'Over ' . $this->maxDepth . ' levels deep, aborting normalization'; + } + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + return $data; + } + if (is_array($data)) { + $normalized = array(); + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items (' . count($data) . ' total), aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value, $depth + 1); + } + return $normalized; + } + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + if (is_object($data)) { + if ($data instanceof Exception || \PHP_VERSION_ID > 70000 && $data instanceof \Throwable) { + return $this->normalizeException($data); + } + + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + $value = $this->toJson($data, \true); + } + return sprintf("[object] (%s: %s)", Utils::getClass($data), $value); + } + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + return '[unknown(' . gettype($data) . ')]'; + } + protected function normalizeException($e) + { + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got ' . gettype($e) . ' / ' . Utils::getClass($e)); + } + $data = array('class' => Utils::getClass($e), 'message' => $e->getMessage(), 'code' => (int) $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine()); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, \true); + } + } + } + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'] . ':' . $frame['line']; + } + } + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + return $data; + } + /** + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException + * @return string + */ + protected function toJson($data, $ignoreErrors = \false) + { + return Utils::jsonEncode($data, null, $ignoreErrors); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 00000000..b6159177 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Formatter\FormatterInterface; +use ComfinoExternal\Monolog\Formatter\LineFormatter; +use ComfinoExternal\Monolog\Logger; +use ComfinoExternal\Monolog\ResettableInterface; + +abstract class AbstractHandler implements HandlerInterface, ResettableInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = \true; + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + /** + * @param int|string $level + * @param bool $bubble + */ + public function __construct($level = Logger::DEBUG, $bubble = \true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + public function close() + { + } + + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), ' . var_export($callback, \true) . ' given'); + } + array_unshift($this->processors, $callback); + return $this; + } + + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + return $this; + } + + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + return $this->formatter; + } + /** + * @param int|string $level + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + return $this; + } + /** + * @return int + */ + public function getLevel() + { + return $this->level; + } + /** + * @param bool $bubble + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + return $this; + } + /** + * @return bool + */ + public function getBubble() + { + return $this->bubble; + } + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + } + public function reset() + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + /** + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 00000000..2bd5aac3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\ResettableInterface; + +abstract class AbstractProcessingHandler extends AbstractHandler +{ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return \false; + } + $record = $this->processRecord($record); + $record['formatted'] = $this->getFormatter()->format($record); + $this->write($record); + return \false === $this->bubble; + } + /** + * @param array $record + * @return void + */ + abstract protected function write(array $record); + /** + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 00000000..1b6723a3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Formatter\FormatterInterface; + +interface FormattableHandlerInterface +{ + /** + * @param FormatterInterface $formatter + * @return HandlerInterface + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + /** + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 00000000..57dcd956 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Formatter\FormatterInterface; +use ComfinoExternal\Monolog\Formatter\LineFormatter; + +trait FormattableHandlerTrait +{ + /** + * @var FormatterInterface + */ + protected $formatter; + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + return $this; + } + + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + return $this->formatter; + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 00000000..084dcb86 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Formatter\FormatterInterface; + +interface HandlerInterface +{ + /** + * @param array $record + * @return bool + */ + public function isHandling(array $record); + /** + * @param array $record + * @return bool + */ + public function handle(array $record); + /** + * @param array $records + */ + public function handleBatch(array $records); + /** + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + /** + * @return callable + */ + public function popProcessor(); + /** + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + /** + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 00000000..36f6b3b9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Processor\ProcessorInterface; + +interface ProcessableHandlerInterface +{ + /** + * @param ProcessorInterface|callable $callback + * @return HandlerInterface + */ + public function pushProcessor($callback): HandlerInterface; + /** + * @throws \LogicException + * @return callable + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 00000000..89bd3485 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\ResettableInterface; + +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + */ + protected $processors = []; + + public function pushProcessor($callback): HandlerInterface + { + array_unshift($this->processors, $callback); + return $this; + } + + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + return $record; + } + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 00000000..02d9aad0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Logger; +use ComfinoExternal\Monolog\Utils; + +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + /** + * @param string $filename + * @param int $maxFiles + * @param int $level + * @param bool $bubble + * @param int|null $filePermission + * @param bool $useLocking + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = \true, $filePermission = null, $useLocking = \false) + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + public function close() + { + parent::close(); + if (\true === $this->mustRotate) { + $this->rotate(); + } + } + + public function reset() + { + parent::reset(); + if (\true === $this->mustRotate) { + $this->rotate(); + } + } + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error('Invalid date format - format must be one of ' . 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") ' . 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the ' . 'date formats using slashes, underscores and/or dots instead of dashes.', \E_USER_DEPRECATED); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error('Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', \E_USER_DEPRECATED); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + protected function write(array $record) + { + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = \true; + $this->close(); + } + parent::write($record); + } + + protected function rotate() + { + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + if (0 === $this->maxFiles) { + return; + } + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + return; + } + + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + }); + unlink($file); + restore_error_handler(); + } + } + $this->mustRotate = \false; + } + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace(array('{filename}', '{date}'), array($fileInfo['filename'], date($this->dateFormat)), $fileInfo['dirname'] . '/' . $this->filenameFormat); + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.' . $fileInfo['extension']; + } + return $timedFilename; + } + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace(array('{filename}', '{date}'), array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'), $fileInfo['dirname'] . '/' . $this->filenameFormat); + if (!empty($fileInfo['extension'])) { + $glob .= '.' . $fileInfo['extension']; + } + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 00000000..1f8ac723 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Handler; + +use ComfinoExternal\Monolog\Logger; +use ComfinoExternal\Monolog\Utils; + +class StreamHandler extends AbstractProcessingHandler +{ + const CHUNK_SIZE = 524288; + + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + /** + * @param resource|string $stream + * @param int $level + * @param bool $bubble + * @param int|null $filePermission + * @param bool $useLocking + * @throws \Exception + * @throws \InvalidArgumentException + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = \true, $filePermission = null, $useLocking = \false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + $this->streamSetChunkSize(); + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + /** + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + /** + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: ' . $this->errorMessage, $this->url)); + } + $this->streamSetChunkSize(); + } + if ($this->useLocking) { + flock($this->stream, \LOCK_EX); + } + $this->streamWrite($this->stream, $record); + if ($this->useLocking) { + flock($this->stream, \LOCK_UN); + } + } + /** + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + protected function streamSetChunkSize() + { + if (version_compare(\PHP_VERSION, '5.4.0', '>=')) { + return stream_set_chunk_size($this->stream, self::CHUNK_SIZE); + } + return \false; + } + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + /** + * @param string $stream + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === \false) { + return dirname($stream); + } + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + return null; + } + private function createDir() + { + if ($this->dirCreated) { + return; + } + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, \true); + restore_error_handler(); + if (\false === $status && !is_dir($dir)) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: ' . $this->errorMessage, $dir)); + } + } + $this->dirCreated = \true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 00000000..7852c651 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,514 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog; + +use ComfinoExternal\Monolog\Handler\HandlerInterface; +use ComfinoExternal\Monolog\Handler\StreamHandler; +use ComfinoExternal\Psr\Log\LoggerInterface; +use ComfinoExternal\Psr\Log\InvalidArgumentException; +use Exception; + +class Logger implements LoggerInterface, ResettableInterface +{ + const DEBUG = 100; + + const INFO = 200; + + const NOTICE = 250; + + const WARNING = 300; + + const ERROR = 400; + + const CRITICAL = 500; + + const ALERT = 550; + + const EMERGENCY = 600; + /** + * @var int + */ + const API = 1; + /** + * @var array $levels + */ + protected static $levels = array(self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', self::WARNING => 'WARNING', self::ERROR => 'ERROR', self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY'); + /** + * @var \DateTimeZone + */ + protected static $timezone; + /** + * @var string + */ + protected $name; + /** + * @var HandlerInterface[] + */ + protected $handlers; + /** + * @var callable[] + */ + protected $processors; + /** + * @var bool + */ + protected $microsecondTimestamps = \true; + /** + * @var callable + */ + protected $exceptionHandler; + /** + * @param string $name + * @param HandlerInterface[] $handlers + * @param callable[] $processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + } + /** + * @return string + */ + public function getName() + { + return $this->name; + } + /** + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + return $new; + } + /** + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + return $this; + } + /** + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + return array_shift($this->handlers); + } + /** + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + return $this; + } + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + /** + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), ' . var_export($callback, \true) . ' given'); + } + array_unshift($this->processors, $callback); + return $this; + } + /** + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + /** + * @param bool $micro + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + /** + * @param int $level + * @param string $message + * @param array $context + * @return bool + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + $levelName = static::getLevelName($level); + + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + next($this->handlers); + } + if (null === $handlerKey) { + return \false; + } + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + if ($this->microsecondTimestamps && \PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(\true)), static::$timezone); + } else { + $ts = new \DateTime('now', static::$timezone); + } + $ts->setTimezone(static::$timezone); + $record = array('message' => (string) $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, 'datetime' => $ts, 'extra' => array()); + try { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + while ($handler = current($this->handlers)) { + if (\true === $handler->handle($record)) { + break; + } + next($this->handlers); + } + } catch (Exception $e) { + $this->handleException($e, $record); + } + return \true; + } + + public function close() + { + foreach ($this->handlers as $handler) { + if (method_exists($handler, 'close')) { + $handler->close(); + } + } + } + + public function reset() + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + /** + * @return array + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + /** + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels))); + } + return static::$levels[$level]; + } + /** + * @param string|int $level + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level)) { + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__ . '::' . $upper)) { + return constant(__CLASS__ . '::' . $upper); + } + } + return $level; + } + /** + * @param int $level + * @return bool + */ + public function isHandling($level) + { + $record = array('level' => $level); + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return \true; + } + } + return \false; + } + /** + * @param callable $callback + * @return $this + */ + public function setExceptionHandler($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), ' . var_export($callback, \true) . ' given'); + } + $this->exceptionHandler = $callback; + return $this; + } + /** + * @return callable + */ + public function getExceptionHandler() + { + return $this->exceptionHandler; + } + + protected function handleException(Exception $e, array $record) + { + if (!$this->exceptionHandler) { + throw $e; + } + call_user_func($this->exceptionHandler, $e, $record); + } + /** + * @param mixed $level + * @param string $message + * @param array $context + * @return bool + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + return $this->addRecord($level, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + /** + * @param string $message + * @param array $context + * @return bool + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + /** + * @param \DateTimeZone $tz + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 00000000..01d57aa5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Processor; + +interface ProcessorInterface +{ + /** + * @return array + */ + public function __invoke(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 00000000..df47fb6a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog\Processor; + +use ComfinoExternal\Monolog\Utils; + +class PsrLogMessageProcessor implements ProcessorInterface +{ + const SIMPLE_DATE = "Y-m-d\\TH:i:s.uP"; + + private $dateFormat; + + private $removeUsedContextFields; + /** + * @param string|null $dateFormat + * @param bool $removeUsedContextFields + */ + public function __construct($dateFormat = null, $removeUsedContextFields = \false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (\false === strpos($record['message'], '{')) { + return $record; + } + $replacements = array(); + foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record['message'], $placeholder) === \false) { + continue; + } + if (is_null($val) || is_scalar($val) || is_object($val) && method_exists($val, "__toString")) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTime) { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } elseif (is_object($val)) { + $replacements[$placeholder] = '[object ' . Utils::getClass($val) . ']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array' . Utils::jsonEncode($val, null, \true); + } else { + $replacements[$placeholder] = '[' . gettype($val) . ']'; + } + if ($this->removeUsedContextFields) { + unset($record['context'][$key]); + } + } + $record['message'] = strtr($record['message'], $replacements); + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 00000000..bfb358ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog; + +interface ResettableInterface +{ + public function reset(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 00000000..2a0d9417 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Monolog; + +class Utils +{ + public static function getClass($object) + { + $class = \get_class($object); + return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\x00") ? get_parent_class($class) . '@anonymous' : $class; + } + /** + * @param string $streamUrl + * @return string + */ + public static function canonicalizePath($streamUrl) + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + if (\false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix . $streamUrl; + } + $streamUrl = getcwd() . '/' . $streamUrl; + return $prefix . $streamUrl; + } + /** + * @param mixed $data + * @param int $encodeFlags + * @param bool $ignoreErrors + * @throws \RuntimeException + * @return string + */ + public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = \false) + { + if (null === $encodeFlags && version_compare(\PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE; + } + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (\false === $json) { + return 'null'; + } + return $json; + } + $json = json_encode($data, $encodeFlags); + if (\false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + return $json; + } + /** + * @param int $code + * @param mixed $data + * @param int $encodeFlags + * @throws \RuntimeException + * @return string + */ + public static function handleJsonError($code, $data, $encodeFlags = null) + { + if ($code !== \JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + if (null === $encodeFlags && version_compare(\PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE; + } + $json = json_encode($data, $encodeFlags); + if ($json === \false) { + self::throwEncodeError(json_last_error(), $data); + } + return $json; + } + /** + * @param int $code + * @param mixed $data + * @throws \RuntimeException + */ + private static function throwEncodeError($code, $data) + { + switch ($code) { + case \JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case \JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case \JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case \JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + throw new \RuntimeException('JSON encoding failed: ' . $msg . '. Encoding: ' . var_export($data, \true)); + } + /** + * @param mixed $data + */ + public static function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback('/[\x80-\xFF]+/', function ($m) { + return utf8_encode($m[0]); + }, $data); + $data = str_replace(array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), $data); + } + } +} diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 00000000..4957933a --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 00000000..cc7cc123 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 00000000..0a96e7c5 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (!\function_exists('ComfinoExternal\trigger_deprecation')) { + /** + * @param string $package + * @param string $version + * @param string $message + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @\trigger_error(($package || $version ? "Since {$package} {$version}: " : '') . ($args ? \vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php new file mode 100644 index 00000000..5239309a --- /dev/null +++ b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Debug; + +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use ComfinoExternal\Symfony\Component\OptionsResolver\OptionsResolver; + +class OptionsResolverIntrospector +{ + private $get; + public function __construct(OptionsResolver $optionsResolver) + { + $this->get = \Closure::bind(function ($property, $option, $message) { + if (!$this->isDefined($option)) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); + } + if (!\array_key_exists($option, $this->{$property})) { + throw new NoConfigurationException($message); + } + return $this->{$property}[$option]; + }, $optionsResolver, $optionsResolver); + } + /** + * @return mixed + * @throws NoConfigurationException + */ + public function getDefault(string $option) + { + return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + } + /** + * @return \Closure[] + * @throws NoConfigurationException + */ + public function getLazyClosures(string $option): array + { + return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + } + /** + * @return string[] + * @throws NoConfigurationException + */ + public function getAllowedTypes(string $option): array + { + return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + } + /** + * @return mixed[] + * @throws NoConfigurationException + */ + public function getAllowedValues(string $option): array + { + return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + } + /** + * @throws NoConfigurationException + */ + public function getNormalizer(string $option): \Closure + { + return current($this->getNormalizers($option)); + } + /** + * @throws NoConfigurationException + */ + public function getNormalizers(string $option): array + { + return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } + /** + * @return string|\Closure + * @throws NoConfigurationException + */ + public function getDeprecationMessage(string $option) + { + return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option)); + } +} diff --git a/vendor/symfony/options-resolver/Exception/AccessException.php b/vendor/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 00000000..2c3a5fa4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/AccessException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class AccessException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 00000000..e8d89a54 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..903d864a --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 00000000..8b5468a4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class InvalidOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/MissingOptionsException.php b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 00000000..a565b5c3 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class MissingOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoConfigurationException.php b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 00000000..a87f6e23 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +use ComfinoExternal\Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; + +class NoConfigurationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 00000000..3b79bc1e --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 00000000..ae4936ea --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class OptionDefinitionException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 00000000..042e6f70 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver\Exception; + +class UndefinedOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE new file mode 100644 index 00000000..88bf75bb --- /dev/null +++ b/vendor/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/options-resolver/Options.php b/vendor/symfony/options-resolver/Options.php new file mode 100644 index 00000000..5e10c182 --- /dev/null +++ b/vendor/symfony/options-resolver/Options.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver; + +interface Options extends \ArrayAccess, \Countable +{ +} diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 00000000..ef7e5190 --- /dev/null +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,728 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\OptionsResolver; + +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\AccessException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use ComfinoExternal\Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; + +class OptionsResolver implements Options +{ + private $defined = []; + + private $defaults = []; + /** + * @var \Closure[][] + */ + private $nested = []; + + private $required = []; + + private $resolved = []; + /** + * @var \Closure[][] + */ + private $normalizers = []; + + private $allowedValues = []; + + private $allowedTypes = []; + + private $lazy = []; + + private $calling = []; + + private $deprecated = []; + + private $given = []; + + private $locked = \false; + private $parentsOptions = []; + private const TYPE_ALIASES = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float']; + /** + * @param string $option + * @param mixed $value + * @return $this + * @throws AccessException + */ + public function setDefault($option, $value) + { + if ($this->locked) { + throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); + } + + if ($value instanceof \Closure) { + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + if (isset($params[0]) && Options::class === $this->getParameterClassName($params[0])) { + if (!isset($this->defaults[$option])) { + $this->defaults[$option] = null; + } + + if (!isset($this->lazy[$option]) || !isset($params[1])) { + $this->lazy[$option] = []; + } + + $this->lazy[$option][] = $value; + $this->defined[$option] = \true; + + unset($this->resolved[$option], $this->nested[$option]); + return $this; + } + if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || ($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName())) { + $this->nested[$option][] = $value; + $this->defaults[$option] = []; + $this->defined[$option] = \true; + + unset($this->resolved[$option], $this->lazy[$option]); + return $this; + } + } + + unset($this->lazy[$option], $this->nested[$option]); + + if (!isset($this->defined[$option]) || \array_key_exists($option, $this->resolved)) { + $this->resolved[$option] = $value; + } + $this->defaults[$option] = $value; + $this->defined[$option] = \true; + return $this; + } + /** + * @return $this + * @throws AccessException + */ + public function setDefaults(array $defaults) + { + foreach ($defaults as $option => $value) { + $this->setDefault($option, $value); + } + return $this; + } + /** + * @param string $option + * @return bool + */ + public function hasDefault($option) + { + return \array_key_exists($option, $this->defaults); + } + /** + * @param string|string[] $optionNames + * @return $this + * @throws AccessException + */ + public function setRequired($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); + } + foreach ((array) $optionNames as $option) { + $this->defined[$option] = \true; + $this->required[$option] = \true; + } + return $this; + } + /** + * @param string $option + * @return bool + */ + public function isRequired($option) + { + return isset($this->required[$option]); + } + /** + * @return string[] + */ + public function getRequiredOptions() + { + return array_keys($this->required); + } + /** + * @param string $option + * @return bool + */ + public function isMissing($option) + { + return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults); + } + /** + * @return string[] + */ + public function getMissingOptions() + { + return array_keys(array_diff_key($this->required, $this->defaults)); + } + /** + * @param string|string[] $optionNames + * @return $this + * @throws AccessException + */ + public function setDefined($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); + } + foreach ((array) $optionNames as $option) { + $this->defined[$option] = \true; + } + return $this; + } + /** + * @param string $option + * @return bool + */ + public function isDefined($option) + { + return isset($this->defined[$option]); + } + /** + * @return string[] + */ + public function getDefinedOptions() + { + return array_keys($this->defined); + } + public function isNested(string $option): bool + { + return isset($this->nested[$option]); + } + /** + * @param string|\Closure $deprecationMessage + */ + public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage))); + } + + if ('' === $deprecationMessage) { + return $this; + } + $this->deprecated[$option] = $deprecationMessage; + + unset($this->resolved[$option]); + return $this; + } + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + /** + * @param string $option + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function setNormalizer($option, \Closure $normalizer) + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + $this->normalizers[$option] = [$normalizer]; + + unset($this->resolved[$option]); + return $this; + } + /** + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = \false): self + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + if ($forcePrepend) { + $this->normalizers[$option] = $this->normalizers[$option] ?? []; + array_unshift($this->normalizers[$option], $normalizer); + } else { + $this->normalizers[$option][] = $normalizer; + } + + unset($this->resolved[$option]); + return $this; + } + /** + * @param string $option + * @param mixed $allowedValues + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function setAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues]; + + unset($this->resolved[$option]); + return $this; + } + /** + * @param string $option + * @param mixed $allowedValues + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function addAllowedValues($option, $allowedValues) + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + if (!\is_array($allowedValues)) { + $allowedValues = [$allowedValues]; + } + if (!isset($this->allowedValues[$option])) { + $this->allowedValues[$option] = $allowedValues; + } else { + $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); + } + + unset($this->resolved[$option]); + return $this; + } + /** + * @param string $option + * @param string|string[] $allowedTypes + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function setAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + $this->allowedTypes[$option] = (array) $allowedTypes; + + unset($this->resolved[$option]); + return $this; + } + /** + * @param string $option + * @param string|string[] $allowedTypes + * @return $this + * @throws UndefinedOptionsException + * @throws AccessException + */ + public function addAllowedTypes($option, $allowedTypes) + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); + } + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + if (!isset($this->allowedTypes[$option])) { + $this->allowedTypes[$option] = (array) $allowedTypes; + } else { + $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); + } + + unset($this->resolved[$option]); + return $this; + } + /** + * @param string|string[] $optionNames + * @return $this + * @throws AccessException + */ + public function remove($optionNames) + { + if ($this->locked) { + throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); + } + foreach ((array) $optionNames as $option) { + unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); + unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option]); + } + return $this; + } + /** + * @return $this + * @throws AccessException + */ + public function clear() + { + if ($this->locked) { + throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); + } + $this->defined = []; + $this->defaults = []; + $this->nested = []; + $this->required = []; + $this->resolved = []; + $this->lazy = []; + $this->normalizers = []; + $this->allowedTypes = []; + $this->allowedValues = []; + $this->deprecated = []; + return $this; + } + /** + * @return array + * @throws UndefinedOptionsException + * @throws InvalidOptionsException + * @throws MissingOptionsException + * @throws OptionDefinitionException + * @throws NoSuchOptionException + * @throws AccessException + */ + public function resolve(array $options = []) + { + if ($this->locked) { + throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); + } + + $clone = clone $this; + + $diff = array_diff_key($options, $clone->defined); + if (\count($diff) > 0) { + ksort($clone->defined); + ksort($diff); + throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.') . ' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined)))); + } + + foreach ($options as $option => $value) { + $clone->given[$option] = \true; + $clone->defaults[$option] = $value; + unset($clone->resolved[$option], $clone->lazy[$option]); + } + + $diff = array_diff_key($clone->required, $clone->defaults); + if (\count($diff) > 0) { + ksort($diff); + throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff)))); + } + + $clone->locked = \true; + + foreach ($clone->defaults as $option => $_) { + $clone->offsetGet($option); + } + return $clone->resolved; + } + /** + * @param string $option + * @param bool $triggerDeprecation + * @return mixed + * @throws AccessException + * @throws NoSuchOptionException + * @throws InvalidOptionsException + * @throws OptionDefinitionException + */ + #[\ReturnTypeWillChange] + public function offsetGet($option) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + $triggerDeprecation = 1 === \func_num_args() || func_get_arg(1); + + if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) { + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option])) { + @trigger_error(strtr($this->deprecated[$option], ['%name%' => $option]), \E_USER_DEPRECATED); + } + return $this->resolved[$option]; + } + + if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) { + if (!isset($this->defined[$option])) { + throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option]))); + } + $value = $this->defaults[$option]; + + if (isset($this->nested[$option])) { + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + if (!\is_array($value)) { + throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), $this->formatTypeOf($value))); + } + + $this->calling[$option] = \true; + try { + $resolver = new self(); + $resolver->parentsOptions = $this->parentsOptions; + $resolver->parentsOptions[] = $option; + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + $value = $resolver->resolve($value); + } finally { + unset($this->calling[$option]); + } + } + + if (isset($this->lazy[$option])) { + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + $this->calling[$option] = \true; + try { + foreach ($this->lazy[$option] as $closure) { + $value = $closure($this, $value); + } + } finally { + unset($this->calling[$option]); + } + + } + + if (isset($this->allowedTypes[$option])) { + $valid = \true; + $invalidTypes = []; + foreach ($this->allowedTypes[$option] as $type) { + $type = self::TYPE_ALIASES[$type] ?? $type; + if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { + break; + } + } + if (!$valid) { + $fmtActualValue = $this->formatValue($value); + $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]); + $fmtProvidedTypes = implode('|', array_keys($invalidTypes)); + $allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static function ($item) { + return str_ends_with(self::TYPE_ALIASES[$item] ?? $item, '[]'); + })) > 0; + if (\is_array($value) && $allowedContainsArrayType) { + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + } + + if (isset($this->allowedValues[$option])) { + $success = \false; + $printableAllowedValues = []; + foreach ($this->allowedValues[$option] as $allowedValue) { + if ($allowedValue instanceof \Closure) { + if ($allowedValue($value)) { + $success = \true; + break; + } + + continue; + } + if ($value === $allowedValue) { + $success = \true; + break; + } + $printableAllowedValues[] = $allowedValue; + } + if (!$success) { + $message = sprintf('The option "%s" with value %s is invalid.', $option, $this->formatValue($value)); + if (\count($printableAllowedValues) > 0) { + $message .= sprintf(' Accepted values are: %s.', $this->formatValues($printableAllowedValues)); + } + throw new InvalidOptionsException($message); + } + } + + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling && \is_string($this->deprecated[$option]))) { + $deprecationMessage = $this->deprecated[$option]; + if ($deprecationMessage instanceof \Closure) { + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + $this->calling[$option] = \true; + try { + if (!\is_string($deprecationMessage = $deprecationMessage($this, $value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', \gettype($deprecationMessage))); + } + } finally { + unset($this->calling[$option]); + } + } + if ('' !== $deprecationMessage) { + @trigger_error(strtr($deprecationMessage, ['%name%' => $option]), \E_USER_DEPRECATED); + } + } + + if (isset($this->normalizers[$option])) { + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + $this->calling[$option] = \true; + try { + foreach ($this->normalizers[$option] as $normalizer) { + $value = $normalizer($this, $value); + } + } finally { + unset($this->calling[$option]); + } + + } + + $this->resolved[$option] = $value; + return $value; + } + private function verifyTypes(string $type, $value, array &$invalidTypes, int $level = 0): bool + { + if (\is_array($value) && '[]' === substr($type, -2)) { + $type = substr($type, 0, -2); + $valid = \true; + foreach ($value as $val) { + if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) { + $valid = \false; + } + } + return $valid; + } + if ('null' === $type && null === $value || \function_exists($func = 'is_' . $type) && $func($value) || $value instanceof $type) { + return \true; + } + if (!$invalidTypes || $level > 0) { + $invalidTypes[$this->formatTypeOf($value)] = \true; + } + return \false; + } + /** + * @param string $option + * @return bool + * @throws AccessException + */ + #[\ReturnTypeWillChange] + public function offsetExists($option) + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + return \array_key_exists($option, $this->defaults); + } + /** + * @return void + * @throws AccessException + */ + #[\ReturnTypeWillChange] + public function offsetSet($option, $value) + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + /** + * @return void + * @throws AccessException + */ + #[\ReturnTypeWillChange] + public function offsetUnset($option) + { + throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); + } + /** + * @return int + * @throws AccessException + */ + #[\ReturnTypeWillChange] + public function count() + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + return \count($this->defaults); + } + /** + * @param mixed $value + * @return string + */ + private function formatTypeOf($value): string + { + return \is_object($value) ? \get_class($value) : \gettype($value); + } + /** + * @param mixed $value + */ + private function formatValue($value): string + { + if (\is_object($value)) { + return \get_class($value); + } + if (\is_array($value)) { + return 'array'; + } + if (\is_string($value)) { + return '"' . $value . '"'; + } + if (\is_resource($value)) { + return 'resource'; + } + if (null === $value) { + return 'null'; + } + if (\false === $value) { + return 'false'; + } + if (\true === $value) { + return 'true'; + } + return (string) $value; + } + + private function formatValues(array $values): string + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value); + } + return implode(', ', $values); + } + private function formatOptions(array $options): string + { + if ($this->parentsOptions) { + $prefix = array_shift($this->parentsOptions); + if ($this->parentsOptions) { + $prefix .= sprintf('[%s]', implode('][', $this->parentsOptions)); + } + $options = array_map(static function (string $option) use ($prefix): string { + return sprintf('%s[%s]', $prefix, $option); + }, $options); + } + return implode('", "', $options); + } + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) { + return null; + } + return $type->getName(); + } +} diff --git a/vendor/symfony/options-resolver/README.md b/vendor/symfony/options-resolver/README.md new file mode 100644 index 00000000..c63b9005 --- /dev/null +++ b/vendor/symfony/options-resolver/README.md @@ -0,0 +1,15 @@ +OptionsResolver Component +========================= + +The OptionsResolver component is `array_replace` on steroids. It allows you to +create an options system with required options, defaults, validation (type, +value), normalization and more. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/options_resolver.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/options-resolver/composer.json b/vendor/symfony/options-resolver/composer.json new file mode 100644 index 00000000..b63c7ee6 --- /dev/null +++ b/vendor/symfony/options-resolver/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Provides an improved replacement for the array_replace PHP function", + "keywords": ["options", "config", "configuration"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 00000000..14069b69 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Polyfill\Ctype; + +final class Ctype +{ + /** + * @param mixed $text + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + /** + * @param mixed $text + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + /** + * @param mixed $int + * @param string $function + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + if ($int < -128 || $int > 255) { + return (string) $int; + } + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function . '(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + if ($int < 0) { + $int += 256; + } + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 00000000..b144d03c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 00000000..15da29f0 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use ComfinoExternal\Symfony\Polyfill\Ctype as p; +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__ . '/bootstrap80.php'; +} +if (!\function_exists('ctype_alnum') && !\function_exists('ComfinoExternal\ctype_alnum')) { + function ctype_alnum($text) + { + return p\Ctype::ctype_alnum($text); + } +} +if (!\function_exists('ctype_alpha') && !\function_exists('ComfinoExternal\ctype_alpha')) { + function ctype_alpha($text) + { + return p\Ctype::ctype_alpha($text); + } +} +if (!\function_exists('ctype_cntrl') && !\function_exists('ComfinoExternal\ctype_cntrl')) { + function ctype_cntrl($text) + { + return p\Ctype::ctype_cntrl($text); + } +} +if (!\function_exists('ctype_digit') && !\function_exists('ComfinoExternal\ctype_digit')) { + function ctype_digit($text) + { + return p\Ctype::ctype_digit($text); + } +} +if (!\function_exists('ctype_graph') && !\function_exists('ComfinoExternal\ctype_graph')) { + function ctype_graph($text) + { + return p\Ctype::ctype_graph($text); + } +} +if (!\function_exists('ctype_lower') && !\function_exists('ComfinoExternal\ctype_lower')) { + function ctype_lower($text) + { + return p\Ctype::ctype_lower($text); + } +} +if (!\function_exists('ctype_print') && !\function_exists('ComfinoExternal\ctype_print')) { + function ctype_print($text) + { + return p\Ctype::ctype_print($text); + } +} +if (!\function_exists('ctype_punct') && !\function_exists('ComfinoExternal\ctype_punct')) { + function ctype_punct($text) + { + return p\Ctype::ctype_punct($text); + } +} +if (!\function_exists('ctype_space') && !\function_exists('ComfinoExternal\ctype_space')) { + function ctype_space($text) + { + return p\Ctype::ctype_space($text); + } +} +if (!\function_exists('ctype_upper') && !\function_exists('ComfinoExternal\ctype_upper')) { + function ctype_upper($text) + { + return p\Ctype::ctype_upper($text); + } +} +if (!\function_exists('ctype_xdigit') && !\function_exists('ComfinoExternal\ctype_xdigit')) { + function ctype_xdigit($text) + { + return p\Ctype::ctype_xdigit($text); + } +} diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 00000000..153c6d21 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use ComfinoExternal\Symfony\Polyfill\Ctype as p; +if (!\function_exists('ctype_alnum') && !\function_exists('ComfinoExternal\ctype_alnum')) { + function ctype_alnum(mixed $text): bool + { + return p\Ctype::ctype_alnum($text); + } +} +if (!\function_exists('ctype_alpha') && !\function_exists('ComfinoExternal\ctype_alpha')) { + function ctype_alpha(mixed $text): bool + { + return p\Ctype::ctype_alpha($text); + } +} +if (!\function_exists('ctype_cntrl') && !\function_exists('ComfinoExternal\ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool + { + return p\Ctype::ctype_cntrl($text); + } +} +if (!\function_exists('ctype_digit') && !\function_exists('ComfinoExternal\ctype_digit')) { + function ctype_digit(mixed $text): bool + { + return p\Ctype::ctype_digit($text); + } +} +if (!\function_exists('ctype_graph') && !\function_exists('ComfinoExternal\ctype_graph')) { + function ctype_graph(mixed $text): bool + { + return p\Ctype::ctype_graph($text); + } +} +if (!\function_exists('ctype_lower') && !\function_exists('ComfinoExternal\ctype_lower')) { + function ctype_lower(mixed $text): bool + { + return p\Ctype::ctype_lower($text); + } +} +if (!\function_exists('ctype_print') && !\function_exists('ComfinoExternal\ctype_print')) { + function ctype_print(mixed $text): bool + { + return p\Ctype::ctype_print($text); + } +} +if (!\function_exists('ctype_punct') && !\function_exists('ComfinoExternal\ctype_punct')) { + function ctype_punct(mixed $text): bool + { + return p\Ctype::ctype_punct($text); + } +} +if (!\function_exists('ctype_space') && !\function_exists('ComfinoExternal\ctype_space')) { + function ctype_space(mixed $text): bool + { + return p\Ctype::ctype_space($text); + } +} +if (!\function_exists('ctype_upper') && !\function_exists('ComfinoExternal\ctype_upper')) { + function ctype_upper(mixed $text): bool + { + return p\Ctype::ctype_upper($text); + } +} +if (!\function_exists('ctype_xdigit') && !\function_exists('ComfinoExternal\ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool + { + return p\Ctype::ctype_xdigit($text); + } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 00000000..b222fdab --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php73/LICENSE b/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php73/Php73.php b/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 00000000..4f483185 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Polyfill\Php73; + +final class Php73 +{ + public static $startAt = 1533462603; + /** + * @param bool $asNum + * @return array|float|int + */ + public static function hrtime($asNum = \false) + { + $ns = microtime(\false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1000000000.0 * (float) $ns; + if ($asNum) { + $ns += $s * 1000000000.0; + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + return [$s, (int) $ns]; + } +} diff --git a/vendor/symfony/polyfill-php73/README.md b/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 00000000..032fafbd --- /dev/null +++ b/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 00000000..374187cf --- /dev/null +++ b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (\PHP_VERSION_ID < 70300) { + class JsonException extends \Exception + { + } + \class_alias('ComfinoExternal\JsonException', 'JsonException', \false); +} diff --git a/vendor/symfony/polyfill-php73/bootstrap.php b/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 00000000..35ab6140 --- /dev/null +++ b/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use ComfinoExternal\Symfony\Polyfill\Php73 as p; +if (\PHP_VERSION_ID >= 70300) { + return; +} +if (!\function_exists('is_countable') && !\function_exists('ComfinoExternal\is_countable')) { + function is_countable($value) + { + return \is_array($value) || $value instanceof \Countable || $value instanceof \ResourceBundle || $value instanceof \SimpleXmlElement; + } +} +if (!\function_exists('hrtime') && !\function_exists('ComfinoExternal\hrtime')) { + require_once __DIR__ . '/Php73.php'; + p\Php73::$startAt = (int) \microtime(\true); + function hrtime($as_number = \false) + { + return p\Php73::hrtime($as_number); + } +} +if (!\function_exists('array_key_first') && !\function_exists('ComfinoExternal\array_key_first')) { + function array_key_first(array $array) + { + foreach ($array as $key => $value) { + return $key; + } + } +} +if (!\function_exists('array_key_last') && !\function_exists('ComfinoExternal\array_key_last')) { + function array_key_last(array $array) + { + return \key(\array_slice($array, -1, 1, \true)); + } +} diff --git a/vendor/symfony/polyfill-php73/composer.json b/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 00000000..3d47d154 --- /dev/null +++ b/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 00000000..6dc8213b --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Polyfill\Php80; + +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + public static function get_debug_type($value): string + { + switch (\true) { + case null === $value: + return 'null'; + case \is_bool($value): + return 'bool'; + case \is_string($value): + return 'string'; + case \is_array($value): + return 'array'; + case \is_int($value): + return 'int'; + case \is_float($value): + return 'float'; + case \is_object($value): + break; + case $value instanceof \__PHP_Incomplete_Class: + return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + if ('Unknown' === $type) { + $type = 'closed'; + } + return "resource ({$type})"; + } + $class = \get_class($value); + if (\false === strpos($class, '@')) { + return $class; + } + return ((get_parent_class($class) ?: key(class_implements($class))) ?: 'class') . '@anonymous'; + } + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + return (int) $res; + } + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || \false !== strpos($haystack, $needle); + } + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return \true; + } + if ('' === $haystack) { + return \false; + } + $needleLength = \strlen($needle); + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 00000000..a952a860 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Polyfill\Php80; + +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + /** + * @var string + */ + public $text; + /** + * @var int + */ + public $line; + /** + * @var int + */ + public $pos; + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + return $name; + } + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], \true)) { + return \true; + } + } + return \false; + } + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], \true); + } + public function __toString(): string + { + return (string) $this->text; + } + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 00000000..3816c559 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 00000000..2a7b5787 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + public $flags; + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} + +\class_alias('ComfinoExternal\Attribute', 'Attribute', \false); diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 00000000..01d08936 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (\PHP_VERSION_ID < 80000 && \extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } + \class_alias('ComfinoExternal\PhpToken', 'PhpToken', \false); +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 00000000..0e362cda --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } + \class_alias('ComfinoExternal\Stringable', 'Stringable', \false); +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 00000000..042c3314 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends \Error + { + } + \class_alias('ComfinoExternal\UnhandledMatchError', 'UnhandledMatchError', \false); +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 00000000..102d0d6a --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (\PHP_VERSION_ID < 80000) { + class ValueError extends \Error + { + } + \class_alias('ComfinoExternal\ValueError', 'ValueError', \false); +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 00000000..1a722455 --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use ComfinoExternal\Symfony\Polyfill\Php80 as p; +if (\PHP_VERSION_ID >= 80000) { + return; +} +if (!\defined('FILTER_VALIDATE_BOOL') && \defined('FILTER_VALIDATE_BOOLEAN')) { + \define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} +if (!\function_exists('fdiv') && !\function_exists('ComfinoExternal\fdiv')) { + function fdiv(float $num1, float $num2): float + { + return p\Php80::fdiv($num1, $num2); + } +} +if (!\function_exists('preg_last_error_msg') && !\function_exists('ComfinoExternal\preg_last_error_msg')) { + function preg_last_error_msg(): string + { + return p\Php80::preg_last_error_msg(); + } +} +if (!\function_exists('str_contains') && !\function_exists('ComfinoExternal\str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool + { + return p\Php80::str_contains($haystack ?? '', $needle ?? ''); + } +} +if (!\function_exists('str_starts_with') && !\function_exists('ComfinoExternal\str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool + { + return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); + } +} +if (!\function_exists('str_ends_with') && !\function_exists('ComfinoExternal\str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool + { + return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); + } +} +if (!\function_exists('get_debug_type') && !\function_exists('ComfinoExternal\get_debug_type')) { + function get_debug_type($value): string + { + return p\Php80::get_debug_type($value); + } +} +if (!\function_exists('get_resource_id') && !\function_exists('ComfinoExternal\get_resource_id')) { + function get_resource_id($resource): int + { + return p\Php80::get_resource_id($resource); + } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 00000000..46ccde20 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "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" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/yaml/Command/LintCommand.php b/vendor/symfony/yaml/Command/LintCommand.php new file mode 100644 index 00000000..c5df6b01 --- /dev/null +++ b/vendor/symfony/yaml/Command/LintCommand.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Command; + +use ComfinoExternal\Symfony\Component\Console\Command\Command; +use ComfinoExternal\Symfony\Component\Console\Exception\InvalidArgumentException; +use ComfinoExternal\Symfony\Component\Console\Exception\RuntimeException; +use ComfinoExternal\Symfony\Component\Console\Input\InputArgument; +use ComfinoExternal\Symfony\Component\Console\Input\InputInterface; +use ComfinoExternal\Symfony\Component\Console\Input\InputOption; +use ComfinoExternal\Symfony\Component\Console\Output\OutputInterface; +use ComfinoExternal\Symfony\Component\Console\Style\SymfonyStyle; +use ComfinoExternal\Symfony\Component\Yaml\Exception\ParseException; +use ComfinoExternal\Symfony\Component\Yaml\Parser; +use ComfinoExternal\Symfony\Component\Yaml\Yaml; + +class LintCommand extends Command +{ + protected static $defaultName = 'lint:yaml'; + private $parser; + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + } + + protected function configure() + { + $this->setDescription('Lint a file and outputs encountered errors')->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF +); + } + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format'); + $this->displayCorrectFiles = $output->isVerbose(); + $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + if (0 === ftell(\STDIN)) { + @trigger_error('Piping content from STDIN to the "lint:yaml" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED); + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + return $this->display($io, $filesInfo); + } + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : \false; + }); + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => \false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + return ['file' => $file, 'valid' => \true]; + } + private function display(SymfonyStyle $io, array $files): int + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + private function displayTxt(SymfonyStyle $io, array $filesInfo): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = \false; + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK' . ($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR ' . ($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + if (\false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = \true; + } + } + } + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + return min($erroredFiles, 1); + } + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + if (isset($v['message']) && \false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + return min($errors, 1); + } + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + return; + } + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + yield $file; + } + } + private function getParser(): Parser + { + if (!$this->parser) { + $this->parser = new Parser(); + } + return $this->parser; + } + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY); + }; + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + return $default($directory); + } + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + return $default($fileOrDirectory); + } +} diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php new file mode 100644 index 00000000..0d42226c --- /dev/null +++ b/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +use ComfinoExternal\Symfony\Component\Yaml\Tag\TaggedValue; + +class Dumper +{ + /** + * @var int + */ + protected $indentation; + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + $this->indentation = $indentation; + } + /** + * @param mixed $input + * @param int $inline + * @param int $indent + * @param int $flags + * @return string + */ + public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = \true; + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + if ($inline <= 0 || !\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap || empty($input)) { + $output .= $prefix . Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && \false !== strpos($value, "\n") && \false === strpos($value, "\r")) { + $blockIndentationIndicator = ' ' === substr($value, 0, 1) ? (string) $this->indentation : ''; + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + continue; + } + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', $value->getTag()); + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && \false !== strpos($value->getValue(), "\n") && \false === strpos($value->getValue(), "\r\n")) { + $blockIndentationIndicator = ' ' === substr($value->getValue(), 0, 1) ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + continue; + } + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' ' . $this->dump($value->getValue(), $inline - 1, 0, $flags) . "\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + continue; + } + $dumpObjectAsInlineMap = \true; + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + $output .= sprintf('%s%s%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)) . ($willBeInlined ? "\n" : ''); + } + } + return $output; + } +} diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php new file mode 100644 index 00000000..00c45654 --- /dev/null +++ b/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +class Escaper +{ + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]||…| |
|
"; + + private const ESCAPEES = ['\\', '\\\\', '\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\t", "\n", "\v", "\f", "\r", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "", "…", " ", "
", "
"]; + private const ESCAPED = ['\\\\', '\"', '\\\\', '\"', '\0', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\e', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f', '\N', '\_', '\L', '\P']; + /** + * @param string $value + * @return bool + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/' . self::REGEX_CHARACTER_TO_ESCAPE . '/u', $value); + } + /** + * @param string $value + * @return string + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + /** + * @param string $value + * @return bool + */ + public static function requiresSingleQuoting(string $value): bool + { + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return \true; + } + + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); + } + /** + * @param string $value + * @return string + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 00000000..e0010339 --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Exception; + +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 00000000..7d40fe86 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 00000000..b33bb585 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Exception; + +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + /** + * @param string $message + * @param int $parsedLine + * @param string|null $snippet + * @param string|null $parsedFile + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + $this->updateRepr(); + parent::__construct($this->message, 0, $previous); + } + /** + * @return string + */ + public function getSnippet() + { + return $this->snippet; + } + /** + * @param string $snippet + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + $this->updateRepr(); + } + /** + * @return string + */ + public function getParsedFile() + { + return $this->parsedFile; + } + /** + * @param string $parsedFile + */ + public function setParsedFile($parsedFile) + { + $this->parsedFile = $parsedFile; + $this->updateRepr(); + } + /** + * @return int + */ + public function getParsedLine() + { + return $this->parsedLine; + } + /** + * @param int $parsedLine + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + $this->updateRepr(); + } + private function updateRepr() + { + $this->message = $this->rawMessage; + $dot = \false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = \true; + } + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 00000000..8b84cf18 --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php new file mode 100644 index 00000000..4279a06a --- /dev/null +++ b/vendor/symfony/yaml/Inline.php @@ -0,0 +1,631 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +use ComfinoExternal\Symfony\Component\Yaml\Exception\DumpException; +use ComfinoExternal\Symfony\Component\Yaml\Exception\ParseException; +use ComfinoExternal\Symfony\Component\Yaml\Tag\TaggedValue; + +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + public static $parsedLineNumber = -1; + public static $parsedFilename; + private static $exceptionOnInvalidType = \false; + private static $objectSupport = \false; + private static $objectForMap = \false; + private static $constantSupport = \false; + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + /** + * @param string $value + * @param int $flags + * @param array $references + * @return mixed + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array &$references = []) + { + self::initialize($flags); + $value = trim($value); + if ('' === $value) { + return ''; + } + if (2 & (int) \ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + try { + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references); + } + + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + return $result; + } finally { + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + } + } + /** + * @param mixed $value + * @param int $flags + * @return string + * @throws DumpException + */ + public static function dump($value, int $flags = 0): string + { + switch (\true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', \get_class($value), $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!' . $value->getTag() . ' ' . self::dump($value->getValue(), $flags); + } + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object ' . self::dump(serialize($value)); + } + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + return sprintf('{ %s }', implode(', ', $output)); + } + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case \true === $value: + return 'true'; + case \false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && \false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (\false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + $repr = '!!float ' . $repr; + } + } else { + $repr = \is_string($value) ? "'{$value}'" : (string) $value; + } + if (\false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary ' . base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + /** + * @param array|\ArrayObject|\stdClass $value + * @return bool + */ + public static function isHash($value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return \true; + } + $expectedKey = 0; + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return \true; + } + } + return \false; + } + /** + * @param array $value + * @param int $flags + * @return string + */ + private static function dumpArray(array $value, int $flags): string + { + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + return sprintf('[%s]', implode(', ', $output)); + } + + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + return sprintf('{ %s }', implode(', ', $output)); + } + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + return 'null'; + } + /** + * @return mixed + * @throws ParseException + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = \true, array &$references = [], bool &$isQuoted = null) + { + if (\in_array($scalar[$i], ['"', "'"])) { + $isQuoted = \true; + $output = self::parseQuotedScalar($scalar, $i); + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + $isQuoted = \false; + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)(' . implode('|', $delimiters) . ')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + return $output; + } + /** + * @throws ParseException + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/' . self::REGEX_QUOTED_STRING . '/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + $output = substr($match[0], 1, \strlen($match[0]) - 2); + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + $i += \strlen($match[0]); + return $output; + } + /** + * @throws ParseException + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + continue; + } + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + if (\is_string($value) && !$isQuoted && \false !== strpos($value, ': ')) { + try { + $pos = 0; + $value = self::parseMapping('{' . $value . '}', $flags, $pos, $references); + } catch (\InvalidArgumentException $e) { + } + } + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + --$i; + } + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + $output[] = $value; + ++$i; + } + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + /** + * @return array|\stdClass + * @throws ParseException + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []) + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = \false; + + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + return $output; + } + + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], \true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, \false); + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + if ('!php/const' === $key) { + $key .= ' ' . self::parseScalar($mapping, $flags, [':'], $i, \false); + $key = self::evaluateScalar($key, $flags); + } + if (\false === $i = strpos($mapping, ':', $i)) { + break; + } + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], \true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + if ('<<' === $key) { + $allowOverwrite = \true; + } + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + continue; + } + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + + $value = self::parseSequence($mapping, $flags, $i, $references); + + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + + $value = self::parseMapping($mapping, $flags, $i, $references); + + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + continue 2; + } + } + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + /** + * @return mixed + * @throws ParseException + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null) + { + $isQuotedString = \false; + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + if (0 === strpos($scalar, '*')) { + if (\false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + if (\false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + return $references[$value]; + } + switch (\true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return \true; + case 'false' === $scalarLower: + return \false; + case '!' === $scalar[0]: + switch (\true) { + case 0 === strpos($scalar, '!!str '): + $s = (string) substr($scalar, 6); + if (\in_array($s[0] ?? '', ['"', "'"], \true)) { + $isQuotedString = \true; + $s = self::parseQuotedScalar($s); + } + return $s; + case 0 === strpos($scalar, '! '): + return substr($scalar, 2); + case 0 === strpos($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + return \false; + } + return unserialize(self::parseScalar(substr($scalar, 12))); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + return null; + case 0 === strpos($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + return ''; + } + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, \false))) { + return \constant($const); + } + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + return null; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case 0 === strpos($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + default: + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + } + + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + switch (\true) { + case ctype_digit($scalar): + if (preg_match('/^0[0-7]+$/', $scalar)) { + return octdec($scalar); + } + $cast = (int) $scalar; + return $scalar === (string) $cast ? $cast : $scalar; + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + if (preg_match('/^-0[0-7]+$/', $scalar)) { + return -octdec(substr($scalar, 1)); + } + $cast = (int) $scalar; + return $scalar === (string) $cast ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + return '0x' === $scalar[0] . $scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + + $time = new \DateTime($scalar, new \DateTimeZone('UTC')); + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + try { + if (\false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError $e) { + } + return $time->format('U'); + } + } + return (string) $scalar; + } + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], \true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], \true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { + return null; + } + $i = $nextOffset; + + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + if (0 !== \strlen($parsedBinaryData) % 4) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + return base64_decode($parsedBinaryData, \true); + } + private static function isBinaryString(string $value) + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + /** + * @return string + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + \$~x +EOF; + } + + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/yaml/LICENSE new file mode 100644 index 00000000..88bf75bb --- /dev/null +++ b/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php new file mode 100644 index 00000000..f0d01e32 --- /dev/null +++ b/vendor/symfony/yaml/Parser.php @@ -0,0 +1,997 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +use ComfinoExternal\Symfony\Component\Yaml\Exception\ParseException; +use ComfinoExternal\Symfony\Component\Yaml\Tag\TaggedValue; + +class Parser +{ + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; + private $filename; + private $offset = 0; + private $totalNumberOfLines; + private $lines = []; + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = []; + private $skippedLineNumbers = []; + private $locallySkippedLineNumbers = []; + private $refsBeingParsed = []; + /** + * @param string $filename + * @param int $flags + * @return mixed + * @throws ParseException + */ + public function parseFile(string $filename, int $flags = 0) + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + $this->filename = $filename; + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + /** + * @param string $value + * @param int $flags + * @return mixed + * @throws ParseException + */ + public function parse(string $value, int $flags = 0) + { + if (\false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + $this->refs = []; + $mbEncoding = null; + if (2 & (int) \ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + try { + $data = $this->doParse($value, $flags); + } finally { + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + return $data; + } + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->locallySkippedLineNumbers = []; + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = \count($this->lines); + } + if (!$this->moveToNextLine()) { + return null; + } + $data = []; + $context = null; + $allowOverwrite = \false; + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, \false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + $isRef = $mergeNode = \false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (isset($values['value']) && 0 === strpos(ltrim($values['value'], ' '), '-')) { + $currentLineNumber = $this->getRealCurrentLineNb(); + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n" . $this->getNextEmbedBlock($sequenceIndentation, \true); + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, \true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue($subTag, $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, \true), $flags)); + } else if (isset($values['leadspaces']) && ('!' === $values['value'][0] || self::preg_match('#^(?P' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches))) { + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n" . $this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif (self::preg_match('#^(?P(?:![^\s]++\s++)?(?:' . Inline::REGEX_QUOTED_STRING . '|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) && (\false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string') . ' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (\is_float($key)) { + $key = (string) $key; + } + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = \true; + $allowOverwrite = \true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (\false !== $pos = array_search($refName, $this->refsBeingParsed, \true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $refValue = $this->refs[$refName]; + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $data += $refValue; + + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + if (isset($parsed[0])) { + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + $data += $parsedItem; + + } + } else { + $data += $parsed; + + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + $subTag = null; + if ($mergeNode) { + } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || null !== ($subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + } else { + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + if ($deprecatedUsage = isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + return $value; + } + + if (0 === $this->currentLineNb) { + $previousLineWasNewline = \false; + $previousLineWasTerminatedWithBackslash = \false; + $value = ''; + foreach ($this->lines as $line) { + if ('' !== ltrim($line) && '#' === ltrim($line)[0]) { + continue; + } + + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + if (\false !== strpos($line, ': ')) { + @trigger_error('Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0.', \E_USER_DEPRECATED); + } + if ('' === trim($line)) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + if ('' !== trim($line) && '\\' === substr($line, -1)) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== trim($line)) { + $value .= trim($line); + } + if ('' === trim($line)) { + $previousLineWasNewline = \true; + $previousLineWasTerminatedWithBackslash = \false; + } elseif ('\\' === substr($line, -1)) { + $previousLineWasNewline = \false; + $previousLineWasTerminatedWithBackslash = \true; + } else { + $previousLineWasNewline = \false; + $previousLineWasTerminatedWithBackslash = \false; + } + } + try { + return Inline::parse(trim($value)); + } catch (ParseException $e) { + } + } + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) { + $object = new \stdClass(); + foreach ($data as $key => $value) { + $object->{$key} = $value; + } + $data = $object; + } + return empty($data) ? null : $data; + } + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + $skippedLineNumbers[] = $lineNumber; + } + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs =& $this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + return $parser->doParse($yaml, $flags); + } + /** + * @return int + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + ++$realCurrentLineNumber; + } + return $realCurrentLineNumber; + } + /** + * @return int + */ + private function getCurrentLineIndentation(): int + { + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + /** + * @param int|null $indentation + * @param bool $inSequence + * @return string + * @throws ParseException + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = \false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + if (!$this->moveToNextLine()) { + return ''; + } + if (null === $indentation) { + $newIndent = null; + $movements = 0; + do { + $EOF = \false; + + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + $data = []; + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + return ''; + } + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + $this->moveToPreviousLine(); + return ''; + } + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + $indent = $this->getCurrentLineIndentation(); + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + return implode("\n", $data); + } + private function hasMoreLines(): bool + { + return \count($this->lines) - 1 > $this->currentLineNb; + } + + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= \count($this->lines) - 1) { + return \false; + } + $this->currentLine = $this->lines[++$this->currentLineNb]; + return \true; + } + + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return \false; + } + $this->currentLine = $this->lines[--$this->currentLineNb]; + return \true; + } + /** + * @param string $value + * @param int $flags + * @param string $context + * @return mixed + * @throws ParseException + */ + private function parseValue(string $value, int $flags, string $context) + { + if (0 === strpos($value, '*')) { + if (\false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + if (!\array_key_exists($value, $this->refs)) { + if (\false !== $pos = array_search($value, $this->refsBeingParsed, \true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + return $this->refs[$value]; + } + if (\in_array($value[0], ['!', '|', '>'], \true) && self::preg_match('/^(?:' . self::TAG_PATTERN . ' +)?' . self::BLOCK_SCALAR_HEADER_PATTERN . '$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + return new TaggedValue(substr($matches['tag'], 1), $data); + } + return $data; + } + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + return $parsedValue; + default: + $lines = []; + while ($this->moveToNextLine()) { + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + break; + } + $lines[] = trim($this->currentLine); + } + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = \false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = \true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = \false; + } else { + $value .= ' ' . $lines[$i]; + $previousLineBlank = \false; + } + } + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + $parsedValue = Inline::parse($value, $flags, $this->refs); + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && \false !== strpos($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + throw $e; + } + } + /** + * @param string $style + * @param string $chomping + * @param int $indentation + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + while ($notEOF && $isCurrentLineBlank) { + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + while ($notEOF && ($isCurrentLineBlank || self::preg_match($pattern, $this->currentLine, $matches))) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + if ('>' === $style) { + $text = ''; + $previousLineIndented = \false; + $previousLineBlank = \false; + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = \false; + $previousLineBlank = \true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n" . $blockLines[$i]; + $previousLineIndented = \true; + $previousLineBlank = \false; + } elseif ($previousLineIndented) { + $text .= "\n" . $blockLines[$i]; + $previousLineIndented = \false; + $previousLineBlank = \false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = \false; + $previousLineBlank = \false; + } else { + $text .= ' ' . $blockLines[$i]; + $previousLineIndented = \false; + $previousLineBlank = \false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + return $text; + } + /** + * @return bool + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + do { + $EOF = !$this->moveToNextLine(); + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + if ($EOF) { + return \false; + } + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + return $ret; + } + /** + * @return bool + */ + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + /** + * @return bool + */ + private function isCurrentLineBlank(): bool + { + return '' == trim($this->currentLine, ' '); + } + /** + * @return bool + */ + private function isCurrentLineComment(): bool + { + $ltrimmedLine = ltrim($this->currentLine, ' '); + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + private function isCurrentLineLastLineInDocument(): bool + { + return $this->offset + $this->currentLineNb >= $this->totalNumberOfLines - 1; + } + /** + * @param string $value + * @return string + */ + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + return $value; + } + /** + * @return bool + */ + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + do { + $EOF = !$this->moveToNextLine(); + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + if ($EOF) { + return \false; + } + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + return $ret; + } + /** + * @return bool + */ + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); + } + /** + * @throws ParseException + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (\false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case \PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case \PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case \PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case \PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Error.'; + } + throw new ParseException($error); + } + return $ret; + } + + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + return $value; + } + private function getLineTag(string $value, int $flags, bool $nextLineCheck = \true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^' . self::TAG_PATTERN . ' *( +#.*)?$/', $value, $matches)) { + return null; + } + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + $tag = substr($matches['tag'], 1); + + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + $previousLineWasNewline = \true; + $previousLineWasTerminatedWithBackslash = \false; + $lineNumber = 0; + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\' . $this->currentLine[$cursor]; + } + break; + case $quotation: + ++$cursor; + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + return $value . $quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = \true; + $previousLineWasTerminatedWithBackslash = \false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = \false; + $previousLineWasTerminatedWithBackslash = \true; + } else { + $previousLineWasNewline = \false; + $previousLineWasTerminatedWithBackslash = \false; + } + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + throw new ParseException('Malformed inline YAML string.'); + } + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + return substr($this->currentLine, $offset, $cursor - $offset); + } + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + do { + $this->consumeWhitespaces($cursor); + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + throw new ParseException('Malformed inline YAML string.'); + } + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + return 0 < $whitespacesConsumed; + } +} diff --git a/vendor/symfony/yaml/README.md b/vendor/symfony/yaml/README.md new file mode 100644 index 00000000..ac25024b --- /dev/null +++ b/vendor/symfony/yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/yaml/Tag/TaggedValue.php b/vendor/symfony/yaml/Tag/TaggedValue.php new file mode 100644 index 00000000..22b6bb3c --- /dev/null +++ b/vendor/symfony/yaml/Tag/TaggedValue.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml\Tag; + +final class TaggedValue +{ + private $tag; + private $value; + public function __construct(string $tag, $value) + { + $this->tag = $tag; + $this->value = $value; + } + public function getTag(): string + { + return $this->tag; + } + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/symfony/yaml/Unescaper.php b/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 00000000..b881ad40 --- /dev/null +++ b/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +use ComfinoExternal\Symfony\Component\Yaml\Exception\ParseException; + +class Unescaper +{ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + /** + * @param string $value + * @return string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + /** + * @param string $value + * @return string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + return preg_replace_callback('/' . self::REGEX_ESCAPED_CHARACTER . '/u', $callback, $value); + } + /** + * @param string $value + * @return string + */ + private function unescapeCharacter(string $value): string + { + switch ($value[1]) { + case '0': + return "\x00"; + case 'a': + return "\x07"; + case 'b': + return "\x08"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\v"; + case 'f': + return "\f"; + case 'r': + return "\r"; + case 'e': + return "\x1b"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + + return "…"; + case '_': + + return " "; + case 'L': + + return "
"; + case 'P': + + return "
"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + default: + throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); + } + } + + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xc0 | $c >> 6) . \chr(0x80 | $c & 0x3f); + } + if (0x10000 > $c) { + return \chr(0xe0 | $c >> 12) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f); + } + return \chr(0xf0 | $c >> 18) . \chr(0x80 | $c >> 12 & 0x3f) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f); + } +} diff --git a/vendor/symfony/yaml/Yaml.php b/vendor/symfony/yaml/Yaml.php new file mode 100644 index 00000000..b79f779f --- /dev/null +++ b/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace ComfinoExternal\Symfony\Component\Yaml; + +use ComfinoExternal\Symfony\Component\Yaml\Exception\ParseException; + +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + /** + * @param string $filename + * @param int $flags + * @return mixed + * @throws ParseException + */ + public static function parseFile(string $filename, int $flags = 0) + { + $yaml = new Parser(); + return $yaml->parseFile($filename, $flags); + } + /** + * @param string $input + * @param int $flags + * @return mixed + * @throws ParseException + */ + public static function parse(string $input, int $flags = 0) + { + $yaml = new Parser(); + return $yaml->parse($input, $flags); + } + /** + * @param mixed $input + * @param int $inline + * @param int $indent + * @param int $flags + * @return string + */ + public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json new file mode 100644 index 00000000..10120348 --- /dev/null +++ b/vendor/symfony/yaml/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Loads and dumps YAML files", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/views/templates/admin/_configure/configuration-repair.tpl b/views/templates/admin/_configure/configuration-repair.tpl deleted file mode 100644 index 1c7bce79..00000000 --- a/views/templates/admin/_configure/configuration-repair.tpl +++ /dev/null @@ -1,129 +0,0 @@ -{** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Open Software License (OSL 3.0) - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/OSL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade PrestaShop to newer - * versions in the future. If you wish to customize PrestaShop for your - * needs please refer to https://devdocs.prestashop.com/ for more information. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) - *} -{if $validation.valid} - -{else} - {assign var=missing_count value=$validation.missing_options|count} - -{/if} -{if !$validation.valid} -
- -
-{/if} -
- diff --git a/controllers/front/configurationrepair.php b/views/templates/admin/_configure/debug-log.tpl similarity index 69% rename from controllers/front/configurationrepair.php rename to views/templates/admin/_configure/debug-log.tpl index 43d55ecd..de316731 100644 --- a/controllers/front/configurationrepair.php +++ b/views/templates/admin/_configure/debug-log.tpl @@ -1,5 +1,4 @@ - * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) - */ - -use Comfino\Api\ApiService; -use Comfino\ErrorLogger; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class ComfinoConfigurationRepairModuleFrontController extends ModuleFrontController -{ - public function postProcess(): void - { - ErrorLogger::init(); - - parent::postProcess(); - - exit(ApiService::processRequest('configurationRepair')); - } -} + *} + +
+
+ +
+
\ No newline at end of file diff --git a/views/templates/admin/_configure/error-log.tpl b/views/templates/admin/_configure/error-log.tpl new file mode 100644 index 00000000..06926cbf --- /dev/null +++ b/views/templates/admin/_configure/error-log.tpl @@ -0,0 +1,32 @@ +{** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + *} + +
+
+ +
+
\ No newline at end of file diff --git a/views/templates/admin/_configure/installation-logs.tpl b/views/templates/admin/_configure/installation-logs.tpl new file mode 100644 index 00000000..e5b80a5a --- /dev/null +++ b/views/templates/admin/_configure/installation-logs.tpl @@ -0,0 +1,80 @@ +{** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + *} + +
+
+ {l s="Installation log" mod="comfino"} + {if $install_log_content} + + {else} +
{l s="No installation log available." mod="comfino"}
+ {/if} +
+
+ {l s="Upgrade log" mod="comfino"} + {if $upgrade_log_content} + + {else} +
{l s="No upgrade log available." mod="comfino"}
+ {/if} +
+
+ {l s="Uninstallation log" mod="comfino"} + {if $uninstall_log_content} + + {else} +
{l s="No uninstallation log available." mod="comfino"}
+ {/if} +
+
\ No newline at end of file diff --git a/views/templates/admin/_configure/module-reset.tpl b/views/templates/admin/_configure/module-reset.tpl new file mode 100644 index 00000000..4e9b3d31 --- /dev/null +++ b/views/templates/admin/_configure/module-reset.tpl @@ -0,0 +1,42 @@ +{** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to https://devdocs.prestashop.com/ for more information. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + *} + +
+
+ +
+