Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bae0191
fix(woocommerce): expose response headers from inline WC client
Apr 20, 2026
1907cdf
fix(woocommerce): add getLastResponse() accessor on WC client
Apr 20, 2026
dee30b1
fix(woocommerce): persist last-import timestamp in shopexport config
Apr 20, 2026
ec9a617
fix(woocommerce): use after-filter and pagination for order import
Apr 20, 2026
b7d3a27
fix(woocommerce): stabilize after-filter across pagination run
Apr 20, 2026
810ae74
docs: add plan for woocommerce pagination fix
Apr 20, 2026
7a4ee0b
fix(woocommerce): CLI-context fallback for persistLastImportTimestamp
Apr 20, 2026
0d33e0e
revert(woocommerce): restore SSL-only gating for query-string basic auth
Apr 20, 2026
dfe8fbf
fix(woocommerce): return single order per ImportGetAuftrag call
Apr 20, 2026
1a07e3b
docs: update pagination plan to reflect single-order contract
Apr 20, 2026
d32c4c0
fix(woocommerce): return null (not empty array) from ImportGetAuftrag…
Apr 20, 2026
7099916
fix(woocommerce): remove broken getKonfig() re-init in ImportGetAuftrag
Apr 20, 2026
0af53ef
fix(woocommerce): run ab_nummer migration in the count path, too
Apr 20, 2026
78545a4
fix(woocommerce): tuple cursor (ts, id) to survive same-second orders
Apr 20, 2026
f3055b5
docs: extend pagination plan to cover tuple cursor and count-path mig…
Apr 20, 2026
dbddc49
fix(woocommerce): accumulate same-second order ids in cursor, gate -1…
Apr 20, 2026
e6dd616
docs: document bucket-accumulating cursor and gated -1s offset
Apr 20, 2026
726f2eb
fix(woocommerce): persist fallback cursor when ab_nummer resolution f…
Apr 21, 2026
11e1795
feat(woocommerce): use products/batch endpoints for stock sync
Apr 20, 2026
d7eb2c5
docs: add plan for batch stock sync
Apr 20, 2026
6a71d4b
fix(woocommerce): catch batch request exceptions, log success at info…
Apr 20, 2026
b1bc331
fix(woocommerce): catch SKU-lookup exceptions per chunk in stock sync
Apr 21, 2026
fdd7daa
refactor(woocommerce): require automattic/woocommerce composer depend…
Apr 21, 2026
db44291
refactor(woocommerce): add ClientWrapper for header access on upstrea…
Apr 21, 2026
f4ee7b4
refactor(woocommerce): replace inline WC SDK with composer dependency
Apr 21, 2026
5a978f3
fix(woocommerce): use injected logger in ClientWrapper for API error …
Apr 21, 2026
1797d5d
chore(composer): regenerate autoloader metadata for current HEAD
Apr 21, 2026
3645231
chore(git): exempt vendor/ from diff-check whitespace rules
Apr 21, 2026
fa5dd52
chore(composer): re-sync installed.php to previous HEAD
Apr 21, 2026
2040194
chore(composer): re-sync installed.php after rebase onto #266
Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
256 changes: 256 additions & 0 deletions classes/Components/WooCommerce/ClientWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<?php
/**
* Thin wrapper around the official automattic/woocommerce Composer SDK client.
*
* Adds the two accessors that the OpenXE business logic requires but that the
* upstream SDK does not expose:
*
* - getLastResponse(): ResponseWrapper last HTTP response after any API call
* - ResponseWrapper::getHeader($name) case-insensitive single-header lookup
*
* Also handles the OpenXE-specific ssl_ignore flag and passes it through to the
* upstream client via the verify_ssl option.
*
* @package Xentral\Components\WooCommerce
*/

namespace Xentral\Components\WooCommerce;

use Automattic\WooCommerce\Client as UpstreamClient;
use Automattic\WooCommerce\HttpClient\HttpClientException;

/**
* Wraps a single upstream Response and exposes a case-insensitive getHeader().
*/
class ResponseWrapper
{
/** @var \Automattic\WooCommerce\HttpClient\Response */
private $response;

/** @var array<string,string> 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<string,string>
*/
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)]
);
}
}
}
}
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*"
Expand All @@ -41,4 +42,4 @@
},
"optimize-autoloader": true
}
}
}
Loading