`ael42a_api_base` * option -> default. Filterable via `ael42a_api_base`. * * @return string */ public function api_base() { if ( defined( 'AEL42A_API_BASE' ) && AEL42A_API_BASE ) { $base = AEL42A_API_BASE; } else { $base = get_option( 'ael42a_api_base', self::DEFAULT_API_BASE ); } /** * Filter the 42A API base URL. * * @param string $base API base URL. */ return untrailingslashit( (string) apply_filters( 'ael42a_api_base', $base ) ); } /** * Resolved app URL (for connect links / upsells). * * @return string */ public function app_url() { if ( defined( 'AEL42A_APP_URL' ) && AEL42A_APP_URL ) { $url = AEL42A_APP_URL; } else { $url = get_option( 'ael42a_app_url', self::DEFAULT_APP_URL ); } /** * Filter the 42A app URL. * * @param string $url App URL. */ return untrailingslashit( (string) apply_filters( 'ael42a_app_url', $url ) ); } /** * Exchange a pairing code for a site token. * * @param string $pairing_code 32-hex single-use pairing code. * @return array|\WP_Error Registration data. */ public function register( $pairing_code ) { global $wp_version; $capabilities = array( 'markdown', 'llms_txt', 'llms_full_txt', 'mcp' ); return $this->request( 'POST', '/api/v1/integrations/wordpress/register', array( 'pairing_code' => $pairing_code, 'site_url' => home_url( '/' ), 'plugin_version' => AEL42A_VERSION, 'wp_version' => $wp_version, 'capabilities' => $capabilities, ), null // Pairing code auth - no bearer token yet. ); } /** * Fetch site status (verification state, org/brand summary). * * @return array|\WP_Error */ public function get_site() { return $this->request( 'GET', '/api/v1/integrations/wordpress/site' ); } /** * Fetch the full vitals report for the connected brand. * * @return array|\WP_Error */ public function get_vitals() { return $this->request( 'GET', '/api/v1/integrations/wordpress/vitals' ); } /** * Fetch dashboard-mirror visibility stats (score, mentions, sentiment, * share of voice, coverage, history, projects) for the connected brand. * * @return array|\WP_Error */ public function get_visibility() { return $this->request( 'GET', '/api/v1/integrations/wordpress/visibility' ); } /** * Send a daily telemetry aggregate. * * @param array $payload {date, mcp_requests, markdown_hits, llms_txt_hits, tool_calls}. * @return array|\WP_Error */ public function send_telemetry( $payload ) { return $this->request( 'POST', '/api/v1/integrations/wordpress/telemetry', $payload ); } /** * Disconnect (revoke) this site's token. * * @return array|\WP_Error */ public function disconnect() { return $this->request( 'POST', '/api/v1/integrations/wordpress/disconnect', array() ); } /** * Perform an authenticated JSON request. * * @param string $method GET|POST. * @param string $path API path. * @param array|null $body JSON body for POST. * @param string|null $token Override bearer token; null = stored token, * except register() passes null explicitly and * sends no Authorization header. * @return array|\WP_Error Unwrapped `data` payload. */ private function request( $method, $path, $body = null, $token = '__stored__' ) { $url = $this->api_base() . $path; $headers = array( 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'User-Agent' => 'ael-42a/' . AEL42A_VERSION . '; ' . home_url( '/' ), ); if ( '__stored__' === $token ) { $stored = get_option( 'ael42a_site_token', '' ); if ( '' === $stored ) { return new \WP_Error( 'not_connected', __( 'This site is not connected to 42A.', 'ael-42a' ) ); } $token = $stored; } if ( is_string( $token ) && '' !== $token ) { $headers['Authorization'] = 'Bearer ' . $token; // Some hosts and proxies strip the Authorization header from // outgoing requests - the API accepts this fallback header too. $headers['X-42a-Site-Token'] = $token; } $args = array( 'method' => $method, 'timeout' => self::TIMEOUT, 'headers' => $headers, ); if ( null !== $body ) { $args['body'] = wp_json_encode( $body ); } $response = ( 'GET' === $method ) ? wp_remote_get( $url, $args ) : wp_remote_post( $url, $args ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); $raw_body = wp_remote_retrieve_body( $response ); $decoded = json_decode( $raw_body, true ); if ( $code < 200 || $code >= 300 ) { /* translators: %d: HTTP status code. */ $message = sprintf( __( 'The 42A API returned an error (HTTP %d).', 'ael-42a' ), (int) $code ); if ( is_array( $decoded ) ) { if ( ! empty( $decoded['error']['message'] ) ) { $message = (string) $decoded['error']['message']; } elseif ( ! empty( $decoded['error'] ) && is_string( $decoded['error'] ) ) { $message = $decoded['error']; } elseif ( ! empty( $decoded['message'] ) ) { $message = (string) $decoded['message']; } } // The envelope's machine-readable code (e.g. "site_disconnected") // becomes the WP_Error code so callers can branch on it. $error_code = 'api_error'; if ( is_array( $decoded ) && ! empty( $decoded['error']['code'] ) && is_string( $decoded['error']['code'] ) ) { $error_code = sanitize_key( $decoded['error']['code'] ); } // Body snippet kept in the error data (never displayed raw) so // support/debugging can see what actually came back. return new \WP_Error( $error_code, $message, array( 'status' => $code, 'body' => substr( sanitize_text_field( (string) $raw_body ), 0, 300 ), ) ); } if ( ! is_array( $decoded ) ) { /* translators: %d: HTTP status code. */ return new \WP_Error( 'api_error', sprintf( __( 'The 42A API returned an unexpected response (HTTP %d).', 'ael-42a' ), (int) $code ), array( 'status' => $code ) ); } // Envelope: { data, meta }. return array_key_exists( 'data', $decoded ) ? $decoded['data'] : $decoded; } }