diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..523fc6fce --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Exclude vendored third-party code from git whitespace / diff-check hygiene +# so that upstream formatting choices (trailing whitespace, tabs, mixed EOL) +# do not block `git diff --check` or review tooling. +vendor/** -whitespace diff --git a/classes/Components/WooCommerce/ClientWrapper.php b/classes/Components/WooCommerce/ClientWrapper.php new file mode 100644 index 000000000..3bec69a13 --- /dev/null +++ b/classes/Components/WooCommerce/ClientWrapper.php @@ -0,0 +1,256 @@ + lowercase-keyed header map */ + private $lowercaseHeaders; + + public function __construct(\Automattic\WooCommerce\HttpClient\Response $response) + { + $this->response = $response; + + // Build a normalised, lowercase header map once. + $this->lowercaseHeaders = []; + foreach ($response->getHeaders() as $key => $value) { + $this->lowercaseHeaders[strtolower($key)] = $value; + } + } + + /** + * Case-insensitive single-header lookup. + * + * @param string $name Header name (e.g. 'x-wp-total', 'X-WP-TotalPages') + * @return string|null Header value, or null when not present + */ + public function getHeader($name) + { + $key = strtolower($name); + return isset($this->lowercaseHeaders[$key]) ? $this->lowercaseHeaders[$key] : null; + } + + /** + * All response headers (lowercase keys). + * + * @return array + */ + public function getHeaders() + { + return $this->lowercaseHeaders; + } + + /** @return int HTTP status code */ + public function getCode() + { + return $this->response->getCode(); + } + + /** @return string Response body */ + public function getBody() + { + return $this->response->getBody(); + } +} + +/** + * Drop-in replacement for the old inline WCClient. + * + * Wraps the upstream Automattic\WooCommerce\Client and adds: + * - getLastResponse(): ResponseWrapper + * - ssl_ignore support via verify_ssl option + * - PSR-3-style logger integration: HTTP >= 400 is logged as warning, + * HttpClientException is logged as error (and rethrown) + */ +class ClientWrapper +{ + /** @var UpstreamClient */ + private $client; + + /** @var ResponseWrapper|null */ + private $lastResponse; + + /** @var \Psr\Log\LoggerInterface|null */ + private $logger; + + /** + * @param string $url WooCommerce store URL + * @param string $consumerKey API consumer key + * @param string $consumerSecret API consumer secret + * @param array $options Upstream SDK options (version, timeout, …) + * @param mixed $logger PSR-3-compatible logger; null disables logging + * @param bool|mixed $sslIgnore When truthy, disables SSL certificate verification + */ + public function __construct($url, $consumerKey, $consumerSecret, $options = [], $logger = null, $sslIgnore = false) + { + if ($sslIgnore) { + $options['verify_ssl'] = false; + } + + $this->client = new UpstreamClient($url, $consumerKey, $consumerSecret, $options); + $this->logger = $logger; + } + + /** + * GET request. + * + * @param string $endpoint API endpoint + * @param array $parameters Query parameters + * @return \stdClass|array + * @throws HttpClientException + */ + public function get($endpoint, $parameters = []) + { + return $this->dispatch('GET', $endpoint, function () use ($endpoint, $parameters) { + return $this->client->get($endpoint, $parameters); + }); + } + + /** + * POST request. + * + * @param string $endpoint API endpoint + * @param array $data Request body + * @return \stdClass + * @throws HttpClientException + */ + public function post($endpoint, $data) + { + return $this->dispatch('POST', $endpoint, function () use ($endpoint, $data) { + return $this->client->post($endpoint, $data); + }); + } + + /** + * PUT request. + * + * @param string $endpoint API endpoint + * @param array $data Request body + * @return \stdClass + * @throws HttpClientException + */ + public function put($endpoint, $data) + { + return $this->dispatch('PUT', $endpoint, function () use ($endpoint, $data) { + return $this->client->put($endpoint, $data); + }); + } + + /** + * DELETE request. + * + * @param string $endpoint API endpoint + * @param array $parameters Query parameters + * @return \stdClass + * @throws HttpClientException + */ + public function delete($endpoint, $parameters = []) + { + return $this->dispatch('DELETE', $endpoint, function () use ($endpoint, $parameters) { + return $this->client->delete($endpoint, $parameters); + }); + } + + /** + * OPTIONS request. + * + * @param string $endpoint API endpoint + * @return \stdClass + * @throws HttpClientException + */ + public function options($endpoint) + { + return $this->dispatch('OPTIONS', $endpoint, function () use ($endpoint) { + return $this->client->options($endpoint); + }); + } + + /** + * Returns the response from the most recent API call, or null when no + * request has been made yet. + * + * @return ResponseWrapper|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + /** + * Run an upstream call with unified response capture and error logging. + * Exceptions are logged (when a logger is available) and rethrown so + * existing callsites keep their try/catch contract. + * + * @param string $method HTTP verb for log context + * @param string $endpoint API endpoint for log context + * @param callable $call Zero-argument closure performing the upstream call + * @return mixed Upstream result + * @throws HttpClientException + */ + private function dispatch($method, $endpoint, callable $call) + { + try { + $result = $call(); + } catch (HttpClientException $e) { + $this->captureResponse($method, $endpoint); + if ($this->logger !== null) { + $this->logger->error( + sprintf('WooCommerce %s %s failed: %s', $method, $endpoint, $e->getMessage()), + ['code' => $e->getCode()] + ); + } + throw $e; + } + $this->captureResponse($method, $endpoint); + return $result; + } + + /** + * Snapshot the upstream response after each request. HTTP >= 400 is + * logged as warning with a truncated body excerpt to keep logs small. + * + * @param string $method HTTP verb for log context + * @param string $endpoint API endpoint for log context + */ + private function captureResponse($method, $endpoint) + { + $upstreamResponse = $this->client->http->getResponse(); + if ($upstreamResponse === null) { + return; + } + $this->lastResponse = new ResponseWrapper($upstreamResponse); + + if ($this->logger !== null) { + $code = $upstreamResponse->getCode(); + if ($code >= 400) { + $this->logger->warning( + sprintf('WooCommerce %s %s returned HTTP %d', $method, $endpoint, $code), + ['body' => substr((string) $upstreamResponse->getBody(), 0, 500)] + ); + } + } + } +} diff --git a/composer.json b/composer.json index 06e2a500e..e4cb4f323 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "swiss-payment-slip/swiss-payment-slip": "0.13.0 as 0.11.1", "swiss-payment-slip/swiss-payment-slip-fpdf": "0.6.0", "tecnickcom/tcpdf": "6.3.5", - "y0lk/oauth1-etsy": "1.1.0" + "y0lk/oauth1-etsy": "1.1.0", + "automattic/woocommerce": "^3.1" }, "replace": { "itbz/fpdf": "*" @@ -41,4 +42,4 @@ }, "optimize-autoloader": true } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 85d34929c..e42add22d 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": "229fd9b44bc0380415380fe519e53dfb", + "content-hash": "0e587d2db823187eb218c8737c439bff", "packages": [ { "name": "aura/sqlquery", @@ -74,6 +74,113 @@ }, "time": "2016-10-03T20:34:56+00:00" }, + { + "name": "automattic/woocommerce", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/woocommerce/wc-api-php.git", + "reference": "e378120df655b7dacbeff4756e23b41b66fe9688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/woocommerce/wc-api-php/zipball/e378120df655b7dacbeff4756e23b41b66fe9688", + "reference": "e378120df655b7dacbeff4756e23b41b66fe9688", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">= 7.4.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.5.0", + "phpunit/phpunit": "^9.5.0", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Automattic\\WooCommerce\\": [ + "src/WooCommerce" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Claudio Sanches", + "email": "claudio.sanches@automattic.com" + } + ], + "description": "A PHP wrapper for the WooCommerce REST API", + "keywords": [ + "api", + "woocommerce" + ], + "support": { + "issues": "https://github.com/woocommerce/wc-api-php/issues", + "source": "https://github.com/woocommerce/wc-api-php/tree/3.1.1" + }, + "time": "2026-01-30T16:26:03+00:00" + }, + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, { "name": "aws/aws-sdk-php", "version": "3.175.2", diff --git a/docs/plans/woocommerce-batch-stock-sync.md b/docs/plans/woocommerce-batch-stock-sync.md new file mode 100644 index 000000000..fc9d54e2a --- /dev/null +++ b/docs/plans/woocommerce-batch-stock-sync.md @@ -0,0 +1,126 @@ +# WooCommerce Batch Stock Sync — Plan + +Companion document to [GitHub Issue #263](https://github.com/OpenXE-org/OpenXE/issues/263) +and the `feature/woocommerce-batch-stock-sync` branch. + +--- + +## §1 Ziel + +`ImportSendListLager()` bisher: 2 HTTP-Requests pro Artikel (SKU-Lookup + PUT). +Bei 1.000 Artikeln = 2.000 Requests — langsam, rate-limit-anfällig, kein Partial-Error-Handling. + +Ziel: Nutzung der offiziellen WC REST v3 Batch-Endpoints, um den Request-Count auf +`ceil(n/100)` SKU-Lookups + `ceil(n/100)` Batch-Updates zu reduzieren (~20 statt 2.000). + +--- + +## §2 Scope + +### IN +- Refactor von `ImportSendListLager()` in `www/pages/shopimporter_woocommerce.php` +- Neue private Hilfsmethode `processBatchResponse()` in derselben Klasse +- Plan-Dokument `docs/plans/woocommerce-batch-stock-sync.md` + +### OUT +- Kein neuer `postBatch()`-Helper auf `WCClient` — `post()` reicht direkt +- Keine Änderungen an `ImportGetAuftrag*`, `getKonfig`, `parseOrder` +- Kein Retry-Mechanismus bei HTTP-Fehlern (separater Task) +- Kein DB-Schema-Change +- Kein PR-Open (nach Review durch Maintainer) + +--- + +## §3 Fix-Parameter + +| Parameter | Wert | Begründung | +|---|---|---| +| Batch-Size | 100 Items | WC REST v3 Maximum | +| SKU-Chunk-Size | 100 SKUs | Passend zu `per_page=100` | +| Retry | Keiner | Out-of-scope für diesen PR | +| SKU-CSV-Support | `?sku=a,b,c` | WC REST v3 akzeptiert kommaseparierte Liste | + +--- + +## §4 Funktions-Landkarte + +Nur `ImportSendListLager()` wird geändert. Neue private Methode `processBatchResponse()`. + +``` +ImportSendListLager() + ├── Schritt 1: Alle SKUs + Stock-Params sammeln (pendingUpdates[]) + ├── Schritt 2: Bulk-SKU-Auflösung + │ └── GET products?sku=&per_page=100 (je Chunk à 100 SKUs) + │ → skuMap[sku] = {id, parent, isvariant} + ├── Schritt 3: Gruppieren + │ ├── simpleItems[] (für products/batch) + │ └── variationItems[parent_id][] (für products/{id}/variations/batch) + └── Schritt 4: Batch-Updates senden (je Chunk à 100) + ├── POST products/batch {update: [...]} + ├── POST products/{parent}/variations/batch {update: [...]} + └── processBatchResponse() → Partial-Error-Logging, zählt Erfolge + +processBatchResponse($response, $endpoint) + ├── Iteriert response->update[] + │ ├── item->error vorhanden → logger->error + │ └── kein Fehler → successCount++ + └── Iteriert response->errors[] (WC-Fallback) +``` + +--- + +## §5 Implementierungsschritte + +1. **Vorverlagerte Datensammlung:** Statt der bisherigen per-Artikel-Schleife erst alle SKUs + und Stock-Params in `$pendingUpdates[]` sammeln. + +2. **Bulk-SKU-Auflösung:** `GET products?sku=&per_page=100` in Chunks à 100 SKUs. + Ergebnis in `$skuMap[sku]` cachen. + +3. **Gruppierung:** Simple products in `$simpleItems[]`, Variations pro Parent-ID in + `$variationItems[parent_id][]`. + +4. **Batch-POST:** `$this->client->post('products/batch', ['update' => $chunk])` für + simple products; analog für Variations. + +5. **Partial-Error-Handling:** `processBatchResponse()` liest `response->update[]` und + `response->errors[]`, loggt Fehler per Item ohne den restlichen Sync abzubrechen. + +--- + +## §6 Test-Matrix + +Testumgebung: Docker-Shop auf `192.168.0.143:8080`, WC 10.7, Consumer Keys aktiv. + +| # | Szenario | Erwartetes Ergebnis | +|---|---|---| +| T1 | 1 Artikel | 1 SKU-GET + 1 Batch-POST, Lagerbestand korrekt | +| T2 | 100 Artikel | 1 SKU-GET + 1 Batch-POST (1 Chunk) | +| T3 | 250 Artikel | 3 SKU-GETs + 3 Batch-POSTs (100+100+50) | +| T4 | 1 falsche SKU in 100er-Batch | 99 korrekt updated, 1 Error geloggt, sync läuft durch | +| T5 | Variation (parent != 0) | Variation-Batch-Endpoint genutzt, nicht products/batch | +| T6 | `ausverkauft=1` | stock_quantity=0 im Batch-Item | +| T7 | `inaktiv=1` | status='private' im Batch-Item | +| T8 | `pseudolager` gesetzt | lageranzahl aus pseudolager, nicht anzahl_lager | +| T9 | Leere Artikelliste | Rückgabe 0, keine HTTP-Requests | + +--- + +## §7 Rollout & Rückwärts-Kompatibilität + +- **Interface unverändert:** `ImportSendListLager()` behält Signatur `(): int`. +- **Keine DB-Migration** erforderlich. +- **WC-Mindestversion:** REST v3 Batch-Endpoints seit WC 3.0 (2017) — unkritisch. +- **Rollback:** Git-Revert des Feature-Branch reicht. + +--- + +## §8 Risiken + +| Risiko | Mitigation | +|---|---| +| WC akzeptiert SKU-CSV nicht (`?sku=a,b`) | Verifikation in T1–T3 gegen 192.168.0.143:8080 — falls nicht, Fallback: sequentielle Einzellookups (wie vorher) | +| Batch-Response enthält keine `update`-Keys | `processBatchResponse()` defensiv mit `?? []` abgesichert | +| Hoster-seitiger `per_page`-Cap unter 100 | Derzeit nicht abgefangen; separater Task | +| Partial-Error zählt als nicht-erfolgreich | Korrekt: `$anzahl` zählt nur Items ohne Fehler | +| Variations mit unbekanntem Parent | Variation-Batch schlägt fehl → WCHttpClientException propagiert nach oben (bestehende Semantik) | diff --git a/docs/plans/woocommerce-pagination-fix.md b/docs/plans/woocommerce-pagination-fix.md new file mode 100644 index 000000000..5d874b990 --- /dev/null +++ b/docs/plans/woocommerce-pagination-fix.md @@ -0,0 +1,340 @@ +# WooCommerce-Bestellimport: Pagination + `after`-Filter + +**Issue:** [openxe-org/openxe#262](https://github.com/OpenXE-org/OpenXE/issues/262) +**Branch:** `fix/woocommerce-order-import-pagination` +**Base:** `development` @ `8bb13973` +**Typ:** Bugfix (stiller Datenverlust) + +--- + +## 1. Ziel + +Der WooCommerce-Shopimporter verliert Bestellungen, wenn mehr als 20 neue Aufträge +zwischen zwei Cron-Läufen eingehen. Dieser Fix macht den Bestellabruf vollständig +und verlustfrei, ohne neue Plugin-Abhängigkeiten und ohne Wechsel der API-Version +(bleibt auf `wc/v3`). + +## 2. Scope + +### IN-Scope + +- Single-Order-Abruf mit `after`-Filter + `exclude`-Tupel-Cursor; Caller-Loop iteriert. +- `after`-Filter (ISO-8601) statt 800er-`include`-Hack +- Persistenz "letzter erfolgreicher Import-Timestamp" pro Shop +- Fallback-Logik für Erstlauf (kein Timestamp vorhanden) +- Transitions-Kompatibilität zum Legacy-Parameter `ab_nummer` + +### OUT-of-Scope (eigene Issues/PRs) + +- Batch-Endpoints fuer Stock-Sync (`products/batch`) +- Retry / Backoff fuer 429/5xx +- Composer-Migration des inline-WC-SDK +- Webhook-Support (#239) +- HPOS-Testmatrix +- UI-Reset-Button fuer den Import-Timestamp + +## 3. Fix-Parameter (final) + +| Parameter | Wert | Begruendung | +|---|---|---| +| Erstlauf-Fallback | **30 Tage** | Sinnvoller Mittelweg: holt historische Bestellungen, aber nicht unbegrenzt. UI-Override in Folge-PR moeglich. | +| Persistenz-Feld | `shopexport.einstellungen_json` → `felder.letzter_import_timestamp` | Bestehende Struktur nutzen, keine DB-Migration. | +| Timestamp-Format | ISO-8601 `Y-m-d\TH:i:s` (UTC) | Direkt an `after=` durchreichbar. | +| Caller-Cap (pre-existing) | `shopexport.maxmanuell`, default 100 | Der Batch-Cap liegt beim shopimport.php-Caller, nicht im Importer. | +| Cursor-Format | Tupel `(letzter_import_timestamp, letzter_import_order_ids: [int])` | IDs sammeln den aktuellen Sekunden-Bucket; -1s-Verschiebung greift nur mit mindestens einer ID. | + +## 4. Datei- und Funktions-Landkarte + +Zielmodul: `www/pages/shopimporter_woocommerce.php` + +| Funktion | Zeilen | Betroffen? | Aenderung | +|---|---|---|---| +| `ImportGetAuftraegeAnzahl` | ~80–135 | ja | `include`-Hack raus, `after`-Filter rein, nur noch Count via `X-WP-Total` | +| `ImportGetAuftrag` | ~121–185 | ja | Single-Order-Query mit after-Filter; Caller-Loop in shopimport.php iteriert | +| `parseOrder` | ~222–305 | nein | unveraendert (liefert weiterhin pro Order) | +| `CatchRemoteCommand('data')` | mehrfach | mittelbar | stellt `letzter_import_timestamp` aus `einstellungen_json` bereit | +| `getKonfig` | ~802–837 | nein | nicht anfassen (separates Issue #224) | +| Inline-Client (`WCClient`, `WCHttpClient`, `WCResponse`) | 1021–2370 | ggf. ja | Header-Accessor ergaenzen falls nicht vorhanden | + +## 5. Implementierungsschritte + +### Schritt 1 — Header-Durchreichung im inline-Client pruefen + +**Aufgabe:** Klaeren, ob `WCResponse` die HTTP-Antwort-Header (`X-WP-Total`, +`X-WP-TotalPages`, `Link`) bereits als Array liefert oder ob der inline-Client +angepasst werden muss. + +**Deep-Read-Ziele (nur Lesen, kein Edit):** +- `WCHttpClient::processResponse()` / aequivalent (vermutlich ~Zeile 2140–2200) +- `WCResponse`-Constructor: werden Headers gespeichert? +- `curl_setopt`-Setup: ist `CURLOPT_HEADERFUNCTION` oder `CURLOPT_HEADER` aktiv? + +**Entscheidungs-Gate:** +- Wenn Headers bereits im Response-Objekt: direkt weiter mit Schritt 3. +- Wenn nicht: Schritt 2 vor Schritt 3. + +**Akzeptanzkriterium:** Klarer Plan, welche Client-Aenderung noetig ist (oder dass keine noetig ist). + +### Schritt 2 — Headers exponieren (bedingt) + +**Nur falls Schritt 1 ergibt, dass Headers nicht zur Businesslogik durchdringen.** + +- `WCHttpClient::processResponse()`: Header-String in assoziatives Array parsen. +- `WCResponse` um `getHeaders()` / `getHeader(string $name)` erweitern. +- cURL-Setup: `CURLOPT_HEADERFUNCTION` registrieren, Headers in Sammelarray schreiben. +- Case-insensitive Lookup (`strtolower`-normalisiert speichern), da WP Headers teils + `x-wp-totalpages` vs. `X-WP-TotalPages` sendet. + +**Akzeptanzkriterium:** `$response->getHeader('x-wp-totalpages')` liefert eine Zahl als +String (z.B. `"3"`). + +**Risiko:** Client-Klassen werden auch an anderen Stellen instanziiert (theoretisch). +→ Mit `grep` verifizieren, dass `WCClient`/`WCResponse` nur in +`shopimporter_woocommerce.php` verwendet werden (keine Referenzen in +anderen `www/pages/*.php`-Dateien). + +### Schritt 3 — Timestamp-Persistenz + +**3a — Lesen:** +- In `getKonfig()` oder `CatchRemoteCommand('data')` den Wert + `$felder['letzter_import_timestamp']` auslesen. +- Fallback: `date('Y-m-d\TH:i:s', strtotime('-30 days'))` wenn leer/null. +- Auf Klassen-Property `$this->lastImportTimestamp` ablegen. + +**3b — Schreiben:** +- Nach erfolgreichem Lauf (am Ende von `ImportGetAuftrag`): den Timestamp der + **zuletzt verarbeiteten Bestellung** (nicht `now()`) in `einstellungen_json` zurueckschreiben. +- Grund: wenn der Lauf bei Order #n+3 abbricht, beim naechsten Lauf mit Order #n+3 + weiterarbeiten, nicht mit `now()` (→ Datenverlust). +- SQL: `UPDATE shopexport SET einstellungen_json = :json WHERE id = :id`. +- Muss ueber den in `DatabaseService` vorhandenen Mechanismus laufen (named params, + Prepared Statement — siehe `CLAUDE.md`-Projektregel). + +**3c — Atomic Update:** +- Nur bei erfolgreichem Import-Ende Timestamp persistieren. +- Bei Exception mitten im Lauf: Timestamp NICHT auf den Absturzpunkt schreiben + — besser: pro erfolgreich verarbeiteter Order einzeln fortschreiben (Progress), + sodass ein Absturz nur den aktuellen, nicht alle bisherigen, verliert. + +**Akzeptanzkriterium:** +``` +SELECT einstellungen_json FROM shopexport WHERE id = +→ enthaelt 'letzter_import_timestamp': '2026-04-20T12:34:56' +``` + +### Schritt 4 — Refactor `ImportGetAuftraegeAnzahl` (Count-Funktion) + +**Alt (Zeile 80–135):** +- Query: `orders?status=…&include=<800 IDs>&per_page=100`. +- Liest `count($response)` als Return. + +**Neu:** +- Query: `orders?status[]=&status[]=&after=&per_page=1`. +- Return: `(int) $response->getHeader('x-wp-total')`. +- `per_page=1` reicht — wir brauchen nur den Count-Header, nicht die Daten. + +**Akzeptanzkriterium:** +- Bei 0 neuen Orders: liefert 0. +- Bei 250 neuen Orders: liefert 250 (nicht 100). + +### Schritt 5 — Refactor `ImportGetAuftrag` (Import-Funktion) + +**Alt:** Query mit `include`-Liste, Iteration ueber bis zu 20 Orders, kein Cursor. + +**Neu — Single-Order-Pseudocode:** + +``` +# Fenster aufbauen +if last_order_ids is not empty: + after = last_ts - 1s + exclude = last_order_ids +else: + after = last_ts + exclude = (nicht gesetzt) + +# Fetch +order = client.get(orders, after=after, exclude=exclude, per_page=1, orderby=date, order=asc)[0] + +if response is empty: + return [] + +wcOrder = response[0] +order = parseOrder(wcOrder) + +# Persist +if order.date_created_gmt == last_ts: + last_order_ids = last_order_ids + [order.id] +else: + last_ts = order.date_created_gmt + last_order_ids = [order.id] + +persistLastImportCursor(order.date_created_gmt, order.id) + +return [{ id: order.auftrag, sessionid: '', logdatei: '', warenkorb: ... }] +``` + +**Wichtig:** +- `orderby=date` + `order=asc` garantiert, dass die **aelteste** neue Order zuerst kommt. + Dadurch kann der Progress-Timestamp monoton wachsen. +- `date_created_gmt` als Referenz (nicht `date_created` — Zeitzonen-Fallen vermeiden). +- Volume-Handling liegt beim Caller (shopimport.php), nicht im Importer. + +**Akzeptanzkriterium:** +- Pro Call: exakt 1 Order zurueck (oder leer). +- Bei >100 neuen Orders: Caller-Schleife (maxmanuell-gekappt) iteriert bis Ende. + +### Schritt 5a — Caller-Kontrakt + +Der Caller in shopimport.php:1304-1306 ruft pro Order-Import-Iteration: +- `ImportGetAuftraegeAnzahl()` einmal fuer Count (mit maxmanuell-Cap) +- `ImportGetAuftrag()` in for-Schleife, verarbeitet `$result[0]` + +Der Importer muss diesen Kontrakt einhalten: pro Call max. 1 Order. +Der after-Filter sorgt dafuer, dass jede Iteration die naechste Order +holt. Ein Crash zwischen `RemoteGetAuftrag()` und `shopimport_auftraege`- +Insert verliert max. diese eine Order. + +### Schritt 5b — Migration-Helper + +Um die ab_nummer → timestamp Migration auch im Count-Pfad auszufuehren +(shopimport.php ruft ImportGetAuftraegeAnzahl() BEFORE ImportGetAuftrag()): + +- Extraktion in eine private Methode `migrateAbNummerIfNeeded()`. +- Aufruf am Anfang von beiden ImportGetAuftraegeAnzahl() und ImportGetAuftrag(). +- Idempotent durch `$lastImportTimestampIsFallback`-Check — nach einmaliger Migration + keine weiteren Reads. + +### Schritt 6 — Transitions-Kompatibilitaet `ab_nummer` + +**Ist-Zustand:** `CatchRemoteCommand('data')` liefert u.a. `ab_nummer` — die naechste +Bestell-Nummer, ab der gelesen werden soll (Legacy-Cursor). + +**Uebergangsregel:** +- Wenn `letzter_import_timestamp` gesetzt → `after`-Filter nutzen, `ab_nummer` ignorieren. +- Wenn `letzter_import_timestamp` leer aber `ab_nummer` > 0 → einmalig `ab_nummer` in + einen Timestamp uebersetzen (Query `GET orders/{ab_nummer}` → `date_created_gmt` lesen), + als `letzter_import_timestamp` persistieren, ab dann `after`-Logik. +- Wenn beides leer → 30-Tage-Fallback (Schritt 3a). + +**Akzeptanzkriterium:** Shop, der bisher mit `ab_nummer` lief, importiert nach +Update **keine Duplikate** und **keine Luecken**. + +### Schritt 7 — `include`-Hack entfernen + +- Loeschen: + - Zeile ~99–113 (Count-Pfad `include`-Aufbau) + - Zeile ~153–167 (Import-Pfad `include`-Aufbau) + - Die beiden selbstkritischen Code-Kommentare *"fake"-Filter* +- Keine Ersatz-Struktur — `after` uebernimmt den Job komplett. + +### Schritt 8 — Cleanup & Commits + +**Pre-Commit-Checks:** +- `php -l www/pages/shopimporter_woocommerce.php` +- Trailing Whitespace raus (Subagent-Reste) +- CRLF-Warnungen ignorieren (autocrlf-Artefakte per Projekt-Regel) + +**Commit-Struktur (atomar, auf `fix/woocommerce-order-import-pagination`):** + +1. `fix(woocommerce): expose response headers from inline WC client` + → nur Client-Aenderung (bedingt; entfaellt wenn Schritt 1 Headers schon freigibt) + +2. `fix(woocommerce): persist last-import timestamp in shopexport config` + → Timestamp-Read/Write + 30-Tage-Fallback + Progress-Update pro Order + +3. `fix(woocommerce): use after-filter and pagination for order import` + → Kernaenderung an `ImportGetAuftrag` und `ImportGetAuftraegeAnzahl` + +4. `refactor(woocommerce): remove 800-id include hack` + → Aufraeumen toter Code + Kommentare + +5. `docs: add plan for woocommerce pagination fix` + → Diese Datei (`docs/plans/woocommerce-pagination-fix.md`) + +**PR-Ziel:** `openxe-org/openxe:master` (Upstream hat kein `development`). +**PR-Body:** Verweis auf Issue #262 + knappe Zusammenfassung der 4 Commits. + +## 6. Test-Plan (Integration gegen `192.168.0.143`) + +Keine Unit-Tests (OpenXE hat keine Test-Suite fuer Shopimporter). +Manuelle Integrationstests mit seeded Test-Orders. + +### Setup +- WP-Backend: `admin:password` +- WC-REST: `consumer_key`/`consumer_secret` generieren (Admin → WooCommerce → Einstellungen → Erweitert → REST-API) +- OpenXE-Shop-Konfiguration: bestehende Testinstanz wiederverwenden + +### Test-Matrix + +| # | Szenario | Start-State | Aktion | Erwartung | +|---|---|---|---|---| +| T1 | Frischinstall, keine Orders | `letzter_import_timestamp` leer | Lauf starten | 0 Orders, Timestamp bleibt leer | +| T2 | Frischinstall, 10 Orders <30 Tage alt | `letzter_import_timestamp` leer | Lauf starten | 10 Orders importiert, Timestamp = neueste Order | +| T3 | Frischinstall, 10 Orders >30 Tage alt | `letzter_import_timestamp` leer | Lauf starten | 0 Orders (Fallback-Schwelle), Timestamp leer | +| T4 | Standardlauf, 30 neue Orders | Timestamp gesetzt | Lauf starten | 30 Orders, Timestamp fortgeschrieben | +| T5 | Spike, 150 neue Orders | Timestamp gesetzt | Lauf starten | 150 Orders, Caller-Schleife durch maxmanuell auf 100 gekappt; Folgelauf holt Rest | +| T6 | Rueckstand, >100 neue Orders | Timestamp gesetzt | Lauf 1 starten | 100 Orders (maxmanuell-Cap), Cursor fortgeschrieben | +| T6b | Rueckstand Teil 2 | nach T6 | Lauf 2 starten | restliche Orders bis maxmanuell-Cap, Timestamp final | +| T7 | Transition, Shop mit `ab_nummer`=12345, Timestamp leer | `ab_nummer=12345` | Lauf starten | `ab_nummer` in Timestamp uebersetzt, keine Duplikate | +| T8 | Abbruch mitten im Lauf | Exception nach Fetch, vor INSERT | neuer Lauf | bei Abbruch zwischen Fetch und Insert max. 1 Order verloren; ab naechster Order fortgesetzt | +| T9 | Idempotenz | T4 erfolgreich gelaufen | T4 nochmal starten | 0 neue Orders, keine Duplikate | +| T10 | URL-Laenge (Regression) | 800 alte Orders existieren | Lauf starten | URL bleibt kurz (<2 KB), keine `include`-Liste mehr | + +### Mess-Artefakte (vor/nach) + +- Anzahl HTTP-Requests pro Lauf (via Apache-Log auf Testinstanz) +- Laenge der URL im `GET /orders`-Request +- Laufzeit pro Lauf +- Anzahl importierte Orders pro Lauf + +## 7. Rollout & Rueckwaerts-Kompatibilitaet + +### Migration beim Update +- Keine DB-Migration noetig. +- Beim ersten Lauf nach Update: + - Wenn `ab_nummer` gesetzt → einmalige Uebersetzung (Schritt 6). + - Wenn nichts gesetzt → 30-Tage-Fallback. +- Kein Breaking Change fuer bestehende Shops. + +### Rollback-Szenario +- Rueckkehr zum alten Verhalten: Revert der 4 Commits aus Schritt 8. +- `letzter_import_timestamp` in `einstellungen_json` stoert alte Version nicht + (unbekannte Keys werden ignoriert). + +### Kompatibilitaet mit anderen Modulen +- `class.remote.php` → nicht beruehrt. +- Andere `shopimporter_*`-Module → nicht beruehrt (nur WooCommerce-spezifisch). +- `class.erpapi.php` → nicht beruehrt. + +## 8. Risiken & Mitigations + +| Risiko | Wahrscheinlichkeit | Auswirkung | Mitigation | +|---|---|---|---| +| Inline-Client liefert Headers nicht durch | mittel | hoch (blockiert Schritt 4/5) | Schritt 1 als Entscheidungs-Gate, Schritt 2 bedingter Vorarbeits-Schritt | +| Shop-Timezone vs. `after`-Zeitzone | mittel | mittel (Off-by-one-Tag) | `date_created_gmt` und `after` beide in UTC, explizit testen (T4, T9) | +| Kunden ohne Timestamp + >30 Tage alte neue Orders | niedrig | mittel | Dokumentiert in PR-Body, UI-Override in Folge-PR | +| `orderby=date` Ordering nicht deterministisch bei gleichem Timestamp | niedrig | niedrig | Zusaetzlich `orderby=date&order=asc` und WC liefert stabile Sekundaersortierung nach ID | +| Andere Shopimporter erben Basisklasse-Struktur | niedrig | niedrig | Nur `shopimporter_woocommerce.php` anfassen, `ShopimporterBase` nicht anruehren | +| URL-Laengenlimits der WAF beim `status[]`-Array | sehr niedrig | niedrig | Typisch 2-3 Status-Werte, URL bleibt kurz | +| Caller-Kontrakt-Abweichung (1 vs. n Orders) | niedrig | hoch | Per Design Single-Order-Return; Caller-Loop fuer Volume-Handling | +| Timestamp-Kollision bei identischem date_created_gmt | niedrig | mittel | Adressiert durch Tupel-Cursor + Bucket-Akkumulation (letzter_import_order_ids sammelt alle IDs des Buckets) | +| Unbounded exclude-Liste bei grossem Bucket | niedrig | niedrig | In der Praxis kleine Listen; URL < 2 KB bei ~100 IDs | + +## 9. Definition of Done + +- [ ] Schritt 1 abgeschlossen, Entscheidung fuer/gegen Schritt 2 dokumentiert im PR-Body +- [ ] Alle geplanten Commits auf `fix/woocommerce-order-import-pagination` +- [ ] `php -l` ohne Fehler +- [ ] Testmatrix T1–T10 auf `192.168.0.143` durchgelaufen, Ergebnisse im PR-Body +- [ ] Issue #262 im PR referenziert (`Fixes #262`) +- [ ] PR gegen `openxe-org/openxe:master` eroeffnet +- [ ] Diese Plan-Datei mitversioniert im Fix-Branch + +## 10. Referenzen + +- Issue: https://github.com/OpenXE-org/OpenXE/issues/262 +- WC REST API Docs (Orders): https://woocommerce.github.io/woocommerce-rest-api-docs/#orders +- WP-REST-API Pagination: https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/ +- Parallele Issues: + - #239 Webhook Support (push statt poll) + - #224 JSON-Error in `getKonfig` diff --git a/vendor/autoload.php b/vendor/autoload.php index 76bb3835a..3d848fa8f 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -14,10 +14,7 @@ echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/vendor/automattic/woocommerce/LICENSE b/vendor/automattic/woocommerce/LICENSE new file mode 100644 index 000000000..7e67c6ccb --- /dev/null +++ b/vendor/automattic/woocommerce/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016, Automattic (https://automattic.com/) + +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/automattic/woocommerce/README.md b/vendor/automattic/woocommerce/README.md new file mode 100644 index 000000000..2ddc4cfa7 --- /dev/null +++ b/vendor/automattic/woocommerce/README.md @@ -0,0 +1,169 @@ +# WooCommerce API - PHP Client + +A PHP wrapper for the WooCommerce REST API. Easily interact with the WooCommerce REST API securely using this library. If using a HTTPS connection this library uses BasicAuth, else it uses Oauth to provide a secure connection to WooCommerce. + +[![CI status](https://github.com/woocommerce/wc-api-php/actions/workflows/ci.yml/badge.svg?branch=trunk)](https://github.com/woocommerce/wc-api-php/actions/workflows/ci.yml) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/woocommerce/wc-api-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/woocommerce/wc-api-php/?branch=master) +[![PHP version](https://badge.fury.io/ph/automattic%2Fwoocommerce.svg)](https://packagist.org/packages/automattic/woocommerce) + +## Installation + +``` +composer require automattic/woocommerce +``` + +## Getting started + +Generate API credentials (Consumer Key & Consumer Secret) following this instructions +. + +Check out the WooCommerce API endpoints and data that can be manipulated in . + +## Setup + +Setup for the new WP REST API integration (WooCommerce 2.6 or later): + +```php +require __DIR__ . '/vendor/autoload.php'; + +use Automattic\WooCommerce\Client; + +$woocommerce = new Client( + 'http://example.com', + 'ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + 'cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + [ + 'version' => 'wc/v3', + ] +); +``` + +## Client class + +```php +$woocommerce = new Client($url, $consumer_key, $consumer_secret, $options); +``` + +### Options + +| Option | Type | Required | Description | +| ----------------- | -------- | -------- | ------------------------------------------ | +| `url` | `string` | yes | Your Store URL, example: http://woo.dev/ | +| `consumer_key` | `string` | yes | Your API consumer key | +| `consumer_secret` | `string` | yes | Your API consumer secret | +| `options` | `array` | no | Extra arguments (see client options table) | + +#### Client options + +| Option | Type | Required | Description | +| ------------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `version` | `string` | no | API version, default is `wc/v3` | +| `timeout` | `int` | no | Request timeout, default is `15` | +| `verify_ssl` | `bool` | no | Verify SSL when connect, use this option as `false` when need to test with self-signed certificates, default is `true` | +| `follow_redirects` | `bool` | no | Allow the API call to follow redirects | +| `query_string_auth` | `bool` | no | Force Basic Authentication as query string when `true` and using under HTTPS, default is `false` | +| `oauth_timestamp` | `string` | no | Custom oAuth timestamp, default is `time()` | +| `oauth_only` | `bool` | no | Only use oauth for requests, it will disable Basic Auth, default is `false` | +| `user_agent` | `string` | no | Custom user-agent, default is `WooCommerce API Client-PHP` | +| `wp_api_prefix` | `string` | no | Custom WP REST API URL prefix, used to support custom prefixes created with the `rest_url_prefix` filter | +| `wp_api` | `bool` | no | Set to `false` in order to use the legacy WooCommerce REST API (deprecated and not recommended) | +| `method_override_query` | `bool` | no | If true will mask all non-GET/POST methods by using POST method with added query parameter `?_method=METHOD` into URL | +| `method_override_header` | `bool` | no | If true will mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added `X-HTTP-Method-Override: METHOD` HTTP header into request | + +## Client methods + +### GET + +```php +$woocommerce->get($endpoint, $parameters = []); +``` + +### POST + +```php +$woocommerce->post($endpoint, $data); +``` + +### PUT + +```php +$woocommerce->put($endpoint, $data); +``` + +### DELETE + +```php +$woocommerce->delete($endpoint, $parameters = []); +``` + +### OPTIONS + +```php +$woocommerce->options($endpoint); +``` + +#### Arguments + +| Params | Type | Description | +| ------------ | -------- | ------------------------------------------------------------ | +| `endpoint` | `string` | WooCommerce API endpoint, example: `customers` or `orders/12` | +| `data` | `array` | Only for POST and PUT, data that will be converted to JSON | +| `parameters` | `array` | Only for GET and DELETE, request query string | + +#### Response + +All methods will return arrays on success or throwing `HttpClientException` errors on failure. + +```php +use Automattic\WooCommerce\HttpClient\HttpClientException; + +try { + // Array of response results. + $results = $woocommerce->get('customers'); + // Example: ['customers' => [[ 'id' => 8, 'created_at' => '2015-05-06T17:43:51Z', 'email' => ... + echo '
' . print_r($results, true) . '
'; // JSON output.
+
+  // Last request data.
+  $lastRequest = $woocommerce->http->getRequest();
+  echo '
' . print_r($lastRequest->getUrl(), true) . '
'; // Requested URL (string).
+  echo '
' .
+    print_r($lastRequest->getMethod(), true) .
+    '
'; // Request method (string).
+  echo '
' .
+    print_r($lastRequest->getParameters(), true) .
+    '
'; // Request parameters (array).
+  echo '
' .
+    print_r($lastRequest->getHeaders(), true) .
+    '
'; // Request headers (array).
+  echo '
' . print_r($lastRequest->getBody(), true) . '
'; // Request body (JSON).
+
+  // Last response data.
+  $lastResponse = $woocommerce->http->getResponse();
+  echo '
' . print_r($lastResponse->getCode(), true) . '
'; // Response code (int).
+  echo '
' .
+    print_r($lastResponse->getHeaders(), true) .
+    '
'; // Response headers (array).
+  echo '
' . print_r($lastResponse->getBody(), true) . '
'; // Response body (JSON).
+} catch (HttpClientException $e) {
+  echo '
' . print_r($e->getMessage(), true) . '
'; // Error message.
+  echo '
' . print_r($e->getRequest(), true) . '
'; // Last request data.
+  echo '
' . print_r($e->getResponse(), true) . '
'; // Last response data.
+}
+```
+
+## Release History
+
+- 2022-03-18 - 3.1.0 - Added new options to support `_method` and `X-HTTP-Method-Override` from WP, supports 7+, dropped support to PHP 5.
+- 2019-01-16 - 3.0.0 - Legacy API turned off by default, and improved JSON error handler.
+- 2018-03-29 - 2.0.1 - Fixed fatal errors on `lookForErrors`.
+- 2018-01-12 - 2.0.0 - Responses changes from arrays to `stdClass` objects. Added `follow_redirects` option.
+- 2017-06-06 - 1.3.0 - Remove BOM before decoding and added support for multi-dimensional arrays for oAuth1.0a.
+- 2017-03-15 - 1.2.0 - Added `user_agent` option.
+- 2016-12-14 - 1.1.4 - Fixed WordPress 4.7 compatibility.
+- 2016-10-26 - 1.1.3 - Allow set `oauth_timestamp` and improved how is handled the response headers.
+- 2016-09-30 - 1.1.2 - Added `wp_api_prefix` option to allow custom WP REST API URL prefix.
+- 2016-05-10 - 1.1.1 - Fixed oAuth and error handler for WP REST API.
+- 2016-05-09 - 1.1.0 - Added support for WP REST API, added method `Automattic\WooCommerce\Client::options` and fixed multiple headers responses.
+- 2016-01-25 - 1.0.2 - Fixed an error when getting data containing non-latin characters.
+- 2016-01-21 - 1.0.1 - Sort all oAuth parameters before build request URLs.
+- 2016-01-11 - 1.0.0 - Stable release.
diff --git a/vendor/automattic/woocommerce/composer.json b/vendor/automattic/woocommerce/composer.json
new file mode 100644
index 000000000..5390aec54
--- /dev/null
+++ b/vendor/automattic/woocommerce/composer.json
@@ -0,0 +1,42 @@
+{
+  "name": "automattic/woocommerce",
+  "description": "A PHP wrapper for the WooCommerce REST API",
+  "type": "library",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Claudio Sanches",
+      "email": "claudio.sanches@automattic.com"
+    }
+  ],
+  "minimum-stability": "dev",
+  "keywords": [
+    "API",
+    "WooCommerce"
+  ],
+  "require": {
+    "php": ">= 7.4.0",
+    "ext-curl": "*",
+    "ext-json": "*"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^9.5.0",
+    "squizlabs/php_codesniffer": "3.*",
+    "doctrine/instantiator": "^1.5.0"
+  },
+  "scripts": {
+    "phpcs": "vendor/bin/phpcs --colors --standard=phpcs.xml",
+    "phpcbf": "vendor/bin/phpcbf --colors --standard=phpcs.xml"
+  },
+  "autoload": {
+    "psr-4": {
+      "Automattic\\WooCommerce\\": ["src/WooCommerce"]
+    }
+  },
+  "autoload-dev": {
+    "psr-4": { 
+      "Automattic\\WooCommerce\\LegacyTests\\": "tests/legacy-php/WooCommerce/Tests",
+      "Automattic\\WooCommerce\\Tests\\": "tests/php/WooCommerce/Tests"
+    }
+  }
+}
\ No newline at end of file
diff --git a/vendor/automattic/woocommerce/composer.lock b/vendor/automattic/woocommerce/composer.lock
new file mode 100644
index 000000000..83633c08f
--- /dev/null
+++ b/vendor/automattic/woocommerce/composer.lock
@@ -0,0 +1,1897 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "8410ab16c98c12d7b5806814e8b21d24",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.5.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "12be2483e1f0e850b353e26869e4e6c038459501"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/12be2483e1f0e850b353e26869e4e6c038459501",
+                "reference": "12be2483e1f0e850b353e26869e4e6c038459501",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^9 || ^12",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.16 || ^1",
+                "phpstan/phpstan": "^1.4",
+                "phpstan/phpstan-phpunit": "^1",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
+                "vimeo/psalm": "^4.30 || ^5.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "https://ocramius.github.io/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/instantiator/issues",
+                "source": "https://github.com/doctrine/instantiator/tree/1.5.x"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-12-09T14:16:53+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "conflict": {
+                "doctrine/collections": "<1.6.8",
+                "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.6.8",
+                "doctrine/common": "^2.13.3 || ^3.2.2",
+                "phpspec/prophecy": "^1.10",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+            },
+            "default-branch": true,
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ],
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/DeepCopy/issues",
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+            },
+            "funding": [
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-01T08:46:24+00:00"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "0da2d6679a3df45d6d720aa2e0d4568f82a32e46"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0da2d6679a3df45d6d720aa2e0d4568f82a32e46",
+                "reference": "0da2d6679a3df45d6d720aa2e0d4568f82a32e46",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-json": "*",
+                "ext-tokenizer": "*",
+                "php": ">=7.4"
+            },
+            "require-dev": {
+                "ircmaxell/php-yacc": "^0.0.7",
+                "phpunit/phpunit": "^9.0"
+            },
+            "default-branch": true,
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/PHP-Parser/issues",
+                "source": "https://github.com/nikic/PHP-Parser/tree/master"
+            },
+            "time": "2025-09-08T07:54:25+00:00"
+        },
+        {
+            "name": "phar-io/manifest",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "65f90285728eae4eae313b8b6ba11b2f5436038e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/65f90285728eae4eae313b8b6ba11b2f5436038e",
+                "reference": "65f90285728eae4eae313b8b6ba11b2f5436038e",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-phar": "*",
+                "ext-xmlwriter": "*",
+                "phar-io/version": "^3.0.1",
+                "php": "^7.2 || ^8.0"
+            },
+            "default-branch": true,
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "support": {
+                "issues": "https://github.com/phar-io/manifest/issues",
+                "source": "https://github.com/phar-io/manifest/tree/master"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-07-05T08:48:25+00:00"
+        },
+        {
+            "name": "phar-io/version",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Library for handling version information and constraints",
+            "support": {
+                "issues": "https://github.com/phar-io/version/issues",
+                "source": "https://github.com/phar-io/version/tree/3.2.1"
+            },
+            "time": "2022-02-21T01:04:05+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "9.2.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "0448d60087a382392a1b2a1abe434466e03dcc87"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0448d60087a382392a1b2a1abe434466e03dcc87",
+                "reference": "0448d60087a382392a1b2a1abe434466e03dcc87",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-xmlwriter": "*",
+                "nikic/php-parser": "^4.19.1 || ^5.1.0",
+                "php": ">=7.3",
+                "phpunit/php-file-iterator": "^3.0.6",
+                "phpunit/php-text-template": "^2.0.4",
+                "sebastian/code-unit-reverse-lookup": "^2.0.3",
+                "sebastian/complexity": "^2.0.3",
+                "sebastian/environment": "^5.1.5",
+                "sebastian/lines-of-code": "^1.0.4",
+                "sebastian/version": "^3.0.2",
+                "theseer/tokenizer": "^1.2.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6"
+            },
+            "suggest": {
+                "ext-pcov": "PHP extension that provides line coverage",
+                "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "9.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+                "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-10-31T05:58:25+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "3.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "38b24367e1b340aa78b96d7cab042942d917bb84"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/38b24367e1b340aa78b96d7cab042942d917bb84",
+                "reference": "38b24367e1b340aa78b96d7cab042942d917bb84",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-02-11T16:23:04+00:00"
+        },
+        {
+            "name": "phpunit/php-invoker",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-invoker.git",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "ext-pcntl": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-pcntl": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Invoke callables with a timeout",
+            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+            "keywords": [
+                "process"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+                "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:58:55+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T05:33:50+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "5.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:16:10+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "9.6.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "996021ad5437394004bf3aeb6ae52a9620f47157"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/996021ad5437394004bf3aeb6ae52a9620f47157",
+                "reference": "996021ad5437394004bf3aeb6ae52a9620f47157",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.5.0 || ^2",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xml": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.13.4",
+                "phar-io/manifest": "^2.0.4",
+                "phar-io/version": "^3.2.1",
+                "php": ">=7.3",
+                "phpunit/php-code-coverage": "^9.2.32",
+                "phpunit/php-file-iterator": "^3.0.6",
+                "phpunit/php-invoker": "^3.1.1",
+                "phpunit/php-text-template": "^2.0.4",
+                "phpunit/php-timer": "^5.0.3",
+                "sebastian/cli-parser": "^1.0.2",
+                "sebastian/code-unit": "^1.0.8",
+                "sebastian/comparator": "^4.0.9",
+                "sebastian/diff": "^4.0.6",
+                "sebastian/environment": "^5.1.5",
+                "sebastian/exporter": "^4.0.6",
+                "sebastian/global-state": "^5.0.8",
+                "sebastian/object-enumerator": "^4.0.4",
+                "sebastian/resource-operations": "^3.0.4",
+                "sebastian/type": "^3.2.1",
+                "sebastian/version": "^3.0.2"
+            },
+            "suggest": {
+                "ext-soap": "To be able to generate mocks based on WSDL files",
+                "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "9.6-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Framework/Assert/Functions.php"
+                ],
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+                "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6"
+            },
+            "funding": [
+                {
+                    "url": "https://phpunit.de/sponsors.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-09-12T05:34:54+00:00"
+        },
+        {
+            "name": "sebastian/cli-parser",
+            "version": "1.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/cli-parser.git",
+                "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+                "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for parsing CLI options",
+            "homepage": "https://github.com/sebastianbergmann/cli-parser",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-02T06:27:43+00:00"
+        },
+        {
+            "name": "sebastian/code-unit",
+            "version": "1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit.git",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/code-unit",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:08:54+00:00"
+        },
+        {
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:30:19+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "4.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
+                "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/diff": "^4.0",
+                "sebastian/exporter": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-10T06:51:50+00:00"
+        },
+        {
+            "name": "sebastian/complexity",
+            "version": "2.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/complexity.git",
+                "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
+                "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.18 || ^5.0",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for calculating the complexity of PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/complexity",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/complexity/issues",
+                "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-12-22T06:19:30+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "4.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+                "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3",
+                "symfony/process": "^4.2 || ^5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-02T06:30:58+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "5.1.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+                "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-posix": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/environment/issues",
+                "source": "https://github.com/sebastianbergmann/environment/tree/5.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-02-03T06:03:51+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "4.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
+                "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "https://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-02T06:33:00+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "5.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-dom": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/global-state/issues",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-10T07:10:35+00:00"
+        },
+        {
+            "name": "sebastian/lines-of-code",
+            "version": "1.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+                "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+                "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.18 || ^5.0",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for counting the lines of code in PHP source code",
+            "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+                "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-12-22T06:20:34+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:12:34+00:00"
+        },
+        {
+            "name": "sebastian/object-reflector",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:14:26+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "4.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "https://github.com/sebastianbergmann/recursion-context",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-10T06:57:39+00:00"
+        },
+        {
+            "name": "sebastian/resource-operations",
+            "version": "dev-main",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25",
+                "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.0"
+            },
+            "default-branch": true,
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "support": {
+                "source": "https://github.com/sebastianbergmann/resource-operations/tree/main"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-14T18:47:08+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "3.2.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+                "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/type/issues",
+                "source": "https://github.com/sebastianbergmann/type/tree/3.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-02-03T06:13:03+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "3.0.x-dev",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/version/issues",
+                "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:39:44+00:00"
+        },
+        {
+            "name": "squizlabs/php_codesniffer",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+                "reference": "fcfefc635209c324eb6ed47280d4cc5d245c7cd6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/fcfefc635209c324eb6ed47280d4cc5d245c7cd6",
+                "reference": "fcfefc635209c324eb6ed47280d4cc5d245c7cd6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-simplexml": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+            },
+            "default-branch": true,
+            "bin": [
+                "bin/phpcbf",
+                "bin/phpcs"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Greg Sherwood",
+                    "role": "Former lead"
+                },
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "role": "Current lead"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
+                }
+            ],
+            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+            "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+            "keywords": [
+                "phpcs",
+                "standards",
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+                "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+                "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+                "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCSStandards",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcsstandards",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-09-11T05:53:07+00:00"
+        },
+        {
+            "name": "theseer/tokenizer",
+            "version": "1.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/tokenizer.git",
+                "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+                "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+            "support": {
+                "issues": "https://github.com/theseer/tokenizer/issues",
+                "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-03T12:36:25+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": {},
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">= 7.4.0",
+        "ext-curl": "*",
+        "ext-json": "*"
+    },
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/Client.php b/vendor/automattic/woocommerce/src/WooCommerce/Client.php
new file mode 100644
index 000000000..2bceda31d
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/Client.php
@@ -0,0 +1,109 @@
+http = new HttpClient($url, $consumerKey, $consumerSecret, $options);
+    }
+
+    /**
+     * POST method.
+     *
+     * @param string $endpoint API endpoint.
+     * @param array  $data     Request data.
+     *
+     * @return \stdClass
+     */
+    public function post($endpoint, $data)
+    {
+        return $this->http->request($endpoint, 'POST', $data);
+    }
+
+    /**
+     * PUT method.
+     *
+     * @param string $endpoint API endpoint.
+     * @param array  $data     Request data.
+     *
+     * @return \stdClass
+     */
+    public function put($endpoint, $data)
+    {
+        return $this->http->request($endpoint, 'PUT', $data);
+    }
+
+    /**
+     * GET method.
+     *
+     * @param string $endpoint   API endpoint.
+     * @param array  $parameters Request parameters.
+     *
+     * @return \stdClass|array
+     */
+    public function get($endpoint, $parameters = [])
+    {
+        return $this->http->request($endpoint, 'GET', [], $parameters);
+    }
+
+    /**
+     * DELETE method.
+     *
+     * @param string $endpoint   API endpoint.
+     * @param array  $parameters Request parameters.
+     *
+     * @return \stdClass
+     */
+    public function delete($endpoint, $parameters = [])
+    {
+        return $this->http->request($endpoint, 'DELETE', [], $parameters);
+    }
+
+    /**
+     * OPTIONS method.
+     *
+     * @param string $endpoint API endpoint.
+     *
+     * @return \stdClass
+     */
+    public function options($endpoint)
+    {
+        return $this->http->request($endpoint, 'OPTIONS', [], []);
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/BasicAuth.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/BasicAuth.php
new file mode 100644
index 000000000..b63916812
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/BasicAuth.php
@@ -0,0 +1,96 @@
+ch             = $ch;
+        $this->consumerKey    = $consumerKey;
+        $this->consumerSecret = $consumerSecret;
+        $this->doQueryString  = $doQueryString;
+        $this->parameters     = $parameters;
+
+        $this->processAuth();
+    }
+
+    /**
+     * Process auth.
+     */
+    protected function processAuth()
+    {
+        if ($this->doQueryString) {
+            $this->parameters['consumer_key']    = $this->consumerKey;
+            $this->parameters['consumer_secret'] = $this->consumerSecret;
+        } else {
+            \curl_setopt($this->ch, CURLOPT_USERPWD, $this->consumerKey . ':' . $this->consumerSecret);
+        }
+    }
+
+    /**
+     * Get parameters.
+     *
+     * @return array
+     */
+    public function getParameters()
+    {
+        return $this->parameters;
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClient.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClient.php
new file mode 100644
index 000000000..2180262de
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClient.php
@@ -0,0 +1,490 @@
+options        = new Options($options);
+        $this->url            = $this->buildApiUrl($url);
+        $this->consumerKey    = $consumerKey;
+        $this->consumerSecret = $consumerSecret;
+    }
+
+    /**
+     * Check if is under SSL.
+     *
+     * @return bool
+     */
+    protected function isSsl()
+    {
+        return 'https://' === \substr($this->url, 0, 8);
+    }
+
+    /**
+     * Build API URL.
+     *
+     * @param string $url Store URL.
+     *
+     * @return string
+     */
+    protected function buildApiUrl($url)
+    {
+        $api = $this->options->isWPAPI() ? $this->options->apiPrefix() : '/wc-api/';
+
+        return \rtrim($url, '/') . $api . $this->options->getVersion() . '/';
+    }
+
+    /**
+     * Build URL.
+     *
+     * @param string $url        URL.
+     * @param array  $parameters Query string parameters.
+     *
+     * @return string
+     */
+    protected function buildUrlQuery($url, $parameters = [])
+    {
+        if (!empty($parameters)) {
+            if (false !== strpos($url, '?')) {
+                $url .= '&' . \http_build_query($parameters, '', '&');
+            } else {
+                $url .= '?' . \http_build_query($parameters, '', '&');
+            }
+        }
+
+        return $url;
+    }
+
+    /**
+     * Authenticate.
+     *
+     * @param string $url        Request URL.
+     * @param string $method     Request method.
+     * @param array  $parameters Request parameters.
+     *
+     * @return array
+     */
+    protected function authenticate($url, $method, $parameters = [])
+    {
+        // Setup authentication.
+        if (!$this->options->isOAuthOnly() && $this->isSsl()) {
+            $basicAuth = new BasicAuth(
+                $this->ch,
+                $this->consumerKey,
+                $this->consumerSecret,
+                $this->options->isQueryStringAuth(),
+                $parameters
+            );
+            $parameters = $basicAuth->getParameters();
+        } else {
+            $oAuth = new OAuth(
+                $url,
+                $this->consumerKey,
+                $this->consumerSecret,
+                $this->options->getVersion(),
+                $method,
+                $parameters,
+                $this->options->oauthTimestamp()
+            );
+            $parameters = $oAuth->getParameters();
+        }
+
+        return $parameters;
+    }
+
+    /**
+     * Setup method.
+     *
+     * @param string $method Request method.
+     */
+    protected function setupMethod($method)
+    {
+        if ('POST' == $method) {
+            \curl_setopt($this->ch, CURLOPT_POST, true);
+        } elseif (\in_array($method, ['PUT', 'DELETE', 'OPTIONS'])) {
+            \curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
+        }
+    }
+
+    /**
+     * Get request headers.
+     *
+     * @param  bool $sendData If request send data or not.
+     *
+     * @return array
+     */
+    protected function getRequestHeaders($sendData = false)
+    {
+        $headers = [
+            'Accept'     => 'application/json',
+            'User-Agent' => $this->options->userAgent() . '/' . Client::VERSION,
+        ];
+
+        if ($sendData) {
+            $headers['Content-Type'] = 'application/json;charset=utf-8';
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Create request.
+     *
+     * @param string $endpoint   Request endpoint.
+     * @param string $method     Request method.
+     * @param array  $data       Request data.
+     * @param array  $parameters Request parameters.
+     *
+     * @return Request
+     */
+    protected function createRequest($endpoint, $method, $data = [], $parameters = [])
+    {
+        $body    = '';
+        $url     = $this->url . $endpoint;
+        $hasData = !empty($data);
+        $headers = $this->getRequestHeaders($hasData);
+
+        // HTTP method override feature which masks PUT and DELETE HTTP methods as POST method with added
+        // ?_method=PUT query parameter and/or X-HTTP-Method-Override HTTP header.
+        if (!in_array($method, ['GET', 'POST'])) {
+            $usePostMethod = false;
+            if ($this->options->isMethodOverrideQuery()) {
+                $parameters = array_merge(['_method' => $method], $parameters);
+                $usePostMethod = true;
+            }
+            if ($this->options->isMethodOverrideHeader()) {
+                $headers['X-HTTP-Method-Override'] = $method;
+                $usePostMethod = true;
+            }
+            if ($usePostMethod) {
+                $method = 'POST';
+            }
+        }
+
+        // Setup authentication.
+        $parameters = $this->authenticate($url, $method, $parameters);
+
+        // Setup method.
+        $this->setupMethod($method);
+
+        // Include post fields.
+        if ($hasData) {
+            $body = \json_encode($data);
+            \curl_setopt($this->ch, CURLOPT_POSTFIELDS, $body);
+        }
+
+        $this->request = new Request(
+            $this->buildUrlQuery($url, $parameters),
+            $method,
+            $parameters,
+            $headers,
+            $body
+        );
+
+        return $this->getRequest();
+    }
+
+    /**
+     * Get response headers.
+     *
+     * @return array
+     */
+    protected function getResponseHeaders()
+    {
+        $headers = [];
+        $lines   = \explode("\n", $this->responseHeaders);
+        $lines   = \array_filter($lines, 'trim');
+
+        foreach ($lines as $index => $line) {
+            // Remove HTTP/xxx params.
+            if (strpos($line, ': ') === false) {
+                continue;
+            }
+
+            list($key, $value) = \explode(': ', $line);
+
+            $headers[$key] = isset($headers[$key]) ? $headers[$key] . ', ' . trim($value) : trim($value);
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Create response.
+     *
+     * @return Response
+     */
+    protected function createResponse()
+    {
+
+        // Set response headers.
+        $this->responseHeaders = '';
+        \curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, function ($_, $headers) {
+            $this->responseHeaders .= $headers;
+            return \strlen($headers);
+        });
+
+        // Get response data.
+        $body    = \curl_exec($this->ch);
+        $code    = \curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
+        $headers = $this->getResponseHeaders();
+
+        // Register response.
+        $this->response = new Response($code, $headers, $body);
+
+        return $this->getResponse();
+    }
+
+    /**
+     * Set default cURL settings.
+     */
+    protected function setDefaultCurlSettings()
+    {
+        $verifySsl       = $this->options->verifySsl();
+        $timeout         = $this->options->getTimeout();
+        $followRedirects = $this->options->getFollowRedirects();
+
+        \curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
+        if (!$verifySsl) {
+            \curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $verifySsl);
+        }
+        if ($followRedirects) {
+            \curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
+        }
+        \curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+        \curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout);
+        \curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
+        \curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->request->getRawHeaders());
+        \curl_setopt($this->ch, CURLOPT_URL, $this->request->getUrl());
+
+        foreach ($this->customCurlOptions as $customCurlOptionKey => $customCurlOptionValue) {
+            \curl_setopt($this->ch, $customCurlOptionKey, $customCurlOptionValue);
+        }
+    }
+
+    /**
+     * Look for errors in the request.
+     *
+     * @param array $parsedResponse Parsed body response.
+     */
+    protected function lookForErrors($parsedResponse)
+    {
+        // Any non-200/201/202 response code indicates an error.
+        if (!\in_array($this->response->getCode(), ['200', '201', '202'])) {
+            $errors = isset($parsedResponse->errors) ? $parsedResponse->errors : $parsedResponse;
+            $errorMessage = '';
+            $errorCode = '';
+
+            if (is_array($errors) && $errors) {
+                $errorMessage = $errors[0]->message;
+                $errorCode    = $errors[0]->code;
+            } elseif (isset($errors->message, $errors->code)) {
+                $errorMessage = $errors->message;
+                $errorCode    = $errors->code;
+            }
+
+            throw new HttpClientException(
+                \sprintf('Error: %s [%s]', $errorMessage, $errorCode),
+                $this->response->getCode(),
+                $this->request,
+                $this->response
+            );
+        }
+    }
+
+    /**
+     * Process response.
+     *
+     * @return \stdClass|array
+     */
+    protected function processResponse()
+    {
+        $body = $this->response->getBody();
+
+        // Look for UTF-8 BOM and remove.
+        if (0 === strpos(bin2hex(substr($body, 0, 4)), 'efbbbf')) {
+            $body = substr($body, 3);
+        }
+
+        $parsedResponse = \json_decode($body);
+
+        // Test if return a valid JSON.
+        if (JSON_ERROR_NONE !== json_last_error()) {
+            $message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Invalid JSON returned';
+            throw new HttpClientException(
+                sprintf('JSON ERROR: %s', $message),
+                $this->response->getCode(),
+                $this->request,
+                $this->response
+            );
+        }
+
+        $this->lookForErrors($parsedResponse);
+
+        return $parsedResponse;
+    }
+
+    /**
+     * Make requests.
+     *
+     * @param string $endpoint   Request endpoint.
+     * @param string $method     Request method.
+     * @param array  $data       Request data.
+     * @param array  $parameters Request parameters.
+     *
+     * @return \stdClass|array
+     */
+    public function request($endpoint, $method, $data = [], $parameters = [])
+    {
+        // Initialize cURL.
+        $this->ch = \curl_init();
+
+        // Set request args.
+        $request = $this->createRequest($endpoint, $method, $data, $parameters);
+
+        // Default cURL settings.
+        $this->setDefaultCurlSettings();
+
+        // Get response.
+        $response = $this->createResponse();
+
+        // Check for cURL errors.
+        if (\curl_errno($this->ch)) {
+            throw new HttpClientException('cURL Error: ' . \curl_error($this->ch), 0, $request, $response);
+        }
+
+        // we have to call curl_close only for PHP < 8
+        if (\is_resource($this->ch)) {
+            \curl_close($this->ch);
+        }
+
+        return $this->processResponse();
+    }
+
+    /**
+     * Get request data.
+     *
+     * @return Request
+     */
+    public function getRequest()
+    {
+        return $this->request;
+    }
+
+    /**
+     * Get response data.
+     *
+     * @return Response
+     */
+    public function getResponse()
+    {
+        return $this->response;
+    }
+
+    /**
+     * Set custom cURL options to use in requests.
+     *
+     * @param array $curlOptions
+     */
+    public function setCustomCurlOptions(array $curlOptions)
+    {
+        $this->customCurlOptions = $curlOptions;
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClientException.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClientException.php
new file mode 100644
index 000000000..bfd149fe6
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClientException.php
@@ -0,0 +1,71 @@
+request  = $request;
+        $this->response = $response;
+    }
+
+    /**
+     * Get request data.
+     *
+     * @return Request
+     */
+    public function getRequest()
+    {
+        return $this->request;
+    }
+
+    /**
+     * Get response data.
+     *
+     * @return Response
+     */
+    public function getResponse()
+    {
+        return $this->response;
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/OAuth.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/OAuth.php
new file mode 100644
index 000000000..5f3a4a13a
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/OAuth.php
@@ -0,0 +1,268 @@
+url            = $url;
+        $this->consumerKey    = $consumerKey;
+        $this->consumerSecret = $consumerSecret;
+        $this->apiVersion     = $apiVersion;
+        $this->method         = $method;
+        $this->parameters     = $parameters;
+        $this->timestamp      = $timestamp;
+    }
+
+    /**
+     * Encode according to RFC 3986.
+     *
+     * @param string|array $value Value to be normalized.
+     *
+     * @return string
+     */
+    protected function encode($value)
+    {
+        if (is_array($value)) {
+            return array_map([$this, 'encode'], $value);
+        } else {
+            return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
+        }
+    }
+
+    /**
+     * Normalize parameters.
+     *
+     * @param array $parameters Parameters to normalize.
+     *
+     * @return array
+     */
+    protected function normalizeParameters($parameters)
+    {
+        $normalized = [];
+
+        foreach ($parameters as $key => $value) {
+            // Percent symbols (%) must be double-encoded.
+            $key   = $this->encode($key);
+            $value = $this->encode($value);
+
+            $normalized[$key] = $value;
+        }
+
+        return $normalized;
+    }
+
+    /**
+     * Process filters.
+     *
+     * @param array $parameters Request parameters.
+     *
+     * @return array
+     */
+    protected function processFilters($parameters)
+    {
+        if (isset($parameters['filter'])) {
+            $filters = $parameters['filter'];
+            unset($parameters['filter']);
+            foreach ($filters as $filter => $value) {
+                $parameters['filter[' . $filter . ']'] = $value;
+            }
+        }
+
+        return $parameters;
+    }
+
+    /**
+     * Get secret.
+     *
+     * @return string
+     */
+    protected function getSecret()
+    {
+        $secret = $this->consumerSecret;
+
+        // Fix secret for v3 or later.
+        if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
+            $secret .= '&';
+        }
+
+        return $secret;
+    }
+
+    /**
+     * Generate oAuth1.0 signature.
+     *
+     * @param array $parameters Request parameters including oauth.
+     *
+     * @return string
+     */
+    protected function generateOauthSignature($parameters)
+    {
+        $baseRequestUri = \rawurlencode($this->url);
+
+        // Extract filters.
+        $parameters = $this->processFilters($parameters);
+
+        // Normalize parameter key/values and sort them.
+        $parameters = $this->normalizeParameters($parameters);
+        $parameters = $this->getSortedParameters($parameters);
+
+        // Set query string.
+        $queryString  = \implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
+        $stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
+        $secret       = $this->getSecret();
+
+        return \base64_encode(\hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
+    }
+
+    /**
+     * Creates an array of urlencoded strings out of each array key/value pairs.
+     *
+     * @param  array  $params      Array of parameters to convert.
+     * @param  array  $queryParams Array to extend.
+     * @param  string $key         Optional Array key to append
+     * @return array               Array of urlencoded strings
+     */
+    protected function joinWithEqualsSign($params, $queryParams = [], $key = '')
+    {
+        foreach ($params as $paramKey => $paramValue) {
+            if ($key) {
+                $paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
+            }
+
+            if (is_array($paramValue)) {
+                $queryParams = $this->joinWithEqualsSign($paramValue, $queryParams, $paramKey);
+            } else {
+                $string = $paramKey . '=' . $paramValue; // Join with equals sign.
+                $queryParams[] = $this->encode($string);
+            }
+        }
+
+        return $queryParams;
+    }
+
+    /**
+     * Sort parameters.
+     *
+     * @param array $parameters Parameters to sort in byte-order.
+     *
+     * @return array
+     */
+    protected function getSortedParameters($parameters)
+    {
+        \uksort($parameters, 'strcmp');
+
+        foreach ($parameters as $key => $value) {
+            if (\is_array($value)) {
+                \uksort($parameters[$key], 'strcmp');
+            }
+        }
+
+        return $parameters;
+    }
+
+    /**
+     * Get oAuth1.0 parameters.
+     *
+     * @return string
+     */
+    public function getParameters()
+    {
+        $parameters = \array_merge($this->parameters, [
+            'oauth_consumer_key'     => $this->consumerKey,
+            'oauth_timestamp'        => $this->timestamp,
+            'oauth_nonce'            => \sha1(\microtime()),
+            'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
+        ]);
+
+        // The parameters above must be included in the signature generation.
+        $parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
+
+        return $this->getSortedParameters($parameters);
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Options.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Options.php
new file mode 100644
index 000000000..3e2ce6a14
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Options.php
@@ -0,0 +1,182 @@
+options = $options;
+    }
+
+    /**
+     * Get API version.
+     *
+     * @return string
+     */
+    public function getVersion()
+    {
+        return isset($this->options['version']) ? $this->options['version'] : self::VERSION;
+    }
+
+    /**
+     * Check if need to verify SSL.
+     *
+     * @return bool
+     */
+    public function verifySsl()
+    {
+        return isset($this->options['verify_ssl']) ? (bool) $this->options['verify_ssl'] : true;
+    }
+
+    /**
+     * Only use OAuth.
+     *
+     * @return bool
+     */
+    public function isOAuthOnly()
+    {
+        return isset($this->options['oauth_only']) ? (bool) $this->options['oauth_only'] : false;
+    }
+
+    /**
+     * Get timeout.
+     *
+     * @return int
+     */
+    public function getTimeout()
+    {
+        return isset($this->options['timeout']) ? (int) $this->options['timeout'] : self::TIMEOUT;
+    }
+
+    /**
+     * Basic Authentication as query string.
+     * Some old servers are not able to use CURLOPT_USERPWD.
+     *
+     * @return bool
+     */
+    public function isQueryStringAuth()
+    {
+        return isset($this->options['query_string_auth']) ? (bool) $this->options['query_string_auth'] : false;
+    }
+
+    /**
+     * Check if is WP REST API.
+     *
+     * @return bool
+     */
+    public function isWPAPI()
+    {
+        return isset($this->options['wp_api']) ? (bool) $this->options['wp_api'] : true;
+    }
+
+    /**
+     * Custom API Prefix for WP API.
+     *
+     * @return string
+     */
+    public function apiPrefix()
+    {
+        return isset($this->options['wp_api_prefix']) ? $this->options['wp_api_prefix'] : self::WP_API_PREFIX;
+    }
+
+    /**
+     * oAuth timestamp.
+     *
+     * @return string
+     */
+    public function oauthTimestamp()
+    {
+        return isset($this->options['oauth_timestamp']) ? $this->options['oauth_timestamp'] : \time();
+    }
+
+    /**
+     * Custom user agent.
+     *
+     * @return string
+     */
+    public function userAgent()
+    {
+        return isset($this->options['user_agent']) ? $this->options['user_agent'] : self::USER_AGENT;
+    }
+
+    /**
+     * Get follow redirects.
+     *
+     * @return bool
+     */
+    public function getFollowRedirects()
+    {
+        return isset($this->options['follow_redirects']) ? (bool) $this->options['follow_redirects'] : false;
+    }
+
+    /**
+     * Check is it needed to mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added
+     * query parameter ?_method=METHOD into URL.
+     *
+     * @return bool
+     */
+    public function isMethodOverrideQuery()
+    {
+        return isset($this->options['method_override_query']) && $this->options['method_override_query'];
+    }
+
+    /**
+     * Check is it needed to mask all non-GET/POST methods (PUT/DELETE/etc.) by using POST method with added
+     * "X-HTTP-Method-Override: METHOD" HTTP header into request.
+     *
+     * @return bool
+     */
+    public function isMethodOverrideHeader()
+    {
+        return isset($this->options['method_override_header']) && $this->options['method_override_header'];
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Request.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Request.php
new file mode 100644
index 000000000..b9db6bd16
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Request.php
@@ -0,0 +1,187 @@
+url        = $url;
+        $this->method     = $method;
+        $this->parameters = $parameters;
+        $this->headers    = $headers;
+        $this->body       = $body;
+    }
+
+    /**
+     * Set url.
+     *
+     * @param string $url Request url.
+     */
+    public function setUrl($url)
+    {
+        $this->url = $url;
+    }
+
+    /**
+     * Set method.
+     *
+     * @param string $method Request method.
+     */
+    public function setMethod($method)
+    {
+        $this->method = $method;
+    }
+
+    /**
+     * Set parameters.
+     *
+     * @param array $parameters Request paramenters.
+     */
+    public function setParameters($parameters)
+    {
+        $this->parameters = $parameters;
+    }
+
+    /**
+     * Set headers.
+     *
+     * @param array $headers Request headers.
+     */
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+    }
+
+    /**
+     * Set body.
+     *
+     * @param string $body Request body.
+     */
+    public function setBody($body)
+    {
+        $this->body = $body;
+    }
+
+    /**
+     * Get url.
+     *
+     * @return string
+     */
+    public function getUrl()
+    {
+        return $this->url;
+    }
+
+    /**
+     * Get method.
+     *
+     * @return string
+     */
+    public function getMethod()
+    {
+        return $this->method;
+    }
+
+    /**
+     * Get parameters.
+     *
+     * @return array
+     */
+    public function getParameters()
+    {
+        return $this->parameters;
+    }
+
+    /**
+     * Get headers.
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Get raw headers.
+     *
+     * @return array
+     */
+    public function getRawHeaders()
+    {
+        $headers = [];
+
+        foreach ($this->headers as $key => $value) {
+            $headers[] = $key . ': ' . $value;
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Get body.
+     *
+     * @return string
+     */
+    public function getBody()
+    {
+        return $this->body;
+    }
+}
diff --git a/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Response.php b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Response.php
new file mode 100644
index 000000000..b5a5de19c
--- /dev/null
+++ b/vendor/automattic/woocommerce/src/WooCommerce/HttpClient/Response.php
@@ -0,0 +1,127 @@
+code    = $code;
+        $this->headers = $headers;
+        $this->body    = $body;
+    }
+
+    /**
+     * To string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return \json_encode([
+            'code'    => $this->code,
+            'headers' => $this->headers,
+            'body'    => $this->body,
+        ]);
+    }
+
+    /**
+     * Set code.
+     *
+     * @param int $code Response code.
+     */
+    public function setCode($code)
+    {
+        $this->code = (int) $code;
+    }
+
+    /**
+     * Set headers.
+     *
+     * @param array $headers Response headers.
+     */
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+    }
+
+    /**
+     * Set body.
+     *
+     * @param string $body Response body.
+     */
+    public function setBody($body)
+    {
+        $this->body = $body;
+    }
+
+    /**
+     * Get code.
+     *
+     * @return int
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * Get headers.
+     *
+     * @return array $headers Response headers.
+     */
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Get body.
+     *
+     * @return string $body Response body.
+     */
+    public function getBody()
+    {
+        return $this->body;
+    }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
index 6d29bff66..2052022fd 100644
--- a/vendor/composer/InstalledVersions.php
+++ b/vendor/composer/InstalledVersions.php
@@ -26,6 +26,12 @@
  */
 class InstalledVersions
 {
+    /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
     /**
      * @var mixed[]|null
      * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
@@ -322,6 +328,18 @@ public static function reload($data)
         self::$installedIsLocalDir = false;
     }
 
+    /**
+     * @return string
+     */
+    private static function getSelfDir()
+    {
+        if (self::$selfDir === null) {
+            self::$selfDir = strtr(__DIR__, '\\', '/');
+        }
+
+        return self::$selfDir;
+    }
+
     /**
      * @return array[]
      * @psalm-return list}>
@@ -336,7 +354,7 @@ private static function getInstalled()
         $copiedLocalDir = false;
 
         if (self::$canGetVendors) {
-            $selfDir = strtr(__DIR__, '\\', '/');
+            $selfDir = self::getSelfDir();
             foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                 $vendorDir = strtr($vendorDir, '\\', '/');
                 if (isset(self::$installedByVendor[$vendorDir])) {
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index c0eb26162..fbde08bae 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -6,6 +6,29 @@
 $baseDir = dirname($vendorDir);
 
 return array(
+    'AWS\\CRT\\Auth\\AwsCredentials' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php',
+    'AWS\\CRT\\Auth\\CredentialsProvider' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php',
+    'AWS\\CRT\\Auth\\Signable' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php',
+    'AWS\\CRT\\Auth\\SignatureType' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php',
+    'AWS\\CRT\\Auth\\SignedBodyHeaderType' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/SignedBodyHeaderType.php',
+    'AWS\\CRT\\Auth\\Signing' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/Signing.php',
+    'AWS\\CRT\\Auth\\SigningAlgorithm' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php',
+    'AWS\\CRT\\Auth\\SigningConfigAWS' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningConfigAWS.php',
+    'AWS\\CRT\\Auth\\SigningResult' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php',
+    'AWS\\CRT\\Auth\\StaticCredentialsProvider' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php',
+    'AWS\\CRT\\CRT' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/CRT.php',
+    'AWS\\CRT\\HTTP\\Headers' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php',
+    'AWS\\CRT\\HTTP\\Message' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php',
+    'AWS\\CRT\\HTTP\\Request' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php',
+    'AWS\\CRT\\HTTP\\Response' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php',
+    'AWS\\CRT\\IO\\EventLoopGroup' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php',
+    'AWS\\CRT\\IO\\InputStream' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php',
+    'AWS\\CRT\\Internal\\Encoding' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php',
+    'AWS\\CRT\\Internal\\Extension' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Internal/Extension.php',
+    'AWS\\CRT\\Log' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Log.php',
+    'AWS\\CRT\\NativeResource' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/NativeResource.php',
+    'AWS\\CRT\\OptionValue' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Options.php',
+    'AWS\\CRT\\Options' => $vendorDir . '/aws/aws-crt-php/src/AWS/CRT/Options.php',
     'Aura\\SqlQuery\\AbstractDmlQuery' => $vendorDir . '/aura/sqlquery/src/AbstractDmlQuery.php',
     'Aura\\SqlQuery\\AbstractQuery' => $vendorDir . '/aura/sqlquery/src/AbstractQuery.php',
     'Aura\\SqlQuery\\Common\\Delete' => $vendorDir . '/aura/sqlquery/src/Common/Delete.php',
@@ -43,6 +66,14 @@
     'Aura\\SqlQuery\\Sqlsrv\\Insert' => $vendorDir . '/aura/sqlquery/src/Sqlsrv/Insert.php',
     'Aura\\SqlQuery\\Sqlsrv\\Select' => $vendorDir . '/aura/sqlquery/src/Sqlsrv/Select.php',
     'Aura\\SqlQuery\\Sqlsrv\\Update' => $vendorDir . '/aura/sqlquery/src/Sqlsrv/Update.php',
+    'Automattic\\WooCommerce\\Client' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/Client.php',
+    'Automattic\\WooCommerce\\HttpClient\\BasicAuth' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/BasicAuth.php',
+    'Automattic\\WooCommerce\\HttpClient\\HttpClient' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClient.php',
+    'Automattic\\WooCommerce\\HttpClient\\HttpClientException' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClientException.php',
+    'Automattic\\WooCommerce\\HttpClient\\OAuth' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/OAuth.php',
+    'Automattic\\WooCommerce\\HttpClient\\Options' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/Options.php',
+    'Automattic\\WooCommerce\\HttpClient\\Request' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/Request.php',
+    'Automattic\\WooCommerce\\HttpClient\\Response' => $vendorDir . '/automattic/woocommerce/src/WooCommerce/HttpClient/Response.php',
     'Aws\\ACMPCA\\ACMPCAClient' => $vendorDir . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php',
     'Aws\\ACMPCA\\Exception\\ACMPCAException' => $vendorDir . '/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php',
     'Aws\\AbstractConfigurationProvider' => $vendorDir . '/aws/aws-sdk-php/src/AbstractConfigurationProvider.php',
@@ -2514,8 +2545,6 @@
     'Xentral\\Components\\Mailer\\Transport\\PhpMailerOAuth' => $baseDir . '/classes/Components/Mailer/Transport/PhpMailerOAuth.php',
     'Xentral\\Components\\Mailer\\Transport\\PhpMailerOAuthAuthentificationInterface' => $baseDir . '/classes/Components/Mailer/Transport/PhpMailerOAuthAuthentificationInterface.php',
     'Xentral\\Components\\Mailer\\Transport\\PhpMailerTransport' => $baseDir . '/classes/Components/Mailer/Transport/PhpMailerTransport.php',
-    'Xentral\\Components\\Mailer\\Wrapper\\LoggerWrapper' => $baseDir . '/classes/Components/Mailer/Wrapper/LoggerWrapper.php',
-    'Xentral\\Components\\Mailer\\Wrapper\\MemoryLogger' => $baseDir . '/classes/Components/Mailer/Wrapper/MemoryLogger.php',
     'Xentral\\Components\\Pdf\\Bootstrap' => $baseDir . '/classes/Components/Pdf/Bootstrap.php',
     'Xentral\\Components\\Pdf\\Exception\\FileExistsException' => $baseDir . '/classes/Components/Pdf/Exception/FileExistsException.php',
     'Xentral\\Components\\Pdf\\Exception\\FileNotFoundException' => $baseDir . '/classes/Components/Pdf/Exception/FileNotFoundException.php',
@@ -2634,6 +2663,7 @@
     'Xentral\\Components\\Util\\Exception\\StringUtilException' => $baseDir . '/classes/Components/Util/Exception/StringUtilException.php',
     'Xentral\\Components\\Util\\Exception\\UtilExceptionInterface' => $baseDir . '/classes/Components/Util/Exception/UtilExceptionInterface.php',
     'Xentral\\Components\\Util\\StringUtil' => $baseDir . '/classes/Components/Util/StringUtil.php',
+    'Xentral\\Components\\WooCommerce\\ClientWrapper' => $baseDir . '/classes/Components/WooCommerce/ClientWrapper.php',
     'Xentral\\Core\\DependencyInjection\\AbstractBaseContainer' => $baseDir . '/classes/Core/DependencyInjection/AbstractBaseContainer.php',
     'Xentral\\Core\\DependencyInjection\\ContainerInterface' => $baseDir . '/classes/Core/DependencyInjection/ContainerInterface.php',
     'Xentral\\Core\\DependencyInjection\\Definition\\FactoryMethodDefinition' => $baseDir . '/classes/Core/DependencyInjection/Definition/FactoryMethodDefinition.php',
@@ -2951,6 +2981,7 @@
     'Xentral\\Modules\\Datanorm\\Service\\DatanormReader' => $baseDir . '/classes/Modules/Datanorm/Service/DatanormReader.php',
     'Xentral\\Modules\\Datanorm\\Wrapper\\AddressWrapper' => $baseDir . '/classes/Modules/Datanorm/Wrapper/AddressWrapper.php',
     'Xentral\\Modules\\DatevApi\\DataTable\\DatevExportDataTable' => $baseDir . '/classes/Modules/DatevApi/DataTable/DatevExportDataTable.php',
+    'Xentral\\Modules\\DatevExport\\DatevExport' => $baseDir . '/classes/Modules/DatevExport/DatevExport.php',
     'Xentral\\Modules\\DemoExporter\\Bootstrap' => $baseDir . '/classes/Modules/DemoExporter/Bootstrap.php',
     'Xentral\\Modules\\DemoExporter\\DemoExporterCleanerService' => $baseDir . '/classes/Modules/DemoExporter/DemoExporterCleanerService.php',
     'Xentral\\Modules\\DemoExporter\\DemoExporterDateiService' => $baseDir . '/classes/Modules/DemoExporter/DemoExporterDateiService.php',
@@ -3291,7 +3322,6 @@
     'Xentral\\Modules\\Log\\Service\\DatabaseLogGateway' => $baseDir . '/classes/Modules/Log/Service/DatabaseLogGateway.php',
     'Xentral\\Modules\\Log\\Service\\DatabaseLogService' => $baseDir . '/classes/Modules/Log/Service/DatabaseLogService.php',
     'Xentral\\Modules\\Log\\Service\\LoggerConfigService' => $baseDir . '/classes/Modules/Log/Service/LoggerConfigService.php',
-    'Xentral\\Modules\\Log\\Wrapper\\CompanyConfigWrapper' => $baseDir . '/classes/Modules/Log/Wrapper/CompanyConfigWrapper.php',
     'Xentral\\Modules\\MandatoryFields\\Bootstrap' => $baseDir . '/classes/Modules/MandatoryFields/Bootstrap.php',
     'Xentral\\Modules\\MandatoryFields\\Data\\MandatoryFieldData' => $baseDir . '/classes/Modules/MandatoryFields/Data/MandatoryFieldData.php',
     'Xentral\\Modules\\MandatoryFields\\Data\\ValidatorResultData' => $baseDir . '/classes/Modules/MandatoryFields/Data/ValidatorResultData.php',
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index aa47ba3c3..6ff597737 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -49,6 +49,7 @@
     'Datto\\JsonRpc\\Http\\' => array($vendorDir . '/datto/json-rpc-http/src'),
     'Datto\\JsonRpc\\' => array($vendorDir . '/datto/json-rpc/src'),
     'Aws\\' => array($vendorDir . '/aws/aws-sdk-php/src'),
+    'Automattic\\WooCommerce\\' => array($vendorDir . '/automattic/woocommerce/src/WooCommerce'),
     'Aura\\SqlQuery\\' => array($vendorDir . '/aura/sqlquery/src'),
     '' => array($vendorDir . '/league/color-extractor/src'),
 );
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
index adb873a96..ac10ca409 100644
--- a/vendor/composer/autoload_real.php
+++ b/vendor/composer/autoload_real.php
@@ -22,8 +22,6 @@ public static function getLoader()
             return self::$loader;
         }
 
-        require __DIR__ . '/platform_check.php';
-
         spl_autoload_register(array('ComposerAutoloaderInit0c49a81c1214ef2f7493c6ce921b17ee', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
         spl_autoload_unregister(array('ComposerAutoloaderInit0c49a81c1214ef2f7493c6ce921b17ee', 'loadClassLoader'));
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 449b91726..c6768b765 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -29,23 +29,23 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
     );
 
     public static $prefixLengthsPsr4 = array (
-        'l' => 
+        'l' =>
         array (
             'lfkeitel\\phptotp\\' => 17,
         ),
-        'Y' => 
+        'Y' =>
         array (
             'Y0lk\\OAuth1\\Client\\Server\\' => 26,
         ),
-        'X' => 
+        'X' =>
         array (
             'Xentral\\' => 8,
         ),
-        'W' => 
+        'W' =>
         array (
             'Webmozart\\Assert\\' => 17,
         ),
-        'S' => 
+        'S' =>
         array (
             'Symfony\\Polyfill\\Mbstring\\' => 26,
             'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
@@ -63,18 +63,18 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
             'Sabre\\CardDAV\\' => 14,
             'Sabre\\CalDAV\\' => 13,
         ),
-        'R' => 
+        'R' =>
         array (
             'Rakit\\Validation\\' => 17,
         ),
-        'P' => 
+        'P' =>
         array (
             'Psr\\Log\\' => 8,
             'Psr\\Http\\Message\\' => 17,
             'Psr\\Container\\' => 14,
             'PHPMailer\\PHPMailer\\' => 20,
         ),
-        'L' => 
+        'L' =>
         array (
             'League\\OAuth1\\Client\\' => 21,
             'League\\MimeTypeDetection\\' => 25,
@@ -86,208 +86,213 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
             'Laminas\\Mail\\' => 13,
             'Laminas\\Loader\\' => 15,
         ),
-        'J' => 
+        'J' =>
         array (
             'JmesPath\\' => 9,
         ),
-        'G' => 
+        'G' =>
         array (
             'GuzzleHttp\\Psr7\\' => 16,
             'GuzzleHttp\\Promise\\' => 19,
             'GuzzleHttp\\' => 11,
         ),
-        'F' => 
+        'F' =>
         array (
             'FiskalyClient\\' => 14,
             'FastRoute\\' => 10,
         ),
-        'D' => 
+        'D' =>
         array (
             'Datto\\JsonRpc\\Http\\Examples\\' => 28,
             'Datto\\JsonRpc\\Http\\' => 19,
             'Datto\\JsonRpc\\' => 14,
         ),
-        'A' => 
+        'A' =>
         array (
             'Aws\\' => 4,
+            'Automattic\\WooCommerce\\' => 23,
             'Aura\\SqlQuery\\' => 14,
         ),
     );
 
     public static $prefixDirsPsr4 = array (
-        'lfkeitel\\phptotp\\' => 
+        'lfkeitel\\phptotp\\' =>
         array (
             0 => __DIR__ . '/..' . '/lfkeitel/phptotp/src',
         ),
-        'Y0lk\\OAuth1\\Client\\Server\\' => 
+        'Y0lk\\OAuth1\\Client\\Server\\' =>
         array (
             0 => __DIR__ . '/..' . '/y0lk/oauth1-etsy/src',
         ),
-        'Xentral\\' => 
+        'Xentral\\' =>
         array (
             0 => __DIR__ . '/../..' . '/classes',
         ),
-        'Webmozart\\Assert\\' => 
+        'Webmozart\\Assert\\' =>
         array (
             0 => __DIR__ . '/..' . '/webmozart/assert/src',
         ),
-        'Symfony\\Polyfill\\Mbstring\\' => 
+        'Symfony\\Polyfill\\Mbstring\\' =>
         array (
             0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
         ),
-        'Symfony\\Polyfill\\Intl\\Normalizer\\' => 
+        'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
         array (
             0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
         ),
-        'Symfony\\Polyfill\\Intl\\Idn\\' => 
+        'Symfony\\Polyfill\\Intl\\Idn\\' =>
         array (
             0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
         ),
-        'SwissPaymentSlip\\SwissPaymentSlip\\' => 
+        'SwissPaymentSlip\\SwissPaymentSlip\\' =>
         array (
             0 => __DIR__ . '/..' . '/swiss-payment-slip/swiss-payment-slip/src',
         ),
-        'SwissPaymentSlip\\SwissPaymentSlipPdf\\' => 
+        'SwissPaymentSlip\\SwissPaymentSlipPdf\\' =>
         array (
             0 => __DIR__ . '/..' . '/swiss-payment-slip/swiss-payment-slip-pdf/src',
         ),
-        'SwissPaymentSlip\\SwissPaymentSlipFpdf\\' => 
+        'SwissPaymentSlip\\SwissPaymentSlipFpdf\\' =>
         array (
             0 => __DIR__ . '/..' . '/swiss-payment-slip/swiss-payment-slip-fpdf/src',
         ),
-        'Sabre\\Xml\\' => 
+        'Sabre\\Xml\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/xml/lib',
         ),
-        'Sabre\\VObject\\' => 
+        'Sabre\\VObject\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/vobject/lib',
         ),
-        'Sabre\\Uri\\' => 
+        'Sabre\\Uri\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/uri/lib',
         ),
-        'Sabre\\HTTP\\' => 
+        'Sabre\\HTTP\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/http/lib',
         ),
-        'Sabre\\Event\\' => 
+        'Sabre\\Event\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/event/lib',
         ),
-        'Sabre\\DAV\\' => 
+        'Sabre\\DAV\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV',
         ),
-        'Sabre\\DAVACL\\' => 
+        'Sabre\\DAVACL\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL',
         ),
-        'Sabre\\CardDAV\\' => 
+        'Sabre\\CardDAV\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV',
         ),
-        'Sabre\\CalDAV\\' => 
+        'Sabre\\CalDAV\\' =>
         array (
             0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV',
         ),
-        'Rakit\\Validation\\' => 
+        'Rakit\\Validation\\' =>
         array (
             0 => __DIR__ . '/..' . '/rakit/validation/src',
         ),
-        'Psr\\Log\\' => 
+        'Psr\\Log\\' =>
         array (
             0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
         ),
-        'Psr\\Http\\Message\\' => 
+        'Psr\\Http\\Message\\' =>
         array (
             0 => __DIR__ . '/..' . '/psr/http-message/src',
         ),
-        'Psr\\Container\\' => 
+        'Psr\\Container\\' =>
         array (
             0 => __DIR__ . '/..' . '/psr/container/src',
         ),
-        'PHPMailer\\PHPMailer\\' => 
+        'PHPMailer\\PHPMailer\\' =>
         array (
             0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
         ),
-        'League\\OAuth1\\Client\\' => 
+        'League\\OAuth1\\Client\\' =>
         array (
             0 => __DIR__ . '/..' . '/league/oauth1-client/src',
         ),
-        'League\\MimeTypeDetection\\' => 
+        'League\\MimeTypeDetection\\' =>
         array (
             0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
         ),
-        'League\\Flysystem\\' => 
+        'League\\Flysystem\\' =>
         array (
             0 => __DIR__ . '/..' . '/league/flysystem/src',
         ),
-        'Laminas\\Validator\\' => 
+        'Laminas\\Validator\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-validator/src',
         ),
-        'Laminas\\Stdlib\\' => 
+        'Laminas\\Stdlib\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-stdlib/src',
         ),
-        'Laminas\\ServiceManager\\' => 
+        'Laminas\\ServiceManager\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src',
         ),
-        'Laminas\\Mime\\' => 
+        'Laminas\\Mime\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-mime/src',
         ),
-        'Laminas\\Mail\\' => 
+        'Laminas\\Mail\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-mail/src',
         ),
-        'Laminas\\Loader\\' => 
+        'Laminas\\Loader\\' =>
         array (
             0 => __DIR__ . '/..' . '/laminas/laminas-loader/src',
         ),
-        'JmesPath\\' => 
+        'JmesPath\\' =>
         array (
             0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src',
         ),
-        'GuzzleHttp\\Psr7\\' => 
+        'GuzzleHttp\\Psr7\\' =>
         array (
             0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
         ),
-        'GuzzleHttp\\Promise\\' => 
+        'GuzzleHttp\\Promise\\' =>
         array (
             0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
         ),
-        'GuzzleHttp\\' => 
+        'GuzzleHttp\\' =>
         array (
             0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
         ),
-        'FiskalyClient\\' => 
+        'FiskalyClient\\' =>
         array (
             0 => __DIR__ . '/..' . '/fiskaly/fiskaly-sdk-php/src',
         ),
-        'FastRoute\\' => 
+        'FastRoute\\' =>
         array (
             0 => __DIR__ . '/..' . '/nikic/fast-route/src',
         ),
-        'Datto\\JsonRpc\\Http\\Examples\\' => 
+        'Datto\\JsonRpc\\Http\\Examples\\' =>
         array (
             0 => __DIR__ . '/..' . '/datto/json-rpc-http/examples/src',
         ),
-        'Datto\\JsonRpc\\Http\\' => 
+        'Datto\\JsonRpc\\Http\\' =>
         array (
             0 => __DIR__ . '/..' . '/datto/json-rpc-http/src',
         ),
-        'Datto\\JsonRpc\\' => 
+        'Datto\\JsonRpc\\' =>
         array (
             0 => __DIR__ . '/..' . '/datto/json-rpc/src',
         ),
-        'Aws\\' => 
+        'Aws\\' =>
         array (
             0 => __DIR__ . '/..' . '/aws/aws-sdk-php/src',
         ),
-        'Aura\\SqlQuery\\' => 
+        'Automattic\\WooCommerce\\' =>
+        array (
+            0 => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce',
+        ),
+        'Aura\\SqlQuery\\' =>
         array (
             0 => __DIR__ . '/..' . '/aura/sqlquery/src',
         ),
@@ -298,9 +303,9 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
     );
 
     public static $prefixesPsr0 = array (
-        'H' => 
+        'H' =>
         array (
-            'HTMLPurifier' => 
+            'HTMLPurifier' =>
             array (
                 0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
             ),
@@ -308,6 +313,29 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
     );
 
     public static $classMap = array (
+        'AWS\\CRT\\Auth\\AwsCredentials' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php',
+        'AWS\\CRT\\Auth\\CredentialsProvider' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php',
+        'AWS\\CRT\\Auth\\Signable' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php',
+        'AWS\\CRT\\Auth\\SignatureType' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php',
+        'AWS\\CRT\\Auth\\SignedBodyHeaderType' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/SignedBodyHeaderType.php',
+        'AWS\\CRT\\Auth\\Signing' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/Signing.php',
+        'AWS\\CRT\\Auth\\SigningAlgorithm' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php',
+        'AWS\\CRT\\Auth\\SigningConfigAWS' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningConfigAWS.php',
+        'AWS\\CRT\\Auth\\SigningResult' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php',
+        'AWS\\CRT\\Auth\\StaticCredentialsProvider' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php',
+        'AWS\\CRT\\CRT' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/CRT.php',
+        'AWS\\CRT\\HTTP\\Headers' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php',
+        'AWS\\CRT\\HTTP\\Message' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php',
+        'AWS\\CRT\\HTTP\\Request' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php',
+        'AWS\\CRT\\HTTP\\Response' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php',
+        'AWS\\CRT\\IO\\EventLoopGroup' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php',
+        'AWS\\CRT\\IO\\InputStream' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php',
+        'AWS\\CRT\\Internal\\Encoding' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php',
+        'AWS\\CRT\\Internal\\Extension' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Internal/Extension.php',
+        'AWS\\CRT\\Log' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Log.php',
+        'AWS\\CRT\\NativeResource' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/NativeResource.php',
+        'AWS\\CRT\\OptionValue' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Options.php',
+        'AWS\\CRT\\Options' => __DIR__ . '/..' . '/aws/aws-crt-php/src/AWS/CRT/Options.php',
         'Aura\\SqlQuery\\AbstractDmlQuery' => __DIR__ . '/..' . '/aura/sqlquery/src/AbstractDmlQuery.php',
         'Aura\\SqlQuery\\AbstractQuery' => __DIR__ . '/..' . '/aura/sqlquery/src/AbstractQuery.php',
         'Aura\\SqlQuery\\Common\\Delete' => __DIR__ . '/..' . '/aura/sqlquery/src/Common/Delete.php',
@@ -345,6 +373,14 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
         'Aura\\SqlQuery\\Sqlsrv\\Insert' => __DIR__ . '/..' . '/aura/sqlquery/src/Sqlsrv/Insert.php',
         'Aura\\SqlQuery\\Sqlsrv\\Select' => __DIR__ . '/..' . '/aura/sqlquery/src/Sqlsrv/Select.php',
         'Aura\\SqlQuery\\Sqlsrv\\Update' => __DIR__ . '/..' . '/aura/sqlquery/src/Sqlsrv/Update.php',
+        'Automattic\\WooCommerce\\Client' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/Client.php',
+        'Automattic\\WooCommerce\\HttpClient\\BasicAuth' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/BasicAuth.php',
+        'Automattic\\WooCommerce\\HttpClient\\HttpClient' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClient.php',
+        'Automattic\\WooCommerce\\HttpClient\\HttpClientException' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/HttpClientException.php',
+        'Automattic\\WooCommerce\\HttpClient\\OAuth' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/OAuth.php',
+        'Automattic\\WooCommerce\\HttpClient\\Options' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/Options.php',
+        'Automattic\\WooCommerce\\HttpClient\\Request' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/Request.php',
+        'Automattic\\WooCommerce\\HttpClient\\Response' => __DIR__ . '/..' . '/automattic/woocommerce/src/WooCommerce/HttpClient/Response.php',
         'Aws\\ACMPCA\\ACMPCAClient' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php',
         'Aws\\ACMPCA\\Exception\\ACMPCAException' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php',
         'Aws\\AbstractConfigurationProvider' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/AbstractConfigurationProvider.php',
@@ -2816,8 +2852,6 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
         'Xentral\\Components\\Mailer\\Transport\\PhpMailerOAuth' => __DIR__ . '/../..' . '/classes/Components/Mailer/Transport/PhpMailerOAuth.php',
         'Xentral\\Components\\Mailer\\Transport\\PhpMailerOAuthAuthentificationInterface' => __DIR__ . '/../..' . '/classes/Components/Mailer/Transport/PhpMailerOAuthAuthentificationInterface.php',
         'Xentral\\Components\\Mailer\\Transport\\PhpMailerTransport' => __DIR__ . '/../..' . '/classes/Components/Mailer/Transport/PhpMailerTransport.php',
-        'Xentral\\Components\\Mailer\\Wrapper\\LoggerWrapper' => __DIR__ . '/../..' . '/classes/Components/Mailer/Wrapper/LoggerWrapper.php',
-        'Xentral\\Components\\Mailer\\Wrapper\\MemoryLogger' => __DIR__ . '/../..' . '/classes/Components/Mailer/Wrapper/MemoryLogger.php',
         'Xentral\\Components\\Pdf\\Bootstrap' => __DIR__ . '/../..' . '/classes/Components/Pdf/Bootstrap.php',
         'Xentral\\Components\\Pdf\\Exception\\FileExistsException' => __DIR__ . '/../..' . '/classes/Components/Pdf/Exception/FileExistsException.php',
         'Xentral\\Components\\Pdf\\Exception\\FileNotFoundException' => __DIR__ . '/../..' . '/classes/Components/Pdf/Exception/FileNotFoundException.php',
@@ -2936,6 +2970,7 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
         'Xentral\\Components\\Util\\Exception\\StringUtilException' => __DIR__ . '/../..' . '/classes/Components/Util/Exception/StringUtilException.php',
         'Xentral\\Components\\Util\\Exception\\UtilExceptionInterface' => __DIR__ . '/../..' . '/classes/Components/Util/Exception/UtilExceptionInterface.php',
         'Xentral\\Components\\Util\\StringUtil' => __DIR__ . '/../..' . '/classes/Components/Util/StringUtil.php',
+        'Xentral\\Components\\WooCommerce\\ClientWrapper' => __DIR__ . '/../..' . '/classes/Components/WooCommerce/ClientWrapper.php',
         'Xentral\\Core\\DependencyInjection\\AbstractBaseContainer' => __DIR__ . '/../..' . '/classes/Core/DependencyInjection/AbstractBaseContainer.php',
         'Xentral\\Core\\DependencyInjection\\ContainerInterface' => __DIR__ . '/../..' . '/classes/Core/DependencyInjection/ContainerInterface.php',
         'Xentral\\Core\\DependencyInjection\\Definition\\FactoryMethodDefinition' => __DIR__ . '/../..' . '/classes/Core/DependencyInjection/Definition/FactoryMethodDefinition.php',
@@ -3253,6 +3288,7 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
         'Xentral\\Modules\\Datanorm\\Service\\DatanormReader' => __DIR__ . '/../..' . '/classes/Modules/Datanorm/Service/DatanormReader.php',
         'Xentral\\Modules\\Datanorm\\Wrapper\\AddressWrapper' => __DIR__ . '/../..' . '/classes/Modules/Datanorm/Wrapper/AddressWrapper.php',
         'Xentral\\Modules\\DatevApi\\DataTable\\DatevExportDataTable' => __DIR__ . '/../..' . '/classes/Modules/DatevApi/DataTable/DatevExportDataTable.php',
+        'Xentral\\Modules\\DatevExport\\DatevExport' => __DIR__ . '/../..' . '/classes/Modules/DatevExport/DatevExport.php',
         'Xentral\\Modules\\DemoExporter\\Bootstrap' => __DIR__ . '/../..' . '/classes/Modules/DemoExporter/Bootstrap.php',
         'Xentral\\Modules\\DemoExporter\\DemoExporterCleanerService' => __DIR__ . '/../..' . '/classes/Modules/DemoExporter/DemoExporterCleanerService.php',
         'Xentral\\Modules\\DemoExporter\\DemoExporterDateiService' => __DIR__ . '/../..' . '/classes/Modules/DemoExporter/DemoExporterDateiService.php',
@@ -3593,7 +3629,6 @@ class ComposerStaticInit0c49a81c1214ef2f7493c6ce921b17ee
         'Xentral\\Modules\\Log\\Service\\DatabaseLogGateway' => __DIR__ . '/../..' . '/classes/Modules/Log/Service/DatabaseLogGateway.php',
         'Xentral\\Modules\\Log\\Service\\DatabaseLogService' => __DIR__ . '/../..' . '/classes/Modules/Log/Service/DatabaseLogService.php',
         'Xentral\\Modules\\Log\\Service\\LoggerConfigService' => __DIR__ . '/../..' . '/classes/Modules/Log/Service/LoggerConfigService.php',
-        'Xentral\\Modules\\Log\\Wrapper\\CompanyConfigWrapper' => __DIR__ . '/../..' . '/classes/Modules/Log/Wrapper/CompanyConfigWrapper.php',
         'Xentral\\Modules\\MandatoryFields\\Bootstrap' => __DIR__ . '/../..' . '/classes/Modules/MandatoryFields/Bootstrap.php',
         'Xentral\\Modules\\MandatoryFields\\Data\\MandatoryFieldData' => __DIR__ . '/../..' . '/classes/Modules/MandatoryFields/Data/MandatoryFieldData.php',
         'Xentral\\Modules\\MandatoryFields\\Data\\ValidatorResultData' => __DIR__ . '/../..' . '/classes/Modules/MandatoryFields/Data/ValidatorResultData.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 8f974ba08..c0f1f1847 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -71,6 +71,119 @@
                         },
                         "install-path": "../aura/sqlquery"
                 },
+                {
+                        "name": "automattic/woocommerce",
+                        "version": "3.1.1",
+                        "version_normalized": "3.1.1.0",
+                        "source": {
+                                "type": "git",
+                                "url": "https://github.com/woocommerce/wc-api-php.git",
+                                "reference": "e378120df655b7dacbeff4756e23b41b66fe9688"
+                        },
+                        "dist": {
+                                "type": "zip",
+                                "url": "https://api.github.com/repos/woocommerce/wc-api-php/zipball/e378120df655b7dacbeff4756e23b41b66fe9688",
+                                "reference": "e378120df655b7dacbeff4756e23b41b66fe9688",
+                                "shasum": ""
+                        },
+                        "require": {
+                                "ext-curl": "*",
+                                "ext-json": "*",
+                                "php": ">= 7.4.0"
+                        },
+                        "require-dev": {
+                                "doctrine/instantiator": "^1.5.0",
+                                "phpunit/phpunit": "^9.5.0",
+                                "squizlabs/php_codesniffer": "3.*"
+                        },
+                        "time": "2026-01-30T16:26:03+00:00",
+                        "type": "library",
+                        "installation-source": "dist",
+                        "autoload": {
+                                "psr-4": {
+                                        "Automattic\\WooCommerce\\": [
+                                                "src/WooCommerce"
+                                        ]
+                                }
+                        },
+                        "notification-url": "https://packagist.org/downloads/",
+                        "license": [
+                                "MIT"
+                        ],
+                        "authors": [
+                                {
+                                        "name": "Claudio Sanches",
+                                        "email": "claudio.sanches@automattic.com"
+                                }
+                        ],
+                        "description": "A PHP wrapper for the WooCommerce REST API",
+                        "keywords": [
+                                "api",
+                                "woocommerce"
+                        ],
+                        "support": {
+                                "issues": "https://github.com/woocommerce/wc-api-php/issues",
+                                "source": "https://github.com/woocommerce/wc-api-php/tree/3.1.1"
+                        },
+                        "install-path": "../automattic/woocommerce"
+                },
+                {
+                        "name": "aws/aws-crt-php",
+                        "version": "v1.2.7",
+                        "version_normalized": "1.2.7.0",
+                        "source": {
+                                "type": "git",
+                                "url": "https://github.com/awslabs/aws-crt-php.git",
+                                "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e"
+                        },
+                        "dist": {
+                                "type": "zip",
+                                "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e",
+                                "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e",
+                                "shasum": ""
+                        },
+                        "require": {
+                                "php": ">=5.5"
+                        },
+                        "require-dev": {
+                                "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
+                                "yoast/phpunit-polyfills": "^1.0"
+                        },
+                        "suggest": {
+                                "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
+                        },
+                        "time": "2024-10-18T22:15:13+00:00",
+                        "type": "library",
+                        "installation-source": "dist",
+                        "autoload": {
+                                "classmap": [
+                                        "src/"
+                                ]
+                        },
+                        "notification-url": "https://packagist.org/downloads/",
+                        "license": [
+                                "Apache-2.0"
+                        ],
+                        "authors": [
+                                {
+                                        "name": "AWS SDK Common Runtime Team",
+                                        "email": "aws-sdk-common-runtime@amazon.com"
+                                }
+                        ],
+                        "description": "AWS Common Runtime for PHP",
+                        "homepage": "https://github.com/awslabs/aws-crt-php",
+                        "keywords": [
+                                "amazon",
+                                "aws",
+                                "crt",
+                                "sdk"
+                        ],
+                        "support": {
+                                "issues": "https://github.com/awslabs/aws-crt-php/issues",
+                                "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7"
+                        },
+                        "install-path": "../aws/aws-crt-php"
+                },
                 {
                         "name": "aws/aws-sdk-php",
                         "version": "3.175.2",
@@ -382,8 +495,9 @@
                         ],
                         "support": {
                                 "issues": "https://github.com/fiskaly/fiskaly-sdk-php/issues",
-                                "source": "https://github.com/fiskaly/fiskaly-sdk-php/tree/v1.2.100"
+                                "source": "https://github.com/fiskaly/fiskaly-sdk-php/tree/master"
                         },
+                        "abandoned": true,
                         "install-path": "../fiskaly/fiskaly-sdk-php"
                 },
                 {
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 1292bc1b3..6144f471c 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -1,9 +1,9 @@
  array(
         'name' => '__root__',
-        'pretty_version' => '1.0.0+no-version-set',
-        'version' => '1.0.0.0',
-        'reference' => null,
+        'pretty_version' => 'dev-master',
+        'version' => 'dev-master',
+        'reference' => 'fa5dd52f24c581680e2c3dc549c544db5d9c0c81',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -11,9 +11,9 @@
     ),
     'versions' => array(
         '__root__' => array(
-            'pretty_version' => '1.0.0+no-version-set',
-            'version' => '1.0.0.0',
-            'reference' => null,
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'reference' => 'fa5dd52f24c581680e2c3dc549c544db5d9c0c81',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
@@ -28,6 +28,24 @@
             'aliases' => array(),
             'dev_requirement' => false,
         ),
+        'automattic/woocommerce' => array(
+            'pretty_version' => '3.1.1',
+            'version' => '3.1.1.0',
+            'reference' => 'e378120df655b7dacbeff4756e23b41b66fe9688',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../automattic/woocommerce',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'aws/aws-crt-php' => array(
+            'pretty_version' => 'v1.2.7',
+            'version' => '1.2.7.0',
+            'reference' => 'd71d9906c7bb63a28295447ba12e74723bd3730e',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../aws/aws-crt-php',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
         'aws/aws-sdk-php' => array(
             'pretty_version' => '3.175.2',
             'version' => '3.175.2.0',
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
deleted file mode 100644
index 4c3a5d68f..000000000
--- a/vendor/composer/platform_check.php
+++ /dev/null
@@ -1,26 +0,0 @@
-= 80100)) {
-    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
-}
-
-if ($issues) {
-    if (!headers_sent()) {
-        header('HTTP/1.1 500 Internal Server Error');
-    }
-    if (!ini_get('display_errors')) {
-        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
-            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
-        } elseif (!headers_sent()) {
-            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
-        }
-    }
-    trigger_error(
-        'Composer detected issues in your platform: ' . implode(' ', $issues),
-        E_USER_ERROR
-    );
-}
diff --git a/www/pages/shopimporter_woocommerce.php b/www/pages/shopimporter_woocommerce.php
index 0d221f317..bf33fcb96 100644
--- a/www/pages/shopimporter_woocommerce.php
+++ b/www/pages/shopimporter_woocommerce.php
@@ -1,13 +1,13 @@
 app = $app;
@@ -75,135 +89,190 @@ public function ImportList()
   }
 
   /**
-   * This function returns the number of orders which have not yet been imported
+   * Returns the total number of orders pending import since the last import
+   * timestamp. Uses the WC v3 after= parameter and reads the count from
+   * the X-WP-Total response header (per_page=1 to minimise payload).
+   *
+   * @return int
    */
   public function ImportGetAuftraegeAnzahl()
   {
-    // Query the API to get new orders, filtered by the order status as specifed by the user.
-    // We set per_page to 100 - this could lead to a situation where there are more than
-    // 100 new Orders, but we still only return 100.
-
-    // Array containing additional settings, namely 'ab_nummer' (containting the next order number to get)
-    // and 'holeallestati' (an integer)
-    $tmp = $this->CatchRemoteCommand('data');
-
-    // Only orders having an order number greater or equal than this should be fetched. null otherwise
-    $number_from = empty($tmp['ab_nummer']) ? null : (int) $tmp['ab_nummer'];
-
-    // pending orders will be fetched into this array. it's length is returned at the end of the funciton
-    $pendingOrders = array();
-
-    if ($number_from) {
-      // Number-based import is selected
-
-      // The WooCommerce API doenst allow for a proper "greater than id n" request.
-      // we fake this behavior by creating an array that contains 'many' (~ 1000) consecutive
-      // ids that are greater than $from_number and use this array with the 'include' property
-      // of the WooCommerce API
-
-      $number_to = $number_from + 800;
-      if (!empty($tmp['bis_nummer'])) {
-        $number_to = $tmp['bis_nummer'];
-      }
-
-      $fakeGreaterThanIds = range($number_from, $number_to);
+    $this->migrateAbNummerIfNeeded();
 
-      $pendingOrders = $this->client->get('orders', [
-        'per_page' => 100,
-        'include' => implode(",", $fakeGreaterThanIds),
-      ]);
+    $configuredStatuses = array_map('trim', explode(';', (string) $this->statusPending));
 
+    if (!empty($this->lastImportOrderIds)) {
+      $afterTs = gmdate('Y-m-d\TH:i:s', max(0, strtotime($this->lastImportTimestamp) - 1));
+      $queryArgs = [
+        'status'   => $configuredStatuses,
+        'after'    => $afterTs,
+        'per_page' => 1,
+        'exclude'  => array_values($this->lastImportOrderIds),
+      ];
     } else {
-      // fetch posts by status
+      $queryArgs = [
+        'status'   => $configuredStatuses,
+        'after'    => $this->lastImportTimestamp,
+        'per_page' => 1,
+      ];
+    }
 
-      $pendingOrders = $this->client->get('orders', [
-        'status' => array_map('trim', explode(';', $this->statusPending)),
-        'per_page' => 100
-      ]);
+    try {
+      $this->client->get('orders', $queryArgs);
+    } catch (Exception $e) {
+      $this->logger->warning('WooCommerce ImportGetAuftraegeAnzahl: API request failed: ' . $e->getMessage());
+      return 0;
+    }
 
+    $wcResponse = $this->client->getLastResponse();
+    if ($wcResponse === null) {
+      $this->logger->warning('WooCommerce ImportGetAuftraegeAnzahl: getLastResponse() returned null');
+      return 0;
     }
 
-    return (!empty($pendingOrders) ? count($pendingOrders) : 0);
+    $total = $wcResponse->getHeader('x-wp-total');
+    if ($total === null) {
+      $this->logger->warning('WooCommerce ImportGetAuftraegeAnzahl: X-WP-Total header missing');
+      return 0;
+    }
+
+    return (int) $total;
   }
 
   /**
-   * Calling this function queries the api for pending orders and returns them
-   * as an array.
+   * Queries the WooCommerce API for the oldest pending order since the last
+   * import timestamp and returns it as a Xentral-formatted array with at most
+   * one element. The caller (shopimport.php::RemoteGetAuftrag loop) expects
+   * $result[0] per iteration; this contract must be maintained.
    *
-   * TODO: Only one single order is returned per invocation of this function.
-   * Given that we have to perform an exteremly expensive external HTTP call
-   * every time we call this function and could easily process more than one
-   * order this seems very bad performance-wise.
+   * The after-filter advances per order so each caller-iteration fetches the
+   * next order. A crash between RemoteGetAuftrag() and the shopimport_auftraege
+   * INSERT loses at most this one order (consistent with pre-#262 behaviour).
+   *
+   * @return array Array with at most one order entry, or empty array if none.
    */
   public function ImportGetAuftrag()
   {
-    // Array containing additional settings, namely 'ab_nummer' (containting the next order number to get)
-    // and 'holeallestati' (an integer)
-    $tmp = $this->CatchRemoteCommand('data');
+    $data = $this->CatchRemoteCommand('data');
 
-    // Only orders having an order number greater or equal than this should be fetched. null otherwise
-    $number_from = empty($tmp['ab_nummer']) ? null : (int) $tmp['ab_nummer'];
+    $this->migrateAbNummerIfNeeded();
 
-    // pending orders will be fetched into this array. it's length is returned at the end of the funciton
-    $pendingOrders = array();
+    $configuredStatuses = array_map('trim', explode(';', (string) $this->statusPending));
 
-    if ($number_from) {
-      // Number-based import is selected
+    if (!empty($this->lastImportOrderIds)) {
+      $afterTs = gmdate('Y-m-d\TH:i:s', max(0, strtotime($this->lastImportTimestamp) - 1));
+      $queryArgs = [
+        'status'   => $configuredStatuses,
+        'after'    => $afterTs,
+        'per_page' => 1,
+        'page'     => 1,
+        'orderby'  => 'date',
+        'order'    => 'asc',
+        'exclude'  => array_values($this->lastImportOrderIds),
+      ];
+    } else {
+      $queryArgs = [
+        'status'   => $configuredStatuses,
+        'after'    => $this->lastImportTimestamp,
+        'per_page' => 1,
+        'page'     => 1,
+        'orderby'  => 'date',
+        'order'    => 'asc',
+      ];
+    }
 
-      // The WooCommerce API doenst allow for a proper "greater than id n" request.
-      // we fake this behavior by creating an array that contains 'many' (~ 1000) consecutive
-      // ids that are greater than $from_number and use this array with the 'include' property
-      // of the WooCommerce API
+    try {
+      $pageOrders = $this->client->get('orders', $queryArgs);
+    } catch (Exception $e) {
+      $this->logger->warning('WooCommerce ImportGetAuftrag: ' . $e->getMessage());
+      return null;
+    }
 
-      $number_to = $number_from + 800;
-      if (!empty($tmp['bis_nummer'])) {
-        $number_to = $tmp['bis_nummer'];
-      }
+    if (empty($pageOrders)) {
+      return null;
+    }
 
-      $fakeGreaterThanIds = range($number_from, $number_to);
+    $wcOrder = $pageOrders[0] ?? null;
+    if ($wcOrder === null) {
+      return null;
+    }
 
-      $pendingOrders = $this->client->get('orders', [
-        'per_page' => 20,
-        'include' => implode(',', $fakeGreaterThanIds),
-        'order' => 'asc',
-        'orderby' => 'id'
-      ]);
+    $order = $this->parseOrder($wcOrder);
 
-    } else {
-      // fetch posts by status
+    // Persist tuple cursor so the next Caller-iteration advances past this order.
+    // Using ts-1s in the query means same-second peers are still fetched, while
+    // the exclude=[id] parameter prevents re-delivering this exact order.
+    if (!empty($wcOrder->date_created_gmt) && !empty($wcOrder->id)) {
+      $this->persistLastImportCursor((string) $wcOrder->date_created_gmt, (int) $wcOrder->id);
+    }
 
-      $pendingOrders = $this->client->get('orders', [
-        'status' => array_map('trim', explode(';', $this->statusPending)),
-        'per_page' => 20,
-        'order' => 'asc',
-        'orderby' => 'id'
-      ]);
+    return [[
+      'id'        => $order['auftrag'],
+      'sessionid' => '',
+      'logdatei'  => '',
+      'warenkorb' => base64_encode(serialize($order)),
+    ]];
+  }
 
+  /**
+   * Resolves a legacy WooCommerce order ID (ab_nummer) to the order's
+   * date_created_gmt timestamp for the one-shot transition from cursor-
+   * based to timestamp-based import.
+   *
+   * @param int $abNummer WooCommerce order ID
+   * @return string|null ISO-8601 UTC timestamp or null on failure
+   */
+  private function resolveAbNummerToTimestamp($abNummer)
+  {
+    try {
+      $order = $this->client->get('orders/' . $abNummer);
+    } catch (Exception $e) {
+      $this->logger->warning('WooCommerce resolveAbNummerToTimestamp(' . $abNummer . '): ' . $e->getMessage());
+      return null;
     }
 
-    // Return an empty array in case there are no orders to import
-    if ((!empty($pendingOrders) ? count($pendingOrders) : 0) === 0) {
+    if (empty($order->date_created_gmt)) {
+      $this->logger->warning('WooCommerce resolveAbNummerToTimestamp(' . $abNummer . '): date_created_gmt missing');
       return null;
     }
 
-    $tmp = [];
-
-    foreach ($pendingOrders as $pendingOrder) {
-      $wcOrder = $pendingOrder;
-      $order = $this->parseOrder($wcOrder);
-
-      if (is_null($wcOrder)) {
-        continue;
-      }
+    $ts = strtotime((string) $order->date_created_gmt);
+    if ($ts === false) {
+      return null;
+    }
+    return gmdate('Y-m-d\TH:i:s', $ts - 1);
+  }
 
-      $tmp[] = [
-        'id' => $order['auftrag'],
-        'sessionid' => '',
-        'logdatei' => '',
-        'warenkorb' => base64_encode(serialize($order)),
-      ];
+  /**
+   * Runs the one-shot legacy ab_nummer -> timestamp migration when the stored
+   * cursor is still the 30-day fallback and the caller passes an ab_nummer.
+   * Idempotent: once migrated, lastImportTimestampIsFallback flips to false
+   * and subsequent calls become no-ops.
+   */
+  private function migrateAbNummerIfNeeded()
+  {
+    if (!$this->lastImportTimestampIsFallback) {
+      return;
+    }
+    $data = $this->CatchRemoteCommand('data');
+    if (empty($data['ab_nummer'])) {
+      return;
+    }
+    $resolved = $this->resolveAbNummerToTimestamp((int) $data['ab_nummer']);
+    if ($resolved !== null) {
+      $this->persistLastImportTimestamp($resolved);
+      return;
     }
-    return $tmp;
+    // Resolution failed (order deleted, 404, missing date_created_gmt). Persist the
+    // current 30-day fallback so subsequent runs use a stable lower bound
+    // instead of sliding the window on every cron cycle.
+    $this->logger->warning(
+      sprintf(
+        'WooCommerce ab_nummer=%d konnte nicht aufgeloest werden; persistiere 30-Tage-Fallback als Cursor',
+        (int) $data['ab_nummer']
+      )
+    );
+    $this->persistLastImportTimestamp($this->lastImportTimestamp);
   }
 
   // This function searches the wcOrder for the specified WC Meta key
@@ -438,7 +507,7 @@ public function ImportDeleteAuftrag()
    * Updates the order status once payment and shipping are set to ok.
    * Also updates the order with the shipping tracking code
    * @return string
-   * @throws WCHttpClientException
+   * @throws HttpClientException
    */
   public function ImportUpdateAuftrag()
   {
@@ -492,9 +561,12 @@ public function ImportUpdateAuftrag()
   }
 
   /**
-   * This function syncs the current stock to the remote WooCommerce shop
-   * @return int
-   * @throws WCHttpClientException
+   * This function syncs the current stock to the remote WooCommerce shop.
+   * Uses WC REST v3 batch endpoints to reduce HTTP round-trips from 2n to
+   * roughly ceil(n/100) + ceil(n/100) requests.
+   *
+   * @return int Number of articles successfully synced
+   * @throws HttpClientException
    */
   public function ImportSendListLager()
   {
@@ -502,8 +574,12 @@ public function ImportSendListLager()
     $anzahl = 0;
     $ctmp = (!empty($tmp) ? count($tmp) : 0);
 
+    // --- Step 1: Collect all SKUs and compute desired stock params ---
+
+    // $pendingUpdates: sku => ['lageranzahl' => int, 'status' => string]
+    $pendingUpdates = [];
+
     for ($i = 0; $i < $ctmp; $i++) {
-      // Get important values from input data
       $artikel = $tmp[$i]['artikel'];
       if ($artikel === 'ignore') {
         continue;
@@ -517,52 +593,168 @@ public function ImportSendListLager()
       $inaktiv = $tmp[$i]['inaktiv'];
       $status = 'publish';
 
-      // Do some computations, sanitize input
-
       if ($pseudolager !== '') {
         $lageranzahl = $pseudolager;
       }
-
       if ($tmp[$i]['ausverkauft']) {
         $lageranzahl = 0;
       }
-
       if ($inaktiv) {
         $status = 'private';
       }
 
-      // get the product id that WooCommerce uses to represent the current article
-      $remoteIdInformation = $this->getShopIdBySKU($nummer);
+      $pendingUpdates[$nummer] = [
+        'lageranzahl' => $lageranzahl,
+        'status' => $status,
+      ];
+    }
+
+    if (empty($pendingUpdates)) {
+      return 0;
+    }
+
+    // --- Step 2: Bulk-resolve SKUs to WC product IDs ---
+    // WC REST v3 accepts a comma-separated list in the ?sku= parameter.
+    // We fetch in chunks of 100 to stay within per_page limits.
+
+    // $skuMap: sku => ['id' => int, 'parent' => int, 'isvariant' => bool]
+    $skuMap = [];
+    $skuChunks = array_chunk(array_keys($pendingUpdates), 100);
+
+    foreach ($skuChunks as $skuChunk) {
+      $skuCsv = implode(',', $skuChunk);
+      try {
+        $products = $this->client->get('products', [
+          'sku' => $skuCsv,
+          'per_page' => 100,
+        ]);
+      } catch (Exception $e) {
+        $this->logger->error(
+          'WooCommerce SKU-Lookup-Chunk fehlgeschlagen: ' . $e->getMessage(),
+          ['chunk_size' => count($skuChunk)]
+        );
+        continue;
+      }
+      if (!is_array($products)) {
+        continue;
+      }
+      foreach ($products as $product) {
+        if (!isset($product->sku)) {
+          continue;
+        }
+        $skuMap[$product->sku] = [
+          'id' => $product->id,
+          'parent' => $product->parent_id,
+          'isvariant' => !empty($product->parent_id),
+        ];
+      }
+    }
 
-      if (empty($remoteIdInformation['id'])) {
-        // The online shop doesnt know this article, write to log and continue with next product
+    // --- Step 3: Split into simple products and variations ---
+    // simpleItems: list of batch-update items for POST products/batch
+    // variationItems: parent_id => list of batch-update items for POST products/{parent}/variations/batch
 
-        $this->logger->error("WooCommerce Artikel $nummer wurde im Online-Shop nicht gefunden! Falsche Artikelnummer im Shop hinterlegt?");
+    $simpleItems = [];
+    $variationItems = [];
+
+    foreach ($pendingUpdates as $sku => $params) {
+      if (!isset($skuMap[$sku])) {
+        $this->logger->error(
+          "WooCommerce Artikel $sku wurde im Online-Shop nicht gefunden! Falsche Artikelnummer im Shop hinterlegt?"
+        );
         continue;
       }
 
-      // Sync settings to online store
-      $updateProductParams = [
+      $info = $skuMap[$sku];
+      $item = [
+        'id' => $info['id'],
         'manage_stock' => true,
-        'status' => $status,
-        'stock_quantity' => $lageranzahl
-        // WooCommerce doesnt have a standard property for the other values, we're ignoring them
+        'stock_quantity' => $params['lageranzahl'],
+        'status' => $params['status'],
       ];
-      if ($remoteIdInformation['isvariant']) {
-        $result = $this->client->put('products/' . $remoteIdInformation['parent'] . '/variations/' . $remoteIdInformation['id'], $updateProductParams);
+
+      if ($info['isvariant']) {
+        $variationItems[$info['parent']][] = $item;
+      } else {
+        $simpleItems[] = $item;
+      }
+    }
+
+    // --- Step 4: Send batch updates in chunks of 100, handle partial errors ---
+
+    // Simple products
+    foreach (array_chunk($simpleItems, 100) as $chunk) {
+      try {
+        $response = $this->client->post('products/batch', ['update' => $chunk]);
+        $anzahl += $this->processBatchResponse($response, 'products/batch');
+      } catch (Exception $e) {
+        $this->logger->error('WooCommerce Batch-Request fehlgeschlagen fuer products/batch: ' . $e->getMessage());
+      }
+    }
+
+    // Variations (one batch endpoint per parent product)
+    foreach ($variationItems as $parentId => $items) {
+      foreach (array_chunk($items, 100) as $chunk) {
+        $endpoint = 'products/' . $parentId . '/variations/batch';
+        try {
+          $response = $this->client->post($endpoint, ['update' => $chunk]);
+          $anzahl += $this->processBatchResponse($response, $endpoint);
+        } catch (Exception $e) {
+          $this->logger->error('WooCommerce Batch-Request fehlgeschlagen fuer ' . $endpoint . ': ' . $e->getMessage());
+        }
+      }
+    }
+
+    return $anzahl;
+  }
+
+  /**
+   * Evaluates a WC batch response object, logs per-item errors, and returns
+   * the count of successfully updated items.
+   *
+   * @param object $response Decoded JSON response from the batch endpoint.
+   * @param string $endpoint Endpoint label used in log messages.
+   * @return int Number of items reported as updated without error.
+   */
+  private function processBatchResponse($response, $endpoint)
+  {
+    $successCount = 0;
+
+    if (!is_object($response) && !is_array($response)) {
+      $this->logger->error("WooCommerce Batch-Response ungueltig fuer $endpoint");
+      return 0;
+    }
+
+    // Successful updates are in response->update
+    $updated = is_object($response) ? ($response->update ?? []) : [];
+    foreach ($updated as $item) {
+      // WC embeds per-item errors inside the update array when an item fails
+      if (isset($item->error)) {
+        $code = $item->error->code ?? '';
+        $message = $item->error->message ?? '';
+        $this->logger->error(
+          "WooCommerce Batch-Fehler ($endpoint) fuer ID {$item->id}: [$code] $message"
+        );
       } else {
-        $result = $this->client->put('products/' . $remoteIdInformation['id'], $updateProductParams);
+        $this->logger->info(
+          "WooCommerce Lagerzahlenübertragung (Batch) fuer Artikel-ID {$item->id} erfolgreich",
+          ['endpoint' => $endpoint]
+        );
+        $successCount++;
       }
+    }
 
+    // Top-level errors array (some WC versions use this)
+    $errors = is_object($response) ? ($response->errors ?? []) : [];
+    foreach ($errors as $err) {
+      $code = $err->code ?? '';
+      $message = $err->message ?? '';
       $this->logger->error(
-        "WooCommerce Lagerzahlenübertragung für Artikel: $nummer / $remoteIdInformation[id] - Anzahl: $lageranzahl",
-        [
-          'result' => $result
-        ]
+        "WooCommerce Batch-Fehler ($endpoint): [$code] $message"
       );
-      $anzahl++;
     }
-    return $anzahl;
+
+    return $successCount;
   }
 
   public function ImportStorniereAuftrag()
@@ -851,7 +1043,7 @@ public function ImportAuth()
    * @param  [type] $shopid [description]
    * @param  [type] $data   [description]
    * @return [type]         [description]
-   * @throws WCHttpClientException
+   * @throws HttpClientException
    */
   public function getKonfig($shopid, $data)
   {
@@ -879,7 +1071,7 @@ public function getKonfig($shopid, $data)
     $this->priceType = $felder['priceType'] ?? null;
 
     $this->url = $ImportWooCommerceApiUrl;
-    $this->client = new WCClient(
+    $this->client = new ClientWrapper(
       //URL des WooCommerce Rest Servers
       $ImportWooCommerceApiUrl,
       //WooCommerce API Key
@@ -891,6 +1083,108 @@ public function getKonfig($shopid, $data)
       $this->ssl_ignore
     );
 
+    $storedTimestamp = $preferences['felder']['letzter_import_timestamp'] ?? null;
+    if (!empty($storedTimestamp)) {
+      $this->lastImportTimestamp = $storedTimestamp;
+      $this->lastImportTimestampIsFallback = false;
+    } else {
+      $this->lastImportTimestamp = gmdate('Y-m-d\TH:i:s', strtotime('-30 days'));
+      $this->lastImportTimestampIsFallback = true;
+    }
+
+    $storedIds = $preferences['felder']['letzter_import_order_ids'] ?? null;
+    $this->lastImportOrderIds = is_array($storedIds)
+      ? array_values(array_filter(array_map('intval', $storedIds)))
+      : [];
+
+  }
+
+  /**
+   * Backwards-compatible wrapper: persists timestamp only (order id cleared).
+   * Use persistLastImportCursor() when both timestamp and order id are available.
+   *
+   * @param string $isoUtcDate ISO-8601 UTC timestamp, e.g. '2026-04-20T12:34:56'
+   * @return void
+   */
+  public function persistLastImportTimestamp($isoUtcDate)
+  {
+    $this->persistLastImportCursor($isoUtcDate, null);
+  }
+
+  /**
+   * Persists the tuple cursor (timestamp + accumulated order-id bucket) to
+   * shopexport.einstellungen_json. Does a read-modify-write to preserve all
+   * other fields.
+   *
+   * Bucket logic:
+   *  - $orderId === null  → migration path; ids list is cleared.
+   *  - same timestamp as stored → append $orderId to the ids list.
+   *  - new timestamp → reset ids list to [$orderId].
+   *
+   * @param string   $isoUtcDate ISO-8601 UTC timestamp, e.g. '2026-04-20T12:34:56'
+   * @param int|null $orderId    WooCommerce order ID, or null (migration path)
+   * @return void
+   */
+  public function persistLastImportCursor($isoUtcDate, $orderId = null)
+  {
+    $shopid = (int)$this->shopid;
+    // Prefer DatabaseService when available (web context), fall back to DB
+    // so this method also works in the CLI/cron context.
+    if (!empty($this->app->DatabaseService)) {
+      $einstellungen_json = $this->app->DatabaseService->selectValue(
+        "SELECT einstellungen_json FROM shopexport WHERE id = :id LIMIT 1",
+        ['id' => $shopid]
+      );
+    } else {
+      $einstellungen_json = $this->app->DB->Select(
+        "SELECT einstellungen_json FROM shopexport WHERE id = '$shopid' LIMIT 1"
+      );
+    }
+    $current = [];
+    if (!empty($einstellungen_json)) {
+      $current = json_decode($einstellungen_json, true) ?: [];
+    }
+    if (!isset($current['felder']) || !is_array($current['felder'])) {
+      $current['felder'] = [];
+    }
+
+    $previousTs  = $current['felder']['letzter_import_timestamp'] ?? null;
+    $previousIds = $current['felder']['letzter_import_order_ids'] ?? [];
+    if (!is_array($previousIds)) {
+      $previousIds = [];
+    }
+
+    // Determine ids list for the new state.
+    if ($orderId === null) {
+      // Migration path — timestamp without a concrete order-id anchor.
+      $newIds = [];
+    } elseif ($previousTs !== null && $isoUtcDate === $previousTs) {
+      // Same timestamp bucket — append id if not already present.
+      $newIds = $previousIds;
+      if (!in_array((int) $orderId, $newIds, true)) {
+        $newIds[] = (int) $orderId;
+      }
+    } else {
+      // New timestamp bucket — reset list to only this id.
+      $newIds = [(int) $orderId];
+    }
+
+    $current['felder']['letzter_import_timestamp']  = $isoUtcDate;
+    $current['felder']['letzter_import_order_ids']  = $newIds;
+
+    $jsonEncoded = $this->app->DB->real_escape_string(json_encode($current));
+    if (!empty($this->app->DatabaseService)) {
+      $this->app->DatabaseService->execute(
+        "UPDATE shopexport SET einstellungen_json = :json WHERE id = :id",
+        ['json' => json_encode($current), 'id' => $shopid]
+      );
+    } else {
+      $this->app->DB->Update(
+        "UPDATE shopexport SET einstellungen_json = '$jsonEncoded' WHERE id = '$shopid'"
+      );
+    }
+    $this->lastImportTimestamp = $isoUtcDate;
+    $this->lastImportOrderIds  = $newIds;
   }
 
   /**
@@ -926,7 +1220,7 @@ public function AuthByAssistent()
     if (empty($ImportWooCommerceApiSecret)) {
       return new JsonResponse(['error' => 'Bitte das API-Secret angeben'], JsonResponse::HTTP_BAD_REQUEST);
     }
-    $this->client = new WCClient(
+    $this->client = new ClientWrapper(
       $ImportWooCommerceApiUrl,
       $ImportWooCommerceApiKey,
       $ImportWooCommerceApiSecret,
@@ -995,7 +1289,7 @@ public function getCreateForm()
    * @param string $sku Artikelnummer
    *
    * @return array|null The WooCommerce product id of the given product, null if such a product does not exist
-   * @throws WCHttpClientException
+   * @throws HttpClientException
    */
   private function getShopIdBySKU($sku)
   {
@@ -1080,1363 +1374,3 @@ protected static function emptyString($string)
   }
 
 }
-
-class WCClient
-{
-  /**
-   * WooCommerce REST API WCClient version.
-   */
-  const VERSION = '3.0.0';
-
-  /**
-   * HttpClient instance.
-   *
-   * @var WCHttpClient
-   */
-  public $http;
-
-  /** @var Logger $logger */
-  public $logger;
-
-  public $ssl_ignore = false;
-
-  /**
-   * Initialize client.
-   *
-   * @param string $url            Store URL.
-   * @param string $consumerKey    Consumer key.
-   * @param string $consumerSecret Consumer secret.
-   * @param array  $options        WCOptions (version, timeout, verify_ssl).
-   *
-   * @throws WCHttpClientException
-   */
-  public function __construct($url, $consumerKey, $consumerSecret, $options = [], $logger, $ssl_ignore)
-  {
-    $this->http = new WCHttpClient($url, $consumerKey, $consumerSecret, $options, $logger, $ssl_ignore);
-    $this->logger = $logger;
-  }
-
-  /**
-   * POST method.
-   *
-   * @param string $endpoint API endpoint.
-   * @param array  $data     WCRequest data.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function post($endpoint, $data)
-  {
-    return $this->http->request($endpoint, 'POST', $data);
-  }
-
-  /**
-   * PUT method.
-   *
-   * @param string $endpoint API endpoint.
-   * @param array  $data     WCRequest data.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function put($endpoint, $data)
-  {
-    return $this->http->request($endpoint, 'PUT', $data);
-  }
-
-  /**
-   * GET method.
-   *
-   * @param string $endpoint   API endpoint.
-   * @param array  $parameters WCRequest parameters.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function get($endpoint, $parameters = [])
-  {
-    return $this->http->request($endpoint, 'GET', [], $parameters);
-  }
-
-  /**
-   * DELETE method.
-   *
-   * @param string $endpoint   API endpoint.
-   * @param array  $parameters WCRequest parameters.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function delete($endpoint, $parameters = [])
-  {
-    return $this->http->request($endpoint, 'DELETE', [], $parameters);
-  }
-
-  /**
-   * OPTIONS method.
-   *
-   * @param string $endpoint API endpoint.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function options($endpoint)
-  {
-    return $this->http->request($endpoint, 'OPTIONS');
-  }
-}
-
-class WCResponse
-{
-  /**
-   * WCResponse code.
-   *
-   * @var int
-   */
-  private $code;
-
-  /**
-   * WCResponse headers.
-   *
-   * @var array
-   */
-  private $headers;
-
-  /**
-   * WCResponse body.
-   *
-   * @var string
-   */
-  private $body;
-
-  /**
-   * Initialize response.
-   *
-   * @param int    $code    WCResponse code.
-   * @param array  $headers WCResponse headers.
-   * @param string $body    WCResponse body.
-   */
-  public function __construct($code = 0, $headers = [], $body = '')
-  {
-    $this->code = $code;
-    $this->headers = $headers;
-    $this->body = $body;
-  }
-
-  /**
-   * Set code.
-   *
-   * @param int $code WCResponse code.
-   */
-  public function setCode($code)
-  {
-    $this->code = (int) $code;
-  }
-
-  /**
-   * Set headers.
-   *
-   * @param array $headers WCResponse headers.
-   */
-  public function setHeaders($headers)
-  {
-    $this->headers = $headers;
-  }
-
-  /**
-   * Set body.
-   *
-   * @param string $body WCResponse body.
-   */
-  public function setBody($body)
-  {
-    $this->body = $body;
-  }
-
-  /**
-   * Get code.
-   *
-   * @return int
-   */
-  public function getCode()
-  {
-    return $this->code;
-  }
-
-  /**
-   * Get headers.
-   *
-   * @return array $headers WCResponse headers.
-   */
-  public function getHeaders()
-  {
-    return $this->headers;
-  }
-
-  /**
-   * Get body.
-   *
-   * @return string $body WCResponse body.
-   */
-  public function getBody()
-  {
-    return $this->body;
-  }
-}
-
-class WCOptions
-{
-  /**
-   * Default WooCommerce REST API version.
-   */
-  const VERSION = 'wc/v3';
-
-  /**
-   * Default request timeout.
-   */
-  const TIMEOUT = 30;
-
-  /**
-   * Default WP API prefix.
-   * Including leading and trailing slashes.
-   */
-  const WP_API_PREFIX = '/wp-json/';
-
-  /**
-   * Default User Agent.
-   * No version number.
-   */
-  const USER_AGENT = 'WooCommerce API Client-PHP';
-
-  /**
-   * WCOptions.
-   *
-   * @var array
-   */
-  private $options;
-
-  /**
-   * Initialize HTTP client options.
-   *
-   * @param array $options Client options.
-   */
-  public function __construct($options)
-  {
-    $this->options = $options;
-  }
-
-  /**
-   * Get API version.
-   *
-   * @return string
-   */
-  public function getVersion()
-  {
-    return isset($this->options['version']) ? $this->options['version'] : self::VERSION;
-  }
-
-  /**
-   * Check if need to verify SSL.
-   *
-   * @return bool
-   */
-  public function verifySsl()
-  {
-    return isset($this->options['verify_ssl']) ? (bool) $this->options['verify_ssl'] : true;
-  }
-
-  /**
-   * Get timeout.
-   *
-   * @return int
-   */
-  public function getTimeout()
-  {
-    return isset($this->options['timeout']) ? (int) $this->options['timeout'] : self::TIMEOUT;
-  }
-
-  /**
-   * Basic Authentication as query string.
-   * Some old servers are not able to use CURLOPT_USERPWD.
-   *
-   * @return bool
-   */
-  public function isQueryStringAuth()
-  {
-    return isset($this->options['query_string_auth']) ? (bool) $this->options['query_string_auth'] : false;
-  }
-
-  /**
-   * Check if is WP REST API.
-   *
-   * @return bool
-   */
-  public function isWPAPI()
-  {
-    return isset($this->options['wp_api']) ? (bool) $this->options['wp_api'] : true;
-  }
-
-  /**
-   * Custom API Prefix for WP API.
-   *
-   * @return string
-   */
-  public function apiPrefix()
-  {
-    return isset($this->options['wp_api_prefix']) ? $this->options['wp_api_prefix'] : self::WP_API_PREFIX;
-  }
-
-  /**
-   * oAuth timestamp.
-   *
-   * @return string
-   */
-  public function oauthTimestamp()
-  {
-    return isset($this->options['oauth_timestamp']) ? $this->options['oauth_timestamp'] : \time();
-  }
-
-  /**
-   * Custom user agent.
-   *
-   * @return string
-   */
-  public function userAgent()
-  {
-    return isset($this->options['user_agent']) ? $this->options['user_agent'] : self::USER_AGENT;
-  }
-
-  /**
-   * Get follow redirects
-   *
-   * @return bool
-   */
-  public function getFollowRedirects()
-  {
-    return isset($this->options['follow_redirects']) ? (bool) $this->options['follow_redirects'] : false;
-  }
-}
-
-class WCRequest
-{
-  /**
-   * WCRequest url.
-   *
-   * @var string
-   */
-  private $url;
-
-  /**
-   * WCRequest method.
-   *
-   * @var string
-   */
-  private $method;
-
-  /**
-   * WCRequest paramenters.
-   *
-   * @var array
-   */
-  private $parameters;
-
-  /**
-   * WCRequest headers.
-   *
-   * @var array
-   */
-  private $headers;
-
-  /**
-   * WCRequest body.
-   *
-   * @var string
-   */
-  private $body;
-
-  /**
-   * Initialize request.
-   *
-   * @param string $url        WCRequest url.
-   * @param string $method     WCRequest method.
-   * @param array  $parameters WCRequest paramenters.
-   * @param array  $headers    WCRequest headers.
-   * @param string $body       WCRequest body.
-   */
-  public function __construct($url = '', $method = 'POST', $parameters = [], $headers = [], $body = '')
-  {
-    $this->url = $url;
-    $this->method = $method;
-    $this->parameters = $parameters;
-    $this->headers = $headers;
-    $this->body = $body;
-  }
-
-  /**
-   * Set url.
-   *
-   * @param string $url WCRequest url.
-   */
-  public function setUrl($url)
-  {
-    $this->url = $url;
-  }
-
-  /**
-   * Set method.
-   *
-   * @param string $method WCRequest method.
-   */
-  public function setMethod($method)
-  {
-    $this->method = $method;
-  }
-
-  /**
-   * Set parameters.
-   *
-   * @param array $parameters WCRequest paramenters.
-   */
-  public function setParameters($parameters)
-  {
-    $this->parameters = $parameters;
-  }
-
-  /**
-   * Set headers.
-   *
-   * @param array $headers WCRequest headers.
-   */
-  public function setHeaders($headers)
-  {
-    $this->headers = $headers;
-  }
-
-  /**
-   * Set body.
-   *
-   * @param string $body WCRequest body.
-   */
-  public function setBody($body)
-  {
-    $this->body = $body;
-  }
-
-  /**
-   * Get url.
-   *
-   * @return string
-   */
-  public function getUrl()
-  {
-    return $this->url;
-  }
-
-  /**
-   * Get method.
-   *
-   * @return string
-   */
-  public function getMethod()
-  {
-    return $this->method;
-  }
-
-  /**
-   * Get parameters.
-   *
-   * @return array
-   */
-  public function getParameters()
-  {
-    return $this->parameters;
-  }
-
-  /**
-   * Get headers.
-   *
-   * @return array
-   */
-  public function getHeaders()
-  {
-    return $this->headers;
-  }
-
-  /**
-   * Get raw headers.
-   *
-   * @return array
-   */
-  public function getRawHeaders()
-  {
-    $headers = [];
-
-    foreach ($this->headers as $key => $value) {
-      $headers[] = $key . ': ' . $value;
-    }
-
-    return $headers;
-  }
-
-  /**
-   * Get body.
-   *
-   * @return string
-   */
-  public function getBody()
-  {
-    return $this->body;
-  }
-}
-
-class WCOAuth
-{
-  /**
-   * OAuth signature method algorithm.
-   */
-  const HASH_ALGORITHM = 'SHA256';
-
-  /**
-   * API endpoint URL.
-   *
-   * @var string
-   */
-  protected $url;
-
-  /**
-   * Consumer key.
-   *
-   * @var string
-   */
-  protected $consumerKey;
-
-  /**
-   * Consumer secret.
-   *
-   * @var string
-   */
-  protected $consumerSecret;
-
-  /**
-   * API version.
-   *
-   * @var string
-   */
-  protected $apiVersion;
-
-  /**
-   * WCRequest method.
-   *
-   * @var string
-   */
-  protected $method;
-
-  /**
-   * WCRequest parameters.
-   *
-   * @var array
-   */
-  protected $parameters;
-
-  /**
-   * Timestamp.
-   *
-   * @var string
-   */
-  protected $timestamp;
-
-  /**
-   * Initialize oAuth class.
-   *
-   * @param string $url            Store URL.
-   * @param string $consumerKey    Consumer key.
-   * @param string $consumerSecret Consumer Secret.
-   * @param string $method         WCRequest method.
-   * @param string $apiVersion     API version.
-   * @param array  $parameters     WCRequest parameters.
-   * @param string $timestamp      Timestamp.
-   */
-  public function __construct(
-    $url,
-    $consumerKey,
-    $consumerSecret,
-    $apiVersion,
-    $method,
-    $parameters = [],
-    $timestamp = ''
-  ) {
-    $this->url = $url;
-    $this->consumerKey = $consumerKey;
-    $this->consumerSecret = $consumerSecret;
-    $this->apiVersion = $apiVersion;
-    $this->method = $method;
-    $this->parameters = $parameters;
-    $this->timestamp = $timestamp;
-  }
-
-  /**
-   * Encode according to RFC 3986.
-   *
-   * @param string|array $value Value to be normalized.
-   *
-   * @return string
-   */
-  //TODO Rückgbabetyp prüfen
-  protected function encode($value)
-  {
-    if (is_array($value)) {
-      return array_map([$this, 'encode'], $value);
-    } else {
-      return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
-    }
-  }
-
-  /**
-   * Normalize parameters.
-   *
-   * @param array $parameters Parameters to normalize.
-   *
-   * @return array
-   */
-  protected function normalizeParameters($parameters)
-  {
-    $normalized = [];
-
-    foreach ($parameters as $key => $value) {
-      // Percent symbols (%) must be double-encoded.
-      $key = $this->encode($key);
-      $value = $this->encode($value);
-
-      $normalized[$key] = $value;
-    }
-
-    return $normalized;
-  }
-
-  /**
-   * Process filters.
-   *
-   * @param array $parameters WCRequest parameters.
-   *
-   * @return array
-   */
-  protected function processFilters($parameters)
-  {
-    if (isset($parameters['filter'])) {
-      $filters = $parameters['filter'];
-      unset($parameters['filter']);
-      foreach ($filters as $filter => $value) {
-        $parameters['filter[' . $filter . ']'] = $value;
-      }
-    }
-
-    return $parameters;
-  }
-
-  /**
-   * Get secret.
-   *
-   * @return string
-   */
-  protected function getSecret()
-  {
-    $secret = $this->consumerSecret;
-
-    // Fix secret for v3 or later.
-    if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
-      $secret .= '&';
-    }
-
-    return $secret;
-  }
-
-  /**
-   * Generate oAuth1.0 signature.
-   *
-   * @param array $parameters WCRequest parameters including oauth.
-   *
-   * @return string
-   */
-  protected function generateOauthSignature($parameters)
-  {
-    $baseRequestUri = rawurlencode($this->url);
-
-    // Extract filters.
-    $parameters = $this->processFilters($parameters);
-
-    // Normalize parameter key/values and sort them.
-    $parameters = $this->normalizeParameters($parameters);
-    uksort($parameters, 'strcmp');
-
-    // Set query string.
-    $queryString = implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
-    $stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
-    $secret = $this->getSecret();
-
-    return base64_encode(hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
-  }
-
-  /**
-   * Creates an array of urlencoded strings out of each array key/value pairs.
-   *
-   * @param  array  $params      Array of parameters to convert.
-   * @param  array  $queryParams Array to extend.
-   * @param  string $key         Optional Array key to append
-   * @return string              Array of urlencoded strings
-   */
-  protected function joinWithEqualsSign($params, $queryParams = [], $key = '')
-  {
-    foreach ($params as $paramKey => $paramValue) {
-      if ($key) {
-        $paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
-      }
-
-      if (is_array($paramValue)) {
-        //TODO Typ prüfen
-        $queryParams = $this->joinWithEqualsSign($paramValue, $queryParams, $paramKey);
-      } else {
-        $string = $paramKey . '=' . $paramValue; // Join with equals sign.
-        $queryParams[] = $this->encode($string);
-      }
-    }
-
-    return $queryParams;
-  }
-
-  /**
-   * Sort parameters.
-   *
-   * @param array $parameters Parameters to sort in byte-order.
-   *
-   * @return array
-   */
-  protected function getSortedParameters($parameters)
-  {
-    uksort($parameters, 'strcmp');
-
-    foreach ($parameters as $key => $value) {
-      if (is_array($value)) {
-        uksort($parameters[$key], 'strcmp');
-      }
-    }
-
-    return $parameters;
-  }
-
-  /**
-   * Get oAuth1.0 parameters.
-   *
-   * @return string
-   */
-  public function getParameters()
-  {
-    $parameters = \array_merge($this->parameters, [
-      'oauth_consumer_key' => $this->consumerKey,
-      'oauth_timestamp' => $this->timestamp,
-      'oauth_nonce' => \sha1(\microtime()),
-      'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
-    ]);
-
-    // The parameters above must be included in the signature generation.
-    $parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
-
-    //TODO Typ prüfen
-    return $this->getSortedParameters($parameters);
-  }
-}
-
-class WCHttpClientException extends \Exception
-{
-  /**
-   * WCRequest.
-   *
-   * @var WCRequest
-   */
-  private $request;
-
-  /**
-   * WCResponse.
-   *
-   * @var WCResponse
-   */
-  private $response;
-
-  /**
-   * Initialize exception.
-   *
-   * @param string   $message  Error message.
-   * @param int      $code     Error code.
-   * @param WCRequest  $request  Request data.
-   * @param WCResponse $response Response data.
-   */
-  public function __construct($message, $code, WCRequest $request, WCResponse $response)
-  {
-    parent::__construct($message, $code);
-
-    $this->request = $request;
-    $this->response = $response;
-  }
-
-  /**
-   * Get request data.
-   *
-   * @return WCRequest
-   */
-  public function getRequest()
-  {
-    return $this->request;
-  }
-
-  /**
-   * Get response data.
-   *
-   * @return WCResponse
-   */
-  public function getResponse()
-  {
-    return $this->response;
-  }
-}
-
-class WCHttpClient
-{
-  /**
-   * cURL handle.
-   *
-   * @var resource
-   */
-  protected $ch;
-
-  /**
-   * Store API URL.
-   *
-   * @var string
-   */
-  protected $url;
-
-  /**
-   * Consumer key.
-   *
-   * @var string
-   */
-  protected $consumerKey;
-
-  /**
-   * Consumer secret.
-   *
-   * @var string
-   */
-  protected $consumerSecret;
-
-  /**
-   * WCClient options.
-   *
-   * @var WCOptions
-   */
-  protected $options;
-
-  /**
-   * WCRequest.
-   *
-   * @var WCRequest
-   */
-  private $request;
-
-  /**
-   * WCResponse.
-   *
-   * @var WCResponse
-   */
-  private $response;
-
-  /**
-   * WCResponse headers.
-   *
-   * @var string
-   */
-  private $responseHeaders;
-
-  /** @var Logger $logger */
-  public $logger;
-
-  public $ssl_ignore = false;
-
-  /**
-   * Initialize HTTP client.
-   *
-   * @param string $url            Store URL.
-   * @param string $consumerKey    Consumer key.
-   * @param string $consumerSecret Consumer Secret.
-   * @param array  $options        WCClient options.
-   *
-   * @throws WCHttpClientException
-   */
-  public function __construct($url, $consumerKey, $consumerSecret, $options, $logger, $ssl_ignore)
-  {
-    if (!function_exists('curl_version')) {
-      throw new WCHttpClientException('cURL is NOT installed on this server', -1, new WCRequest(), new WCResponse());
-    }
-
-    $this->options = new WCOptions($options);
-    $this->url = $this->buildApiUrl($url);
-    $this->consumerKey = $consumerKey;
-    $this->consumerSecret = $consumerSecret;
-    $this->logger = $logger;
-    $this->ssl_ignore = $ssl_ignore;
-  }
-
-  /**
-   * Check if is under SSL.
-   *
-   * @return bool
-   */
-  protected function isSsl()
-  {
-    return strpos($this->url, 'https://') === 0;
-
-  }
-
-  /**
-   * Build API URL.
-   *
-   * @param string $url Store URL.
-   *
-   * @return string
-   */
-  protected function buildApiUrl($url)
-  {
-    $api = $this->options->isWPAPI() ? $this->options->apiPrefix() : '/wc-api/';
-
-    return rtrim($url, '/') . $api . $this->options->getVersion() . '/';
-  }
-
-  /**
-   * Build URL.
-   *
-   * @param string $url        URL.
-   * @param array  $parameters Query string parameters.
-   *
-   * @return string
-   */
-  protected function buildUrlQuery($url, $parameters = [])
-  {
-    if (!empty($parameters)) {
-      $url .= '?' . http_build_query($parameters);
-    }
-
-    return $url;
-  }
-
-  /**
-   * Authenticate.
-   *
-   * @param string $url        WCRequest URL.
-   * @param string $method     WCRequest method.
-   * @param array  $parameters WCRequest parameters.
-   *
-   * @return array
-   */
-  protected function authenticate($url, $method, $parameters = [])
-  {
-    // Setup authentication.
-    if ($this->isSsl()) {
-      $basicAuth = new WCBasicAuth(
-        $this->ch,
-        $this->consumerKey,
-        $this->consumerSecret,
-        $this->options->isQueryStringAuth(),
-        $parameters
-      );
-      $parameters = $basicAuth->getParameters();
-    } else {
-      $oAuth = new WCOAuth(
-        $url,
-        $this->consumerKey,
-        $this->consumerSecret,
-        $this->options->getVersion(),
-        $method,
-        $parameters,
-        $this->options->oauthTimestamp()
-      );
-      //TODO Typ prüfen
-      $parameters = $oAuth->getParameters();
-    }
-
-    return $parameters;
-  }
-
-  /**
-   * Setup method.
-   *
-   * @param string $method WCRequest method.
-   */
-  protected function setupMethod($method)
-  {
-    if ('POST' === $method) {
-      curl_setopt($this->ch, CURLOPT_POST, true);
-    } elseif (in_array($method, ['PUT', 'DELETE', 'OPTIONS'])) {
-      curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
-    }
-  }
-
-  /**
-   * Get request headers.
-   *
-   * @param  bool $sendData If request send data or not.
-   *
-   * @return array
-   */
-  protected function getRequestHeaders($sendData = false)
-  {
-    $headers = [
-      'Accept' => 'application/json',
-      'User-Agent' => $this->options->userAgent() . '/' . WCClient::VERSION,
-    ];
-
-    if ($sendData) {
-      $headers['Content-Type'] = 'application/json;charset=utf-8';
-    }
-
-    return $headers;
-  }
-
-  /**
-   * Create request.
-   *
-   * @param string $endpoint   WCRequest endpoint.
-   * @param string $method     WCRequest method.
-   * @param array  $data       WCRequest data.
-   * @param array  $parameters WCRequest parameters.
-   *
-   * @return WCRequest
-   */
-  protected function createRequest($endpoint, $method, $data = [], $parameters = [])
-  {
-    $body = '';
-    $url = $this->url . $endpoint;
-    $hasData = !empty($data);
-
-    // Setup authentication.
-    $parameters = $this->authenticate($url, $method, $parameters);
-
-    // Setup method.
-    $this->setupMethod($method);
-
-    // Include post fields.
-    if ($hasData) {
-      $body = json_encode($data);
-      curl_setopt($this->ch, CURLOPT_POSTFIELDS, $body);
-    }
-
-    $this->request = new WCRequest(
-      $this->buildUrlQuery($url, $parameters),
-      $method,
-      $parameters,
-      $this->getRequestHeaders($hasData),
-      $body
-    );
-
-    return $this->getRequest();
-  }
-
-  /**
-   * Get response headers.
-   *
-   * @return array
-   */
-  protected function getResponseHeaders()
-  {
-    $headers = [];
-    $lines = explode("\n", $this->responseHeaders);
-    $lines = array_filter($lines, 'trim');
-
-    foreach ($lines as $index => $line) {
-      // Remove HTTP/xxx params.
-      if (strpos($line, ': ') === false) {
-        continue;
-      }
-
-      list($key, $value) = explode(': ', $line);
-
-      $headers[$key] = isset($headers[$key]) ? $headers[$key] . ', ' . trim($value) : trim($value);
-    }
-
-    return $headers;
-  }
-
-  /**
-   * Create response.
-   *
-   * @return WCResponse
-   */
-  protected function createResponse()
-  {
-    // Set response headers.
-    $this->responseHeaders = '';
-    curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, function ($_, $headers) {
-      $this->responseHeaders .= $headers;
-      return strlen($headers);
-    });
-
-    // Get response data.
-    $body = curl_exec($this->ch);
-    $code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
-    $headers = $this->getResponseHeaders();
-
-    // Register response.
-    $this->response = new WCResponse($code, $headers, $body);
-
-    return $this->getResponse();
-  }
-
-  /**
-   * Set default cURL settings.
-   */
-  protected function setDefaultCurlSettings()
-  {
-    if (!$this->ssl_ignore) {
-      $verifySsl = $this->options->verifySsl();
-    }
-
-    $timeout = $this->options->getTimeout();
-    $followRedirects = $this->options->getFollowRedirects();
-
-    curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
-    if (!$verifySsl) {
-      curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $verifySsl);
-    }
-    if ($followRedirects) {
-      curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
-    }
-    curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $timeout);
-    curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout);
-    curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
-    curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->request->getRawHeaders());
-    curl_setopt($this->ch, CURLOPT_URL, $this->request->getUrl());
-  }
-
-  /**
-   * Look for errors in the request.
-   *
-   * @throws WCHttpClientException
-   *
-   * @param array $parsedResponse Parsed body response.
-   */
-  protected function lookForErrors($parsedResponse)
-  {
-    // Any non-200/201/202 response code indicates an error.
-    if (!in_array($this->response->getCode(), ['200', '201', '202'])) {
-      $errors = isset($parsedResponse->errors) ? $parsedResponse->errors : $parsedResponse;
-      $errorMessage = '';
-      $errorCode = '';
-
-      if (is_array($errors)) {
-        $errorMessage = $errors[0]->message;
-        $errorCode = $errors[0]->code;
-      } elseif (isset($errors->message, $errors->code)) {
-        $errorMessage = $errors->message;
-        $errorCode = $errors->code;
-      }
-
-      $this->logger->error(
-        'WooCommerce Error',
-        [
-          'request' => $this->request,
-          'response' => $this->response
-        ]
-      );
-
-      throw new WCHttpClientException(
-        sprintf('Error: %s [%s]', $errorMessage, $errorCode),
-        $this->response->getCode(),
-        $this->request,
-        $this->response
-      );
-    }
-  }
-
-  /**
-   * Process response.
-   *
-   * @throws WCHttpClientException
-   * @return array
-   */
-
-  protected function processResponse()
-  {
-    $body = $this->response->getBody();
-
-    // Look for UTF-8 BOM and remove.
-    if (0 === strpos(bin2hex(substr($body, 0, 4)), 'efbbbf')) {
-      $body = substr($body, 3);
-    }
-
-    $parsedResponse = json_decode($body);
-
-    // Test if return a valid JSON.
-    if (JSON_ERROR_NONE !== json_last_error()) {
-      $message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Invalid JSON returned';
-      // Log the problematic body for debugging
-      if (isset($this->logger)) {
-        $this->logger->error('JSON Parse Error - Raw Body', [
-          'body_preview' => substr($body, 0, 1000),
-          'body_length' => strlen($body),
-          'json_error' => $message
-        ]);
-      }
-      throw new WCHttpClientException(
-        sprintf('JSON ERROR: %s', $message),
-        $this->response->getCode(),
-        $this->request,
-        $this->response
-      );
-    }
-
-    $this->lookForErrors($parsedResponse);
-
-    return $parsedResponse;
-  }
-
-  /**
-   * Make requests.
-   *
-   * @param string $endpoint   WCRequest endpoint.
-   * @param string $method     WCRequest method.
-   * @param array  $data       WCRequest data.
-   * @param array  $parameters WCRequest parameters.
-   *
-   * @throws WCHttpClientException
-   *
-   * @return array
-   */
-  public function request($endpoint, $method, $data = [], $parameters = [])
-  {
-
-    // Initialize cURL.
-    $this->ch = curl_init();
-
-    // Set request args.
-    $request = $this->createRequest($endpoint, $method, $data, $parameters);
-
-    // Default cURL settings.
-    $this->setDefaultCurlSettings();
-
-    // Get response.
-    $response = $this->createResponse();
-
-    // Check for cURL errors.
-    if (curl_errno($this->ch)) {
-      throw new WCHttpClientException('cURL Error: ' . \curl_error($this->ch), 0, $request, $response);
-    }
-
-    curl_close($this->ch);
-
-    return $this->processResponse();
-  }
-
-  /**
-   * Get request data.
-   *
-   * @return WCRequest
-   */
-  public function getRequest()
-  {
-    return $this->request;
-  }
-
-  /**
-   * Get response data.
-   *
-   * @return WCResponse
-   */
-  public function getResponse()
-  {
-    return $this->response;
-  }
-}
-
-class WCBasicAuth
-{
-  /**
-   * cURL handle.
-   *
-   * @var resource
-   */
-  protected $ch;
-
-  /**
-   * Consumer key.
-   *
-   * @var string
-   */
-  protected $consumerKey;
-
-  /**
-   * Consumer secret.
-   *
-   * @var string
-   */
-  protected $consumerSecret;
-
-  /**
-   * Do query string auth.
-   *
-   * @var bool
-   */
-  protected $doQueryString;
-
-  /**
-   * WCRequest parameters.
-   *
-   * @var array
-   */
-  protected $parameters;
-
-  /**
-   * Initialize Basic Authentication class.
-   *
-   * @param resource $ch             cURL handle.
-   * @param string   $consumerKey    Consumer key.
-   * @param string   $consumerSecret Consumer Secret.
-   * @param bool     $doQueryString  Do or not query string auth.
-   * @param array    $parameters     WCRequest parameters.
-   */
-  public function __construct($ch, $consumerKey, $consumerSecret, $doQueryString, $parameters = [])
-  {
-    $this->ch = $ch;
-    $this->consumerKey = $consumerKey;
-    $this->consumerSecret = $consumerSecret;
-    $this->doQueryString = $doQueryString;
-    $this->parameters = $parameters;
-
-    $this->processAuth();
-  }
-
-  /**
-   * Process auth.
-   */
-  protected function processAuth()
-  {
-    if ($this->doQueryString) {
-      $this->parameters['consumer_key'] = $this->consumerKey;
-      $this->parameters['consumer_secret'] = $this->consumerSecret;
-    } else {
-      \curl_setopt($this->ch, CURLOPT_USERPWD, $this->consumerKey . ':' . $this->consumerSecret);
-    }
-  }
-
-  /**
-   * Get parameters.
-   *
-   * @return array
-   */
-  public function getParameters()
-  {
-    return $this->parameters;
-  }
-}