From d38d825e3484d822ea3c1f0fb1df37ecf386b18a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 04 Jan 2026 19:54:16 +0000
Subject: [PATCH] =TaxonomyCreator.js debugging
---
inc/integrations/Integrations.php | 183 ++++++++++++++++++++++++++++-----------------
1 files changed, 114 insertions(+), 69 deletions(-)
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index 5f47ff6..ff053aa 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -62,7 +62,7 @@
*/
protected array $credentials = []; // Service credentials (API keys, tokens, etc.)
protected ?int $userID = null; // User context for user-specific integrations
-
+ private bool $token_refresh_attempted = false; // Circuit breaker for token refresh
protected array $fields = []; // The fields to generate that become credentials
protected array $advanced = []; // The fields that are optional settings
@@ -167,7 +167,7 @@
{
$this->cacheName = $this->cacheName ?: $this->service_name;
$this->userID = $userID;
- $this->cache = new CacheManager('integrations_' . $this->cacheName, $this->ttl);
+ $this->cache = CacheManager::for('integrations_' . $this->cacheName, $this->ttl);
// Load error stats from cache
$this->loadErrorStats();
@@ -258,10 +258,6 @@
if (!empty($this->credentials['expires_at'])) {
$expires_at = intval($this->credentials['expires_at']);
if ($expires_at <= time()) {
- // Token expired, try to refresh
- if (!empty($this->credentials['refresh_token'])) {
- return $this->refreshOAuthToken();
- }
return false;
}
}
@@ -434,7 +430,6 @@
} else {
$result = $this->$method();
}
- error_log('Action result: '.print_r($result, true));
if (is_wp_error($result)) {
return [
'success' => false,
@@ -655,9 +650,7 @@
}
try {
- error_log('Credentials to save: '.print_r($this->credentials, true));
if ($this->isOAuthService && !$this->hasOAuthCredentials()){
- error_log('Just saving credentials, we don\'t have OAuth setup yet...');
//If this is an OAuth service, we might only be saving the app credentials first
$result = true;
} else {
@@ -765,6 +758,9 @@
array $options = []
): array|WP_Error
{
+ if (!$this->is_healthy) {
+ return new WP_Error('unhealthy', 'Connection marked unhealthy. Skipping fetch');
+ }
$this->ensureInitialized();
if (!$this->isSetUp()){
$this->logError('Connection not setup for '.$this->service_name, [
@@ -778,24 +774,21 @@
return new WP_Error('rate_limit', 'Rate limit exceeded. Please try again later.');
}
- // Debug: Check if credentials are loaded
- error_log('['.$this->service_name.'] Make Request - Credentials loaded: ' . (!empty($this->credentials) ? 'Yes' : 'No'));
- error_log('With Credentials: '.print_r($this->credentials, true));
$attempt = 0;
$lastError = null;
while ($attempt < $this->maxRetries) {
try {
- $this->logDebug('[Integrations] Making request to '.$this->service_name);
+// $this->logDebug('[Integrations] Making request to '.$this->service_name);
$result = $this->executeRequest($method, $endpoint, $data, $baseKey, $options);
if (!$result) {
return new WP_Error('Request Error');
}
$this->recordRequest($method, $endpoint);
- $this->logDebug('[Integrations]', [
- 'response' => $result
- ]);
+// $this->logDebug('[Integrations]', [
+// 'response' => $result
+// ]);
// Reset error stats on success
$this->resetErrorStats();
return $result;
@@ -844,16 +837,16 @@
return null;
}
- $this->logDebug("$method request to: $url: ".print_r($args, true));
+// $this->logDebug("$method request to: $url: ".print_r($args, true));
- // Standard WordPress HTTP API
- // Use appropriate WordPress HTTP function
+ // Make the request
$response = match($method) {
'GET' => wp_remote_get($url, $args),
'POST' => wp_remote_post($url, $args),
'PUT', 'PATCH', 'DELETE' => wp_remote_request($url, array_merge($args, ['method' => $method])),
default => null
};
+
if (!$response) {
$this->logError("Unsupported HTTP method $method for $this->service_name");
return null;
@@ -867,9 +860,42 @@
$response_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
+ // Handle 401 - try to refresh token and retry once
+ if ($response_code === 401 && $this->isOAuthService && !empty($this->credentials['refresh_token'])) {
+ // Avoid infinite retry loop - only retry once
+ static $retry_count = 0;
+
+ if ($retry_count === 0) {
+ $retry_count++;
+
+// $this->logDebug('Got 401, attempting token refresh...');
+ if ($this->refreshOAuthToken()) {
+// $this->logDebug('Token refreshed successfully, retrying request...');
+
+ // Rebuild request args with new token
+ $args = $this->buildRequestArgs($method, $data, $options);
+
+ // Retry the request
+ $response = match($method) {
+ 'GET' => wp_remote_get($url, $args),
+ 'POST' => wp_remote_post($url, $args),
+ 'PUT', 'PATCH', 'DELETE' => wp_remote_request($url, array_merge($args, ['method' => $method])),
+ default => null
+ };
+
+ if ($response && !is_wp_error($response)) {
+ $response_code = wp_remote_retrieve_response_code($response);
+ $body = wp_remote_retrieve_body($response);
+ }
+ }
+ $retry_count = 0; // Reset for next request
+ }
+ }
+
if ($response_code >= 400) {
$this->handleApiError($response_code, $body, $endpoint);
}
+
$decoded = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['raw_response' => $body];
@@ -891,25 +917,41 @@
bool $force = false
): ?array
{
- $cacheKey = $this->buildCacheKey('GET', $endpoint, $params);
- $ttl = $this->cacheStrategy[$cacheStrategy] ?? $this->ttl;
+ $cacheKey = $this->buildCacheKey('GET', $endpoint, $params, $baseKey);
+
+ $ttl = is_int($cacheStrategy)
+ ? max(0, $cacheStrategy)
+ : ($this->cacheStrategy[$cacheStrategy] ?? $this->ttl);
if (!$force && $ttl > 0) {
$cached = $this->cache->get($cacheKey);
if ($cached !== false) {
- $this->logDebug("Cache hit for: $cacheKey");
+
+// $this->logDebug("Cache hit for: $cacheKey");
return $cached;
}
}
$result = $this->makeRequest('GET', $endpoint, $params, $baseKey);
- if ($result && $ttl > 0) {
+ // Only cache successful responses (not WP_Error and not error objects)
+ if ($result && !is_wp_error($result) && !$this->isErrorResponse($result) && $ttl > 0) {
$this->cache->set($cacheKey, $result, $ttl);
}
return $result;
}
+ /**
+ * Check if response contains an error
+ * Override in child classes for service-specific error detection
+ */
+ protected function isErrorResponse(array $response): bool
+ {
+ // Common error patterns across APIs
+ return isset($response['error'])
+ || isset($response['errors'])
+ || isset($response['error_description']);
+ }
/**
* POST request
@@ -1171,14 +1213,10 @@
public function handleAjaxResponse()
{
- error_log('Ajax Response: '.print_r($_GET, true));
$code = $_GET['code'];
$state = $_GET['state'];
- error_log('OAuth Callback - Code: ' . $code);
- error_log('OAuth Callback - State: ' . $state);
-
$state_parts = explode('|', $state);
$state_key = $state_parts[0] ?? '';
@@ -1186,16 +1224,13 @@
$user_id = ($user_id === 0) ? null : $user_id;
$return_url = isset($state_parts[2]) ? base64_decode($state_parts[2]) : admin_url('admin.php?page=jvb-integrations');
- error_log('Service: '.print_r($this->service_name, true));
$state_data = get_transient('oauth_state_' . $state_key);
- error_log('State Data: '.print_r($state_data, true));
if (!$state_data || $state_data['service'] !== $this->service_name) {
wp_die('Invalid state parameter', 'OAuth Error');
}
// Delete the transient to prevent reuse
delete_transient('oauth_state_' . $state_key);
- error_log('Return URL: '.print_r($return_url, true));
// Handle error from OAuth provider
if (array_key_exists('error', $_GET)) {
$error_description = $_GET['error_description'] ?? 'Authorization denied';
@@ -1347,17 +1382,29 @@
if ($this->isOAuthService && $this->hasOAuthCredentials()) {
// Check if token is expired first
if (!$this->isOAuthValid()) {
- $this->logDebug('OAuth token expired, attempting refresh');
- if (!$this->refreshOAuthToken()) {
- $this->logError('Failed to refresh expired OAuth token');
+ // Only attempt refresh once per request
+ if (!$this->token_refresh_attempted) {
+ $this->token_refresh_attempted = true;
+// $this->logDebug('OAuth token expired, attempting refresh');
+
+ if (!$this->refreshOAuthToken()) {
+ $this->logError('Failed to refresh expired OAuth token - stopping execution');
+ // Token refresh failed - DO NOT continue making API requests
+ return;
+ }
+ } else {
+ // Already attempted refresh in this request
+// $this->logDebug('Token refresh already attempted, skipping');
+ return;
}
}
// Check if we should proactively refresh (before expiry)
- elseif ($this->shouldRefreshToken()) {
- $this->logDebug('OAuth token should be refreshed proactively');
+ elseif ($this->shouldRefreshToken() && !$this->token_refresh_attempted) {
+ $this->token_refresh_attempted = true;
+// $this->logDebug('OAuth token should be refreshed proactively');
if (!$this->refreshOAuthToken()) {
$this->logError('Failed to proactively refresh OAuth token');
- // Not critical - token is still valid
+ // Not critical - token is still valid, so continue
}
}
}
@@ -1381,6 +1428,7 @@
// Switch context
$this->userID = $user_id;
$this->credentials = [];
+ $this->resetTokenRefreshFlag(); // ADD THIS LINE
$this->ensureInitialized();
}
@@ -1580,8 +1628,6 @@
$auth_url = $this->oauth['authorize'] . '?' . http_build_query($params);
- // Debug log for troubleshooting
- error_log("Generated OAuth URL for {$this->service_name}: " . $auth_url);
return $auth_url;
}
@@ -1758,12 +1804,7 @@
'redirect_uri' => $this->getRedirectUri()
];
- // Use a custom endpoint key for OAuth (not part of regular API)
- // We need to handle this specially since OAuth endpoints are different
$oauth_endpoint = $this->oauth['token'];
-
- // Make the request using the centralized method
- // This automatically includes rate limiting and error handling
$response = $this->makeOAuthRequest('POST', $oauth_endpoint, $request_data);
if (is_wp_error($response)) {
@@ -1776,10 +1817,13 @@
// Parse response
if (isset($response['access_token'])) {
+ $expires_in = $response['expires_in'] ?? 2592000; // 30 days default
+
return [
'access_token' => $response['access_token'],
'refresh_token' => $response['refresh_token'] ?? '',
- 'expires_in' => $response['expires_in'] ?? 2592000, // 30 days default
+ 'expires_in' => $expires_in,
+ 'expires_at' => time() + $expires_in, // Calculate expiry timestamp
'token_type' => $response['token_type'] ?? 'Bearer',
'merchant_id' => $response['merchant_id'] ?? '',
'scope' => $response['scope'] ?? ''
@@ -1877,7 +1921,6 @@
return false;
}
- // Build refresh request data
$request_data = [
'client_id' => $this->credentials['client_id'],
'client_secret' => $this->credentials['client_secret'],
@@ -1885,12 +1928,24 @@
'grant_type' => 'refresh_token'
];
- // Use centralized OAuth request method
$response = $this->makeOAuthRequest('POST', $this->oauth['token'], $request_data);
if (is_wp_error($response)) {
- $this->logError('Failed to refresh Square token', [
- 'error' => $response->get_error_message()
+ $error_message = $response->get_error_message();
+
+ if (str_contains($error_message, 'invalid_grant')) {
+ $this->logError('OAuth refresh token is invalid - user must re-authorize', [
+ 'error' => $error_message
+ ], 'critical');
+
+ // Mark unhealthy immediately
+ $this->error_stats['consecutive_errors'] = $this->error_threshold;
+ $this->is_healthy = false;
+ $this->saveErrorStats();
+ }
+
+ $this->logError('Failed to refresh OAuth token for '.$this->service_name, [
+ 'error' => $error_message
]);
return false;
}
@@ -1899,7 +1954,7 @@
$this->credentials['access_token'] = $response['access_token'];
$this->credentials['expires_at'] = time() + ($response['expires_in'] ?? 2592000); // 30 days
- // Note: Square returns the SAME refresh token
+ // Note: Some services return the SAME refresh token
if (isset($response['refresh_token'])) {
$this->credentials['refresh_token'] = $response['refresh_token'];
}
@@ -2081,45 +2136,36 @@
*/
public function handleSavePost(int $postID, WP_Post $post, bool $update): void
{
- error_log('Testing For Save Post');
if (jvbNoSaveIt($postID, $post)) {
- error_log('Excluded by jvbNoSaveIt');
return;
}
if (empty($this->syncPostTypes)) {
- error_log('No Syncable post types');
return;
}
$config = JVB_CONTENT[jvbNoBase($post->post_type)]??null;
if (!$config) {
- error_log('No Config set');
return;
}
$settings = $config['integrations'][$this->service_name]??null;
if (!$settings) {
- error_log('No settings');
return;
}
$fields = $this->getSyncFields($postID, 'post', ['schedule_'.$this->service_name]);
- error_log('Fields to check: '.print_r($fields, true));
if (!$fields['share_to_'.$this->service_name]) {
return;
}
$isShared = isset($fields["_{$this->service_name}_item_id"]);
if ($update && $isShared && !$fields['_keep_synced_'.$this->service_name]) {
- error_log('Do not keep synced, not syncing with '.$this->service_name);
return;
}
if ($post->post_status !== 'publish' && !$isShared) {
- error_log('Not published and not already shared');
return;
}
- error_log('Sending to integration for processing...');
$this->handleTheSavePost($postID, $post, $update, $settings);
}
@@ -2592,11 +2638,6 @@
];
$this->is_healthy = true;
$this->saveErrorStats();
-
- $this->logDebug('Integration health manually reset', [
- 'reset_by' => get_current_user_id(),
- 'reset_time' => time()
- ]);
}
/**
@@ -2731,9 +2772,6 @@
// Check for duplicate processing (idempotency)
if ($this->isWebhookProcessed($payload)) {
- $this->logDebug('Webhook already processed', [
- 'webhook_id' => $this->extractWebhookId($payload)
- ]);
return true; // Return true to prevent retries
}
@@ -3029,7 +3067,7 @@
switch ($action) {
case 'save_credentials':
$title = $label;
- $label = jvbIcon('save');
+ $label = jvbIcon('floppy-disk');
break;
case 'clear_credentials':
$title = $label;
@@ -3064,7 +3102,7 @@
}
$credentials = $this->getCredentials();
$hasCredentials = $this->hasOAuthCredentials();
- $returnURL = (is_admin()) ? :get_the_permalink();
+ $returnURL = is_admin() ? admin_url('admin.php?page=jvb-integrations') : (get_the_permalink() ?: home_url());
?>
<details <?= $hasCredentials?' open':''?>>
@@ -3146,7 +3184,6 @@
// Verify nonce
$nonce_field = 'jvb_integration_nonce_' . $service;
$nonce_action = 'jvb_integration_save_' . $service;
- error_log('handleAdminPost: '.print_r($_POST, true));
if (!isset($_POST[$nonce_field]) || !wp_verify_nonce($_POST[$nonce_field], $nonce_action)) {
wp_die('Security check failed');
}
@@ -3483,4 +3520,12 @@
throw new Exception('Failed to save JPEG image');
}
}
+ /**
+ * Reset token refresh attempt flag
+ * Called automatically when switching users
+ */
+ protected function resetTokenRefreshFlag(): void
+ {
+ $this->token_refresh_attempted = false;
+ }
}
--
Gitblit v1.10.0