int, 'body' => string, 'json' => mixed]` for 2xx answers and a `WP_Error` otherwise * (transport errors as WordPress reports them; HTTP errors as `zadex_http_` with data `['status' => code]` * and the provider's own message when the body carries one). Nothing here prints anything to visitors. */ final class Http { public const TIMEOUT = 10; /** * @param array $args wp_remote_request arguments (headers, body, timeout ...) * @return array{code: int, body: string, json: mixed}|WP_Error */ public static function request(string $method, string $url, array $args = []) { $args = array_merge(['method' => strtoupper($method), 'timeout' => self::TIMEOUT, 'redirection' => 2], $args); $args['headers'] = array_merge(['User-Agent' => 'Zadex/' . (defined('ZADEX_VERSION') ? ZADEX_VERSION : '1') . ' (WordPress form integration)'], (array) ($args['headers'] ?? [])); $response = wp_remote_request($url, $args); if ($response instanceof WP_Error) { return $response; } $code = (int) wp_remote_retrieve_response_code($response); $body = (string) wp_remote_retrieve_body($response); $json = $body !== '' ? json_decode($body, true) : null; if ($code < 200 || $code >= 300) { $detail = self::detail($json, $body); return new WP_Error('zadex_http_' . $code, 'HTTP ' . $code . ($detail !== '' ? ': ' . $detail : ''), ['status' => $code]); } return ['code' => $code, 'body' => $body, 'json' => $json]; } /** JSON body helper: `request()` with a JSON-encoded body and the matching content type. */ public static function json(string $method, string $url, $body, array $headers = [], int $timeout = self::TIMEOUT) { return self::request($method, $url, [ 'headers' => array_merge(['Content-Type' => 'application/json', 'Accept' => 'application/json'], $headers), 'body' => (string) wp_json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'timeout' => $timeout, ]); } /** HTTP status of a `zadex_http_` error (0 for transport errors). */ public static function status(WP_Error $error): int { return preg_match('/^zadex_http_(\d{3})$/', $error->get_error_code(), $m) ? (int) $m[1] : 0; } /** Short, single-line error detail from a provider response (their `detail` / `message` / `error` field). */ public static function detail($json, string $body): string { $text = ''; if (is_array($json)) { foreach (['detail', 'message', 'error', 'title', 'codeDescription'] as $key) { if (isset($json[$key]) && is_string($json[$key]) && $json[$key] !== '') { $text = $json[$key]; break; } if (isset($json[$key]['message']) && is_string($json[$key]['message'])) { $text = $json[$key]['message']; break; } } if ($text === '' && isset($json['errors'][0])) { $first = $json['errors'][0]; $text = is_array($first) ? (string) ($first['title'] ?? $first['message'] ?? $first['detail'] ?? '') : (string) $first; } } elseif ($body !== '' && strlen($body) < 300) { $text = $body; } $text = preg_replace('/\s+/', ' ', wp_strip_all_tags($text)) ?? ''; return substr(trim($text), 0, 300); } }