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
eeae728
fix(woocommerce): expose response headers from inline WC client
Apr 20, 2026
309b72e
fix(woocommerce): add getLastResponse() accessor on WC client
Apr 20, 2026
a9845ef
fix(woocommerce): persist last-import timestamp in shopexport config
Apr 20, 2026
7a4a80e
fix(woocommerce): use after-filter and pagination for order import
Apr 20, 2026
069e02a
fix(woocommerce): stabilize after-filter across pagination run
Apr 20, 2026
48bd84c
docs: add plan for woocommerce pagination fix
Apr 20, 2026
dd6c4b1
fix(woocommerce): CLI-context fallback for persistLastImportTimestamp
Apr 20, 2026
bea289a
revert(woocommerce): restore SSL-only gating for query-string basic auth
Apr 20, 2026
c1543d0
fix(woocommerce): return single order per ImportGetAuftrag call
Apr 20, 2026
867794b
docs: update pagination plan to reflect single-order contract
Apr 20, 2026
fe9e560
fix(woocommerce): return null (not empty array) from ImportGetAuftrag…
Apr 20, 2026
554030e
fix(woocommerce): remove broken getKonfig() re-init in ImportGetAuftrag
Apr 20, 2026
307cc4a
fix(woocommerce): run ab_nummer migration in the count path, too
Apr 20, 2026
351dd63
fix(woocommerce): tuple cursor (ts, id) to survive same-second orders
Apr 20, 2026
cf0162e
docs: extend pagination plan to cover tuple cursor and count-path mig…
Apr 20, 2026
56d68c2
fix(woocommerce): accumulate same-second order ids in cursor, gate -1…
Apr 20, 2026
d53649d
docs: document bucket-accumulating cursor and gated -1s offset
Apr 20, 2026
b887f00
fix(woocommerce): persist fallback cursor when ab_nummer resolution f…
Apr 21, 2026
45ecd71
feat(woocommerce): use products/batch endpoints for stock sync
Apr 20, 2026
6495a07
docs: add plan for batch stock sync
Apr 20, 2026
a70b864
fix(woocommerce): catch batch request exceptions, log success at info…
Apr 20, 2026
14268eb
fix(woocommerce): catch SKU-lookup exceptions per chunk in stock sync
Apr 21, 2026
3181f0c
refactor(woocommerce): require automattic/woocommerce composer depend…
Apr 21, 2026
f71addf
refactor(woocommerce): add ClientWrapper for header access on upstrea…
Apr 21, 2026
6da4ea4
refactor(woocommerce): replace inline WC SDK with composer dependency
Apr 21, 2026
258b436
fix(woocommerce): use injected logger in ClientWrapper for API error …
Apr 21, 2026
5f4225b
chore(composer): regenerate autoloader metadata for current HEAD
Apr 21, 2026
3d40b51
chore(git): exempt vendor/ from diff-check whitespace rules
Apr 21, 2026
160f95b
chore(composer): re-sync installed.php to previous HEAD
Apr 21, 2026
f26a875
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