From bd5c3d3194aa3fbb08f004e067c20a7615389a0a Mon Sep 17 00:00:00 2001 From: RichardGeorgeDavis Date: Tue, 10 Feb 2026 17:11:33 +0200 Subject: [PATCH 1/3] Fix Collivery auth handling: guard missing data, add cURL timeouts, and treat invalid JSON as API error to prevent warnings and long hangs. --- MdsSupportingClasses/Collivery.php | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/MdsSupportingClasses/Collivery.php b/MdsSupportingClasses/Collivery.php index 8c92327..d123a27 100644 --- a/MdsSupportingClasses/Collivery.php +++ b/MdsSupportingClasses/Collivery.php @@ -156,6 +156,12 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { curl_setopt($client, CURLOPT_HTTPHEADER, $headerArray); + // Prevent long hangs when the API is slow/unreachable. + $connectTimeout = isset($this->config->connect_timeout) ? (int) $this->config->connect_timeout : 10; + $timeout = isset($this->config->timeout) ? (int) $this->config->timeout : 20; + curl_setopt($client, CURLOPT_CONNECTTIMEOUT, max(1, $connectTimeout)); + curl_setopt($client, CURLOPT_TIMEOUT, max(1, $timeout)); + $result = curl_exec($client); if (curl_errno($client)) { @@ -171,16 +177,6 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { ]); } - if (isset($result['error'])) { - $error = $result['error']; - throw new CurlConnectionException('Error executing request', 'ConsumeAPI()', [ - 'Code' => $error['http_code'], - 'Message' => $error['message'], - 'URL' => $url, - 'Result' => $result - ]); - } - curl_close($client); // If $result is already an array. @@ -188,7 +184,12 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { return $result; } - return json_decode($result, true); + $decoded = json_decode($result, true); + if (!is_array($decoded)) { + return ['error' => ['http_code' => 500, 'message' => 'Invalid response from Collivery API']]; + } + + return $decoded; } /** @@ -217,6 +218,16 @@ public function makeAuthenticationRequest($settings = null) "password" => $user_password ], 'POST', true); + if (!is_array($authenticate)) { + $this->setError('result_unexpected', 'No result returned.'); + return []; + } + + if (!isset($authenticate['data'])) { + $this->checkError($authenticate); + return []; + } + $authenticate = $authenticate['data']; if (is_array($authenticate) && isset($authenticate['api_token'])) { From 19c7bfce40510cf15bd52b6421fa37587673fedb Mon Sep 17 00:00:00 2001 From: RichardGeorgeDavis Date: Tue, 10 Feb 2026 17:26:24 +0200 Subject: [PATCH 2/3] Harden Collivery caching/auth and fix cURL GET leak --- MdsSupportingClasses/Collivery.php | 18 ++++++++++++---- MdsSupportingClasses/MdsCache.php | 33 ++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/MdsSupportingClasses/Collivery.php b/MdsSupportingClasses/Collivery.php index d123a27..e02b494 100644 --- a/MdsSupportingClasses/Collivery.php +++ b/MdsSupportingClasses/Collivery.php @@ -81,6 +81,14 @@ protected function authenticate() if ( $this->check_cache && $this->cache->has('collivery.auth') && + is_array($authCache) && + isset( + $authCache['email_address'], + $authCache['client']['primary_address']['id'], + $authCache['client']['id'], + $authCache['id'], + $authCache['api_token'] + ) && $authCache['email_address'] == $this->config->user_email ) { $this->default_address_id = $authCache['client']['primary_address']['id']; @@ -127,13 +135,13 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { } } - $client = curl_init($url); - if ($type == 'POST') { + $client = curl_init($url); curl_setopt($client, CURLOPT_POST, 1); $data = json_encode($data); curl_setopt($client, CURLOPT_POSTFIELDS, $data); } else if ($type == 'PUT') { + $client = curl_init($url); curl_setopt($client, CURLOPT_CUSTOMREQUEST, 'PUT'); $data = json_encode($data); curl_setopt($client, CURLOPT_POSTFIELDS, $data); @@ -1254,10 +1262,12 @@ public function make_key_value_array($data, $key, $value, $isContact = false) { /** * Returns the Collivery User Id for the credentials used by the store owner. * - * @return Integer - The User Id; + * @return int|null - The User Id; */ public function getColliveryUserId() { - return $this->authenticate()['id']; + $auth = $this->authenticate(); + + return (is_array($auth) && isset($auth['id'])) ? $auth['id'] : null; } public function filterServices(array $services) diff --git a/MdsSupportingClasses/MdsCache.php b/MdsSupportingClasses/MdsCache.php index afb2a4e..2649235 100644 --- a/MdsSupportingClasses/MdsCache.php +++ b/MdsSupportingClasses/MdsCache.php @@ -19,11 +19,40 @@ class MdsCache */ public function __construct($cache_dir = 'cache/mds_collivery/') { - if ($cache_dir === null) { + if ($cache_dir === null || $cache_dir === '') { $cache_dir = 'cache/mds_collivery/'; } - $this->cache_dir = $cache_dir; + // Resolve relative paths to a writable base when possible. + if (!$this->is_absolute_path($cache_dir)) { + if (defined('WP_CONTENT_DIR') && WP_CONTENT_DIR) { + $cache_dir = rtrim(WP_CONTENT_DIR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($cache_dir, '/\\'); + } else { + $cache_dir = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'mds_collivery/'; + } + } + + $this->cache_dir = rtrim($cache_dir, '/\\') . DIRECTORY_SEPARATOR; + } + + /** + * Determine if a path is absolute. + * + * @param string $path + * + * @return bool + */ + private function is_absolute_path($path) + { + if ($path === '') { + return false; + } + + if ($path[0] === '/' || $path[0] === '\\') { + return true; + } + + return (bool) preg_match('/^[A-Za-z]:[\/\\\\]/', $path); } /** From 316fb755eff95bde60f70c3f49eba01c577828f3 Mon Sep 17 00:00:00 2001 From: RichardGeorgeDavis Date: Thu, 12 Feb 2026 11:55:12 +0200 Subject: [PATCH 3/3] Include rotated logs in zip and clear operations --- MdsSupportingClasses/Collivery.php | 59 +++++- MdsSupportingClasses/MdsFields.php | 14 +- MdsSupportingClasses/MdsLogger.php | 297 +++++++++++++++++++++++++++-- README.md | 4 + Views/js/mds_collivery.js | 31 ++- WC_Mds_Shipping_Method.php | 15 +- changelog.txt | 19 ++ collivery.php | 32 ++-- mds_admin.php | 75 +++++++- readme.txt | 4 +- 10 files changed, 508 insertions(+), 42 deletions(-) create mode 100644 changelog.txt diff --git a/MdsSupportingClasses/Collivery.php b/MdsSupportingClasses/Collivery.php index e02b494..28f603d 100644 --- a/MdsSupportingClasses/Collivery.php +++ b/MdsSupportingClasses/Collivery.php @@ -24,6 +24,9 @@ class Collivery protected $config; protected $errors = []; protected $check_cache = true; + protected $cache; + protected $last_http_code; + protected $last_request_url; protected $default_address_id; protected $client_id; @@ -120,6 +123,7 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { : rtrim(self::BASE_URL, '/'); $endpoint = ltrim($url, '/'); $url = $base . '/' . $endpoint; + $this->last_request_url = $url; if (!$isAuthenticating) { if ($this->token) { @@ -172,12 +176,14 @@ private function consumeAPI($url, $data, $type, $isAuthenticating = false) { $result = curl_exec($client); + $this->last_http_code = curl_getinfo($client, CURLINFO_HTTP_CODE); + if (curl_errno($client)) { $errno = curl_errno($client); $errmsg = curl_error($client); curl_close($client); - throw new CurlConnectionException('Error executing request', 'ConsumeAPI()', [ + throw new CurlConnectionException('Error executing request: '.$errmsg, 'ConsumeAPI()', [ 'Code' => $errno, 'Message' => $errmsg, 'URL' => $url, @@ -228,11 +234,13 @@ public function makeAuthenticationRequest($settings = null) if (!is_array($authenticate)) { $this->setError('result_unexpected', 'No result returned.'); + $this->logAuthFailure($authenticate, $user_email); return []; } if (!isset($authenticate['data'])) { $this->checkError($authenticate); + $this->logAuthFailure($authenticate, $user_email); return []; } @@ -260,11 +268,57 @@ public function makeAuthenticationRequest($settings = null) } } catch (CurlConnectionException $e) { $this->catchException($e); + $this->logAuthFailure(['exception' => $e->getMessage()], $user_email); } return []; } + /** + * Log additional diagnostics for authentication failures. + * + * @param mixed $response + * @param string $user_email + */ + private function logAuthFailure($response, $user_email) + { + $logger = new MdsLogger(); + $headers = [ + 'X-App-Name' => $this->config->app_name, + 'X-App-Version' => $this->config->app_version, + 'X-App-Host' => $this->config->app_host, + 'X-App-Lang' => 'PHP '.phpversion(), + 'X-App-Url' => $this->config->app_url, + ]; + + if (!is_array($response)) { + $response = ['raw' => $response]; + } + + $logger->warning( + 'Collivery::makeAuthenticationRequest', + 'Authentication request failed', + [ + 'app_name' => $this->config->app_name, + 'app_version' => $this->config->app_version, + 'app_host' => $this->config->app_host, + 'app_url' => $this->config->app_url, + 'base_url' => $this->config->base_url, + ], + [ + 'request' => [ + 'url' => $this->last_request_url, + 'email' => $user_email, + 'headers' => $headers, + 'password' => '[redacted]', + ], + 'response' => $response, + 'http_code' => $this->last_http_code, + 'errors' => $this->errors, + ] + ); + } + /** * Returns true or false if authenticated by making a new authentication request. * @@ -1130,6 +1184,9 @@ protected function catchException($e) private function checkError($data) { if (isset($data['error'])) { $this->setError($data['error']['http_code'], $data['error']['message']); + } elseif (isset($data['message'])) { + $code = $this->last_http_code ? $this->last_http_code : 'result_unexpected'; + $this->setError($code, $data['message']); } else { $this->setError('result_unexpected', 'No result returned.'); } diff --git a/MdsSupportingClasses/MdsFields.php b/MdsSupportingClasses/MdsFields.php index 8cd40cf..e8e4dcc 100644 --- a/MdsSupportingClasses/MdsFields.php +++ b/MdsSupportingClasses/MdsFields.php @@ -30,6 +30,12 @@ public static function getFields() 'placeholder' => admin_url().'admin.php?page=mds_download_log_files', 'default' => null, ], + 'debug_logging' => [ + 'title' => __('Debug logging'), + 'type' => 'checkbox', + 'description' => __('When enabled, logs include request/response details and can grow quickly.'), + 'default' => 'no', + ], 'enabled' => [ 'title' => __('Enabled?'), 'type' => 'checkbox', @@ -322,8 +328,14 @@ public static function getResources(MdsColliveryService $service) foreach (['towns', 'location_types', 'services'] as $resource) { $result = $collivery->{'get'.str_replace('_', '', ucwords($resource))}(); if (!is_array($result)) { + $errorMessage = 'Unable to retrieve fields from the API'; + $errors = $collivery->getErrors(); + if (!empty($errors)) { + $errorMessage .= ': '.implode('; ', array_values($errors)); + } + throw new InvalidResourceDataException( - 'Unable to retrieve fields from the API', + $errorMessage, $service->loggerSettingsArray() ); } diff --git a/MdsSupportingClasses/MdsLogger.php b/MdsSupportingClasses/MdsLogger.php index 1a97ebc..501a374 100644 --- a/MdsSupportingClasses/MdsLogger.php +++ b/MdsSupportingClasses/MdsLogger.php @@ -11,6 +11,25 @@ class MdsLogger */ private $log_dir = 'cache/mds_collivery/'; + /** + * @var string + */ + private $woo_log_source = 'mds_collivery'; + /** + * @var int + */ + private $max_log_bytes = 1048576; + + /** + * @var int + */ + private $throttle_seconds = 60; + + /** + * @var array|null + */ + private $throttle_state_cache = null; + /** * @param string $log_dir */ @@ -46,12 +65,22 @@ public function has($name) */ public function error($function, $error, $settings, $extraData = []) { - $this->put('error', array_filter([ + if ($this->isThrottled('error', $function, $error)) { + return; + } + + $payload = array_filter([ 'function' => $function, 'error' => $error, - 'settings' => $settings, - 'data' => $extraData, - ])); + ]); + + if ($this->isDebugLoggingEnabled()) { + $payload['settings'] = $settings; + $payload['data'] = $extraData; + } + + $this->put('error', $payload); + $this->logToWoo('error', $function, $error, $settings, $extraData); } /** @@ -62,12 +91,22 @@ public function error($function, $error, $settings, $extraData = []) */ public function warning($function, $error, $settings, $extraData = []) { - $this->put('warning', array_filter([ + if ($this->isThrottled('warning', $function, $error)) { + return; + } + + $payload = array_filter([ 'function' => $function, 'error' => $error, - 'settings' => $settings, - 'data' => $extraData, - ])); + ]); + + if ($this->isDebugLoggingEnabled()) { + $payload['settings'] = $settings; + $payload['data'] = $extraData; + } + + $this->put('warning', $payload); + $this->logToWoo('warning', $function, $error, $settings, $extraData); } /** @@ -76,10 +115,224 @@ public function warning($function, $error, $settings, $extraData = []) */ public function success($message, $data = []) { - $this->put('success', array_filter([ + $payload = array_filter([ 'message' => $message, - 'data' => $data, - ])); + ]); + + if ($this->isDebugLoggingEnabled()) { + $payload['data'] = $data; + } + + $this->put('success', $payload); + $this->logToWoo('info', 'MdsLogger::success', $message, [], $data); + } + + /** + * Clear log files and throttle state. + * + * @return bool + */ + public function clearLogFiles() + { + $files = [ + 'error', + 'warning', + 'mdsWoocommerceLogs.zip', + 'log_state.json', + ]; + + $dir = $this->getLogDirectory(); + foreach ($files as $file) { + $path = $dir.$file; + if (file_exists($path)) { + unlink($path); + } + } + + return true; + } + + /** + * Clear WooCommerce log files generated by this plugin. + * + * @return bool + */ + public function clearWooLogFiles() + { + if (!function_exists('wc_get_logger')) { + return false; + } + + $logDir = null; + if (class_exists('WC_Log_Handler_File') && method_exists('WC_Log_Handler_File', 'get_log_file_dir')) { + $logDir = WC_Log_Handler_File::get_log_file_dir(); + } else { + if (function_exists('wp_upload_dir')) { + $uploads = wp_upload_dir(); + if (isset($uploads['basedir'])) { + $logDir = rtrim($uploads['basedir'], '/\\').'/wc-logs/'; + } + } + } + + if (!$logDir || !is_dir($logDir)) { + return false; + } + + $pattern = rtrim($logDir, '/\\').'/'.$this->woo_log_source.'-*.log*'; + foreach (glob($pattern) as $file) { + if (is_file($file)) { + unlink($file); + } + } + + return true; + } + + /** + * Mirror logs to WooCommerce logger when available. + * + * @param string $level + * @param string $function + * @param string $message + * @param mixed $settings + * @param mixed $extraData + */ + private function logToWoo($level, $function, $message, $settings, $extraData) + { + if (!function_exists('wc_get_logger')) { + return; + } + + $logger = wc_get_logger(); + if (!$logger) { + return; + } + + $logMessage = $message; + if ($function) { + $logMessage = $function.': '.$message; + } + + $context = [ + 'source' => $this->woo_log_source, + ]; + + if (!empty($settings) && $this->isDebugLoggingEnabled()) { + $context['settings'] = $this->sanitizeData($settings); + } + + if (!empty($extraData) && $this->isDebugLoggingEnabled()) { + $context['data'] = $this->sanitizeData($extraData); + } + + $logger->log($level, $logMessage, $context); + } + + /** + * @return bool + */ + private function isDebugLoggingEnabled() + { + if (!function_exists('get_option')) { + return false; + } + + $settings = get_option('woocommerce_mds_collivery_settings', []); + if (!is_array($settings)) { + return false; + } + + return isset($settings['debug_logging']) && $settings['debug_logging'] === 'yes'; + } + + /** + * @param string $level + * @param string $function + * @param string $message + * + * @return bool + */ + private function isThrottled($level, $function, $message) + { + $state = $this->getThrottleState(); + $key = md5($level.'|'.$function.'|'.$message); + $now = time(); + + if (isset($state[$key]) && ($now - $state[$key]) < $this->throttle_seconds) { + return true; + } + + $state[$key] = $now; + $this->setThrottleState($state); + + return false; + } + + /** + * @return array + */ + private function getThrottleState() + { + if ($this->throttle_state_cache !== null) { + return $this->throttle_state_cache; + } + + $stateFile = $this->getLogDirectory().'log_state.json'; + if (file_exists($stateFile)) { + $decoded = json_decode((string) file_get_contents($stateFile), true); + if (is_array($decoded)) { + $this->throttle_state_cache = $decoded; + return $this->throttle_state_cache; + } + } + + $this->throttle_state_cache = []; + return $this->throttle_state_cache; + } + + /** + * @param array $state + */ + private function setThrottleState(array $state) + { + $stateFile = $this->getLogDirectory().'log_state.json'; + $this->create_dir($this->getLogDirectory()); + file_put_contents($stateFile, json_encode($state)); + $this->throttle_state_cache = $state; + } + + /** + * Remove sensitive values before logging. + * + * @param mixed $data + * @return mixed + */ + private function sanitizeData($data) + { + if (!is_array($data)) { + return $data; + } + + $redactKeys = [ + 'mds_pass', + 'password', + 'api_token', + 'token', + ]; + + foreach ($data as $key => $value) { + if (is_string($key) && in_array(strtolower($key), $redactKeys, true)) { + $data[$key] = '[redacted]'; + continue; + } + + if (is_array($value)) { + $data[$key] = $this->sanitizeData($value); + } + } + + return $data; } /** @@ -169,6 +422,7 @@ protected function load($name) */ protected function put($name, $value) { + $this->rotateIfNeeded($name); if (file_exists($this->getLogDirectory().$name)) { if (filemtime($this->getLogDirectory().$name) < strtotime(date('Y-m-d').' 00:00:00')) { unlink($this->getLogDirectory().$name); @@ -182,6 +436,27 @@ protected function put($name, $value) } } + /** + * Rotate log file if it grows beyond the max size. + * + * @param string $name + */ + private function rotateIfNeeded($name) + { + $path = $this->getLogDirectory().$name; + if (!file_exists($path)) { + return; + } + + if (filesize($path) < $this->max_log_bytes) { + return; + } + + $timestamp = date('YmdHis'); + $rotated = $this->getLogDirectory().$name.'.'.$timestamp; + rename($path, $rotated); + } + /** * Creates the cache directory. * diff --git a/README.md b/README.md index 36e1a82..6ed14b8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ This plugin makes use of our [REST API](https://collivery.net/integration/api/v3 If you wish to submit any changes, simply fork this repository and submit a pull request. +Test credentials: +MDS Username: api@collivery.co.za +MDS Password: api123 + License -------- diff --git a/Views/js/mds_collivery.js b/Views/js/mds_collivery.js index 5512bc7..d0933b2 100644 --- a/Views/js/mds_collivery.js +++ b/Views/js/mds_collivery.js @@ -347,8 +347,16 @@ jQuery(function() { var downloadSetting = jQuery("#woocommerce_mds_collivery_downloadLogs"); if (downloadSetting.length > 0) { var url = downloadSetting.attr('placeholder'); + var downloadUrl = url + '&_wpnonce=' + encodeURIComponent(woocommerce_mds_collivery.nonce_download_logs); + var clearCacheUrl = url.replace('mds_download_log_files', 'mds_clear_cache_files') + + '&_wpnonce=' + encodeURIComponent(woocommerce_mds_collivery.nonce_clear_cache); + var clearLogsUrl = url.replace('mds_download_log_files', 'mds_clear_log_files') + + '&_wpnonce=' + encodeURIComponent(woocommerce_mds_collivery.nonce_clear_logs); + var clearWooLogsUrl = url.replace('mds_download_log_files', 'mds_clear_woo_log_files') + + '&_wpnonce=' + encodeURIComponent(woocommerce_mds_collivery.nonce_clear_woo_logs); + var downloadButton = document.createElement('a'); - downloadButton.setAttribute('href', url); + downloadButton.setAttribute('href', downloadUrl); downloadButton.setAttribute('class', 'button-primary'); downloadButton.innerHTML = 'Download Error Logs'; downloadButton.setAttribute('id', 'woocommerce_mds_collivery_downloadLogs'); @@ -358,11 +366,28 @@ jQuery(function() { clearCacheButton.innerHTML = 'Clear Cache'; clearCacheButton.setAttribute('class', 'button-primary'); clearCacheButton.setAttribute('id', 'woocommerce_mds_collivery_clearCache'); - clearCacheButton.setAttribute('href', - url.replace('mds_download_log_files', 'mds_clear_cache_files')); + clearCacheButton.setAttribute('href', clearCacheUrl); jQuery(clearCacheButton) .css('margin-right', '10px') .insertBefore('#woocommerce_mds_collivery_downloadLogs'); + + var clearLogsButton = document.createElement('a'); + clearLogsButton.innerHTML = 'Clear Plugin Logs'; + clearLogsButton.setAttribute('class', 'button-secondary'); + clearLogsButton.setAttribute('id', 'woocommerce_mds_collivery_clearLogs'); + clearLogsButton.setAttribute('href', clearLogsUrl); + jQuery(clearLogsButton) + .css('margin-left', '10px') + .insertAfter('#woocommerce_mds_collivery_downloadLogs'); + + var clearWooLogsButton = document.createElement('a'); + clearWooLogsButton.innerHTML = 'Clear Woo Logs (MDS)'; + clearWooLogsButton.setAttribute('class', 'button-secondary'); + clearWooLogsButton.setAttribute('id', 'woocommerce_mds_collivery_clearWooLogs'); + clearWooLogsButton.setAttribute('href', clearWooLogsUrl); + jQuery(clearWooLogsButton) + .css('margin-left', '10px') + .insertAfter('#woocommerce_mds_collivery_clearLogs'); } }) diff --git a/WC_Mds_Shipping_Method.php b/WC_Mds_Shipping_Method.php index a887fe7..a66ba1c 100644 --- a/WC_Mds_Shipping_Method.php +++ b/WC_Mds_Shipping_Method.php @@ -306,11 +306,17 @@ public function process_admin_options() 'email' => $userName, 'password' => $password, ]); - + $authErrors = $this->collivery->getErrors(); + try { if (!$authentication) { + $message = 'MDS authentication failed. Please verify credentials or API connectivity.'; + if (!empty($authErrors)) { + $message .= ' Details: '.implode('; ', array_values($authErrors)); + } + throw new InvalidColliveryDataException( - 'Incorrect MDS account details, username and password discarded', + $message, 'WC_Mds_Shipping_Method::validate_settings_fields', $this->collivery_service->loggerSettingsArray(), $postData @@ -322,15 +328,12 @@ public function process_admin_options() } if ($error) { - unset($postData[$userNameKey.'mds_user']); - unset($postData[$passwordKey.'mds_pass']); - $this->set_post_data($postData); $this->add_error($error); } $this->display_errors(); - $result = $authentication && parent::process_admin_options(); + $result = parent::process_admin_options(); if ($result && !$error) { $this->collivery_service = $this->collivery_service->newInstance($this->settings); diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..718c90c --- /dev/null +++ b/changelog.txt @@ -0,0 +1,19 @@ +Changelog +========= + +4.5.5 - 2026-02-12 +- Guard missing auth data to prevent warnings and hard failures. +- Add cURL connect/total timeouts to reduce admin hangs. +- Resolve cache directory to a writable base when given a relative path. +- Validate cached auth structure before use. +- Avoid cURL handle leaks on GET requests. +- Declare the cache property to avoid PHP 8.2 dynamic property deprecations. +- Normalise plugin header placement. +- Improve API error handling (surface response messages and cURL errors). +- Keep credentials on auth failure and show detailed auth errors in admin. +- Add auth failure diagnostics logging (request/response, redacted). +- Mirror plugin logs into WooCommerce logs (source: mds_collivery). +- Add debug logging toggle for verbose request/response logging. +- Add log throttling and 1MB rotation to limit growth. +- Add buttons to clear plugin logs and WooCommerce logs for this plugin. +- Add nonce/capability checks for log and cache actions. diff --git a/collivery.php b/collivery.php index e122fe6..d50b101 100644 --- a/collivery.php +++ b/collivery.php @@ -1,11 +1,25 @@ wp_create_nonce('mds_download_log_files'), + 'nonce_clear_cache' => wp_create_nonce('mds_clear_cache_files'), + 'nonce_clear_logs' => wp_create_nonce('mds_clear_log_files'), + 'nonce_clear_woo_logs' => wp_create_nonce('mds_clear_woo_log_files'), + ]); +} + /** * Download the error log file. */ function mds_download_log_files() { + if (!current_user_can('manage_options')) { + wp_die(__('You do not have permission to perform this action.')); + } + + check_admin_referer('mds_download_log_files'); + /** @var MdsColliveryService $mds */ $mds = MdsColliveryService::getInstance(); if ($file = $mds->downloadLogFiles()) { @@ -59,6 +91,12 @@ function mds_download_log_files() */ function mds_clear_cache_files() { + if (!current_user_can('manage_options')) { + wp_die(__('You do not have permission to perform this action.')); + } + + check_admin_referer('mds_clear_cache_files'); + /** @var MdsColliveryService $mds */ $mds = MdsColliveryService::getInstance(); $cache = $mds->returnCacheClass(); @@ -68,6 +106,42 @@ function mds_clear_cache_files() exit; } +/** + * Clear the error and warning log files. + */ +function mds_clear_log_files() +{ + if (!current_user_can('manage_options')) { + wp_die(__('You do not have permission to perform this action.')); + } + + check_admin_referer('mds_clear_log_files'); + + $logger = new MdsLogger(); + $logger->clearLogFiles(); + + wp_redirect(get_admin_url().'admin.php?page=wc-settings&tab=shipping§ion=mds_collivery'); + exit; +} + +/** + * Clear WooCommerce log files for this plugin. + */ +function mds_clear_woo_log_files() +{ + if (!current_user_can('manage_options')) { + wp_die(__('You do not have permission to perform this action.')); + } + + check_admin_referer('mds_clear_woo_log_files'); + + $logger = new MdsLogger(); + $logger->clearWooLogFiles(); + + wp_redirect(get_admin_url().'admin.php?page=wc-settings&tab=shipping§ion=mds_collivery'); + exit; +} + /** * Function used to display index of all our deliveries already accepted and sent to MDS Collivery. */ @@ -687,4 +761,3 @@ function sync(){