487 lines
17 KiB
PHP
487 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* IGNY8 API Client Class
|
|
*
|
|
* Handles all communication with IGNY8 API v1.0
|
|
* Follows WORDPRESS-PLUGIN-INTEGRATION.md guidelines
|
|
*
|
|
* @package Igny8Bridge
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Igny8API Class
|
|
*/
|
|
class Igny8API {
|
|
|
|
/**
|
|
* API base URL
|
|
* Note: Base is /api, endpoints should include /v1/ prefix
|
|
*
|
|
* @var string
|
|
*/
|
|
private $base_url = 'https://api.igny8.com/api';
|
|
|
|
/**
|
|
* API key (used as access token)
|
|
*
|
|
* @var string|null
|
|
*/
|
|
private $access_token = null;
|
|
|
|
/**
|
|
* Whether authentication is via API key (always true now)
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $api_key_auth = true;
|
|
|
|
/**
|
|
* Constructor
|
|
* Only uses API key for authentication
|
|
*/
|
|
public function __construct() {
|
|
if (function_exists('igny8_get_secure_option')) {
|
|
$api_key = igny8_get_secure_option('igny8_api_key');
|
|
} else {
|
|
$api_key = get_option('igny8_api_key');
|
|
}
|
|
|
|
// API key is the only authentication method
|
|
if (!empty($api_key)) {
|
|
$this->access_token = $api_key;
|
|
$this->api_key_auth = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect using API key
|
|
* Tests connection by calling /v1/integration/integrations/test-connection/ endpoint
|
|
*
|
|
* @param string $api_key API key from IGNY8 app
|
|
* @param int $site_id Site ID from IGNY8 app
|
|
* @return bool True on success, false on failure
|
|
*/
|
|
public function connect($api_key, $site_id = null) {
|
|
if (empty($api_key)) {
|
|
return false;
|
|
}
|
|
|
|
// Store API key
|
|
if (function_exists('igny8_store_secure_option')) {
|
|
igny8_store_secure_option('igny8_api_key', $api_key);
|
|
} else {
|
|
update_option('igny8_api_key', $api_key);
|
|
}
|
|
|
|
$this->access_token = $api_key;
|
|
$this->api_key_auth = true;
|
|
|
|
// If site_id provided, test connection to integration endpoint
|
|
if (!empty($site_id)) {
|
|
$test_response = $this->post('/v1/integration/integrations/test-connection/', array(
|
|
'site_id' => (int) $site_id,
|
|
'api_key' => $api_key,
|
|
'site_url' => get_site_url()
|
|
));
|
|
|
|
if ($test_response['success']) {
|
|
$timestamp = current_time('timestamp');
|
|
update_option('igny8_last_api_health_check', $timestamp);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fallback: if no site_id, just verify API key exists by making a simple call
|
|
// This tests that the API key is valid format at least
|
|
$timestamp = current_time('timestamp');
|
|
update_option('igny8_last_api_health_check', $timestamp);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if API is authenticated
|
|
*
|
|
* @return bool True if authenticated, false otherwise
|
|
*/
|
|
public function is_authenticated() {
|
|
return !empty($this->access_token);
|
|
}
|
|
|
|
/**
|
|
* Parse unified API response
|
|
*
|
|
* @param array|WP_Error $response HTTP response
|
|
* @return array Parsed response
|
|
*/
|
|
private function parse_response($response) {
|
|
if (is_wp_error($response)) {
|
|
return array(
|
|
'success' => false,
|
|
'error' => $response->get_error_message(),
|
|
'http_status' => 0
|
|
);
|
|
}
|
|
|
|
$status_code = wp_remote_retrieve_response_code($response);
|
|
$raw_body = wp_remote_retrieve_body($response);
|
|
$body = json_decode($raw_body, true);
|
|
|
|
// Handle non-JSON responses — allow empty arrays/objects but detect JSON decode errors
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
return array(
|
|
'success' => false,
|
|
'error' => 'Invalid JSON response: ' . json_last_error_msg(),
|
|
'raw_body' => substr($raw_body, 0, 200),
|
|
'http_status' => $status_code
|
|
);
|
|
}
|
|
|
|
// Check if response follows unified format
|
|
if (isset($body['success'])) {
|
|
$body['http_status'] = $status_code;
|
|
|
|
// Handle throttling errors (429) - extract retry delay from error message
|
|
if ($status_code === 429 && isset($body['error'])) {
|
|
// Extract delay from error message like "Request was throttled. Expected available in 1 second."
|
|
if (preg_match('/Expected available in (\d+) second/i', $body['error'], $matches)) {
|
|
$body['retry_after'] = intval($matches[1]);
|
|
} elseif (preg_match('/(\d+) second/i', $body['error'], $matches)) {
|
|
$body['retry_after'] = intval($matches[1]);
|
|
} else {
|
|
// Default to 2 seconds if we can't parse it
|
|
$body['retry_after'] = 2;
|
|
}
|
|
}
|
|
|
|
return $body;
|
|
}
|
|
|
|
// Legacy format - wrap in unified format
|
|
if ($status_code >= 200 && $status_code < 300) {
|
|
return array(
|
|
'success' => true,
|
|
'data' => $body,
|
|
'http_status' => $status_code
|
|
);
|
|
} else {
|
|
$error_message = $body['detail'] ?? 'HTTP ' . $status_code . ' error';
|
|
|
|
// Handle throttling in legacy format
|
|
$retry_after = null;
|
|
if ($status_code === 429) {
|
|
if (preg_match('/Expected available in (\d+) second/i', $error_message, $matches)) {
|
|
$retry_after = intval($matches[1]);
|
|
} elseif (preg_match('/(\d+) second/i', $error_message, $matches)) {
|
|
$retry_after = intval($matches[1]);
|
|
} else {
|
|
$retry_after = 2;
|
|
}
|
|
}
|
|
|
|
$result = array(
|
|
'success' => false,
|
|
'error' => $error_message,
|
|
'http_status' => $status_code,
|
|
'raw_error' => $body
|
|
);
|
|
|
|
if ($retry_after !== null) {
|
|
$result['retry_after'] = $retry_after;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get headers with authentication
|
|
* Uses Bearer token format for API key authentication
|
|
*
|
|
* @return array Headers array
|
|
*/
|
|
private function get_headers() {
|
|
$headers = array(
|
|
'Content-Type' => 'application/json',
|
|
'Accept' => 'application/json'
|
|
);
|
|
|
|
if (!empty($this->access_token)) {
|
|
$headers['Authorization'] = 'Bearer ' . $this->access_token;
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* Make GET request with automatic retry on throttling
|
|
*
|
|
* @param string $endpoint API endpoint (e.g. /v1/auth/sites/ or /v1/integration/integrations/)
|
|
* @param int $max_retries Maximum number of retries for throttled requests (default: 3)
|
|
* @return array Response data
|
|
*/
|
|
public function get($endpoint, $max_retries = 3) {
|
|
if (!$this->is_authenticated()) {
|
|
return array('success' => false, 'error' => 'Not authenticated', 'http_status' => 401);
|
|
}
|
|
// Ensure endpoint starts with /v1
|
|
if (strpos($endpoint, '/v1/') === false) {
|
|
if (strpos($endpoint, '/') !== 0) {
|
|
$endpoint = '/' . $endpoint;
|
|
}
|
|
if (strpos($endpoint, '/v1') !== 0) {
|
|
$endpoint = '/v1' . $endpoint;
|
|
}
|
|
}
|
|
|
|
$url = $this->base_url . $endpoint;
|
|
$headers = $this->get_headers();
|
|
$retry_count = 0;
|
|
|
|
while ($retry_count <= $max_retries) {
|
|
// Debug logging (enable with WP_DEBUG or IGNY8_DEBUG constant)
|
|
$debug_enabled = (defined('WP_DEBUG') && WP_DEBUG) || (defined('IGNY8_DEBUG') && IGNY8_DEBUG);
|
|
if ($debug_enabled) {
|
|
error_log(sprintf(
|
|
'IGNY8 DEBUG GET: %s | Headers: %s',
|
|
$url,
|
|
json_encode(array_merge($headers, array('Authorization' => 'Bearer ***')))
|
|
));
|
|
}
|
|
|
|
$response = wp_remote_get($url, array(
|
|
'headers' => $headers,
|
|
'timeout' => 30
|
|
));
|
|
|
|
// Debug response
|
|
if ($debug_enabled) {
|
|
$status_code = wp_remote_retrieve_response_code($response);
|
|
$response_body = wp_remote_retrieve_body($response);
|
|
error_log(sprintf(
|
|
'IGNY8 DEBUG RESPONSE: Status=%s | Body=%s',
|
|
$status_code,
|
|
substr($response_body, 0, 500)
|
|
));
|
|
}
|
|
|
|
$body = $this->parse_response($response);
|
|
|
|
// If throttled (429), retry after the specified delay
|
|
if (isset($body['http_status']) && $body['http_status'] === 429 && $retry_count < $max_retries) {
|
|
$retry_after = isset($body['retry_after']) ? $body['retry_after'] : 2;
|
|
|
|
// Add a small buffer (0.5 seconds) to ensure we wait long enough
|
|
$wait_time = $retry_after + 0.5;
|
|
$wait_seconds = (int) ceil($wait_time); // Convert to integer, rounding up
|
|
|
|
// Log retry attempt
|
|
if ($debug_enabled) {
|
|
error_log(sprintf(
|
|
'IGNY8 DEBUG: Request throttled, retrying after %.1f seconds (attempt %d/%d)',
|
|
$wait_time,
|
|
$retry_count + 1,
|
|
$max_retries
|
|
));
|
|
}
|
|
|
|
// Wait before retrying
|
|
sleep($wait_seconds);
|
|
$retry_count++;
|
|
continue;
|
|
}
|
|
|
|
// Not throttled or max retries reached, return response
|
|
// API keys don't expire, so no refresh logic needed
|
|
// If 401, the API key is invalid or revoked
|
|
return $body;
|
|
}
|
|
|
|
// Should never reach here, but return last response if we do
|
|
return $body;
|
|
}
|
|
|
|
/**
|
|
* Make POST request with automatic retry on throttling
|
|
*
|
|
* @param string $endpoint API endpoint (e.g. /v1/integration/integrations/)
|
|
* @param array $data Request data
|
|
* @param int $max_retries Maximum number of retries for throttled requests (default: 3)
|
|
* @return array Response data
|
|
*/
|
|
public function post($endpoint, $data, $max_retries = 3) {
|
|
// Special case: test-connection endpoint allows API key in request body
|
|
// So we don't require pre-authentication for this endpoint
|
|
$is_test_connection = (strpos($endpoint, 'test-connection') !== false);
|
|
$has_api_key_in_data = !empty($data['api_key']);
|
|
$was_authenticated = $this->is_authenticated();
|
|
|
|
// If not authenticated, check if this is a test-connection with API key in data
|
|
if (!$was_authenticated) {
|
|
if ($is_test_connection && $has_api_key_in_data) {
|
|
// Temporarily set the API key for this request
|
|
$temp_api_key = $this->access_token;
|
|
$this->access_token = $data['api_key'];
|
|
} else {
|
|
return array('success' => false, 'error' => 'Not authenticated', 'http_status' => 401);
|
|
}
|
|
}
|
|
// Ensure endpoint starts with /v1
|
|
if (strpos($endpoint, '/v1/') === false) {
|
|
if (strpos($endpoint, '/') !== 0) {
|
|
$endpoint = '/' . $endpoint;
|
|
}
|
|
if (strpos($endpoint, '/v1') !== 0) {
|
|
$endpoint = '/v1' . $endpoint;
|
|
}
|
|
}
|
|
|
|
$retry_count = 0;
|
|
|
|
while ($retry_count <= $max_retries) {
|
|
$response = wp_remote_post($this->base_url . $endpoint, array(
|
|
'headers' => $this->get_headers(),
|
|
'body' => json_encode($data),
|
|
'timeout' => 60
|
|
));
|
|
|
|
$body = $this->parse_response($response);
|
|
|
|
// If throttled (429), retry after the specified delay
|
|
if (isset($body['http_status']) && $body['http_status'] === 429 && $retry_count < $max_retries) {
|
|
$retry_after = isset($body['retry_after']) ? $body['retry_after'] : 2;
|
|
|
|
// Add a small buffer (0.5 seconds) to ensure we wait long enough
|
|
$wait_time = $retry_after + 0.5;
|
|
$wait_seconds = (int) ceil($wait_time); // Convert to integer, rounding up
|
|
|
|
// Log retry attempt
|
|
$debug_enabled = (defined('WP_DEBUG') && WP_DEBUG) || (defined('IGNY8_DEBUG') && IGNY8_DEBUG);
|
|
if ($debug_enabled) {
|
|
error_log(sprintf(
|
|
'IGNY8 DEBUG: Request throttled, retrying after %.1f seconds (attempt %d/%d)',
|
|
$wait_time,
|
|
$retry_count + 1,
|
|
$max_retries
|
|
));
|
|
}
|
|
|
|
// Wait before retrying
|
|
sleep($wait_seconds);
|
|
$retry_count++;
|
|
continue;
|
|
}
|
|
|
|
// Not throttled or max retries reached, return response
|
|
// Restore original access token if we temporarily set it
|
|
if ($is_test_connection && $has_api_key_in_data && !$was_authenticated) {
|
|
$this->access_token = isset($temp_api_key) ? $temp_api_key : null;
|
|
}
|
|
return $body;
|
|
}
|
|
|
|
// Should never reach here, but return last response if we do
|
|
// Restore original access token if we temporarily set it
|
|
if ($is_test_connection && $has_api_key_in_data && !$was_authenticated) {
|
|
$this->access_token = isset($temp_api_key) ? $temp_api_key : null;
|
|
}
|
|
return $body;
|
|
}
|
|
|
|
/**
|
|
* Make PUT request with automatic retry on throttling
|
|
*
|
|
* @param string $endpoint API endpoint (e.g. /v1/integration/integrations/1/update-structure/)
|
|
* @param array $data Request data
|
|
* @param int $max_retries Maximum number of retries for throttled requests (default: 3)
|
|
* @return array Response data
|
|
*/
|
|
public function put($endpoint, $data, $max_retries = 3) {
|
|
if (!$this->is_authenticated()) {
|
|
return array('success' => false, 'error' => 'Not authenticated', 'http_status' => 401);
|
|
}
|
|
// Ensure endpoint starts with /v1
|
|
if (strpos($endpoint, '/v1/') === false) {
|
|
if (strpos($endpoint, '/') !== 0) {
|
|
$endpoint = '/' . $endpoint;
|
|
}
|
|
if (strpos($endpoint, '/v1') !== 0) {
|
|
$endpoint = '/v1' . $endpoint;
|
|
}
|
|
}
|
|
|
|
$retry_count = 0;
|
|
|
|
while ($retry_count <= $max_retries) {
|
|
$response = wp_remote_request($this->base_url . $endpoint, array(
|
|
'method' => 'PUT',
|
|
'headers' => $this->get_headers(),
|
|
'body' => json_encode($data),
|
|
'timeout' => 60
|
|
));
|
|
|
|
$body = $this->parse_response($response);
|
|
|
|
// If throttled (429), retry after the specified delay
|
|
if (isset($body['http_status']) && $body['http_status'] === 429 && $retry_count < $max_retries) {
|
|
$retry_after = isset($body['retry_after']) ? $body['retry_after'] : 2;
|
|
$wait_time = $retry_after + 0.5;
|
|
$wait_seconds = (int) ceil($wait_time); // Convert to integer, rounding up
|
|
|
|
$debug_enabled = (defined('WP_DEBUG') && WP_DEBUG) || (defined('IGNY8_DEBUG') && IGNY8_DEBUG);
|
|
if ($debug_enabled) {
|
|
error_log(sprintf(
|
|
'IGNY8 DEBUG: Request throttled, retrying after %.1f seconds (attempt %d/%d)',
|
|
$wait_time,
|
|
$retry_count + 1,
|
|
$max_retries
|
|
));
|
|
}
|
|
|
|
sleep($wait_seconds);
|
|
$retry_count++;
|
|
continue;
|
|
}
|
|
|
|
return $body;
|
|
}
|
|
|
|
return $body;
|
|
}
|
|
|
|
/**
|
|
* Make DELETE request
|
|
*
|
|
* @param string $endpoint API endpoint
|
|
* @return array Response data
|
|
*/
|
|
public function delete($endpoint) {
|
|
if (!$this->is_authenticated()) {
|
|
return array('success' => false, 'error' => 'Not authenticated', 'http_status' => 401);
|
|
}
|
|
$response = wp_remote_request($this->base_url . $endpoint, array(
|
|
'method' => 'DELETE',
|
|
'headers' => $this->get_headers(),
|
|
'timeout' => 30
|
|
));
|
|
|
|
return $this->parse_response($response);
|
|
}
|
|
|
|
/**
|
|
* Get access token
|
|
*
|
|
* @return string|null Access token
|
|
*/
|
|
public function get_access_token() {
|
|
return $this->access_token;
|
|
}
|
|
}
|
|
|