settings = $settings; } /** * Fetches new blog stubs since the provided timestamp. * * @param int $since Unix timestamp. * @return array>|\WP_Error */ public function get_posts( $since ) { $response = $this->request( 'GET', '/posts', array( 'since' => absint( $since ), ) ); if ( is_wp_error( $response ) ) { return $response; } // Support a few response shapes returned by the upstream API over time. if ( isset( $response['posts'] ) && is_array( $response['posts'] ) ) { return $response['posts']; } if ( isset( $response['data']['posts'] ) && is_array( $response['data']['posts'] ) ) { return $response['data']['posts']; } if ( isset( $response['data']['blogs'] ) && is_array( $response['data']['blogs'] ) ) { return $response['data']['blogs']; } if ( isset( $response['data'] ) && is_array( $response['data'] ) && $this->is_list_array( $response['data'] ) ) { return $response['data']; } if ( $this->is_list_array( $response ) ) { return $response; } return array(); } /** * Fetches the full details for a remote blog. * * @param int|string $blog_id Remote blog ID. * @return array|\WP_Error */ public function get_post_details( $blog_id ) { $response = $this->request( 'GET', '/post-details', array( 'blog_id' => $blog_id, ) ); if ( is_wp_error( $response ) ) { return $response; } if ( isset( $response['data'] ) && is_array( $response['data'] ) ) { return $response['data']; } if ( isset( $response['post'] ) && is_array( $response['post'] ) ) { return $response['post']; } return is_array( $response ) ? $response : array(); } /** * Notifies the remote API that a blog has been published in WordPress. * * @param int|string $blog_id Remote blog ID. * @param int $post_id Local WordPress post ID. * @return array|\WP_Error */ public function mark_published( $blog_id, $post_id ) { return $this->request( 'POST', '/mark-published', array(), array( 'blog_id' => $blog_id, 'post_id' => $post_id, ) ); } /** * Performs a raw HTTP request against the remote API. * * @param string $method HTTP method. * @param string $path Endpoint path. * @param array $query Query arguments. * @param array $body JSON request body. * @return array|\WP_Error */ private function request( $method, $path, array $query = array(), array $body = array() ) { $base_url = defined( 'AIBP_API_BASE_URL' ) ? AIBP_API_BASE_URL : ''; $api_key = $this->settings->get( 'api_key', '' ); if ( empty( $base_url ) ) { return new \WP_Error( 'aibp_missing_base_url', __( 'API Base URL is not configured.', 'ai-blog-poster' ) ); } if ( empty( $api_key ) ) { return new \WP_Error( 'aibp_missing_api_key', __( 'API Key is not configured.', 'ai-blog-poster' ) ); } $url = trailingslashit( $base_url ) . ltrim( $path, '/' ); if ( ! empty( $query ) ) { $url = add_query_arg( $query, $url ); } $args = array( 'method' => strtoupper( $method ), 'timeout' => 30, 'headers' => array( 'api-key' => $api_key, 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), ); if ( 'POST' === strtoupper( $method ) ) { $json_body = wp_json_encode( $body ); if ( false === $json_body ) { return new \WP_Error( 'aibp_json_encode_error', __( 'Could not encode API request body as JSON.', 'ai-blog-poster' ), array( 'endpoint' => $path, ) ); } $args['body'] = $json_body; } $response = wp_remote_request( $url, $args ); if ( is_wp_error( $response ) ) { return $response; } $status_code = (int) wp_remote_retrieve_response_code( $response ); $raw_body = wp_remote_retrieve_body( $response ); $data = json_decode( $raw_body, true ); $json_error = json_last_error(); if ( $status_code < 200 || $status_code >= 300 ) { $message = $this->extract_error_message( $data ); return new \WP_Error( 'aibp_api_error', $message, array( 'status_code' => $status_code, 'endpoint' => $path, 'response' => $this->summarize_response_body( $data, $raw_body ), ) ); } if ( JSON_ERROR_NONE !== $json_error && '' !== trim( (string) $raw_body ) ) { return new \WP_Error( 'aibp_invalid_json', sprintf( /* translators: %s: JSON parser error message */ __( 'Remote API returned invalid JSON: %s', 'ai-blog-poster' ), json_last_error_msg() ), array( 'status_code' => $status_code, 'endpoint' => $path, 'response' => $this->summarize_response_body( null, $raw_body ), ) ); } if ( is_array( $data ) && isset( $data['success'] ) && false === (bool) $data['success'] ) { return new \WP_Error( 'aibp_api_error', $this->extract_error_message( $data ), array( 'status_code' => ! empty( $data['status_code'] ) ? absint( $data['status_code'] ) : $status_code, 'endpoint' => $path, 'response' => $this->summarize_response_body( $data, $raw_body ), ) ); } return is_array( $data ) ? $data : array(); } /** * Checks whether an array is a numeric list rather than an associative map. * * @param array $value Value to inspect. * @return bool */ private function is_list_array( array $value ) { if ( function_exists( 'array_is_list' ) ) { return array_is_list( $value ); } return array_keys( $value ) === range( 0, count( $value ) - 1 ); } /** * Extracts the clearest API error message from a decoded response body. * * @param mixed $data Decoded API response. * @return string */ private function extract_error_message( $data ) { if ( is_array( $data ) ) { foreach ( array( 'message', 'error', 'detail' ) as $key ) { if ( ! empty( $data[ $key ] ) && is_scalar( $data[ $key ] ) ) { return sanitize_text_field( (string) $data[ $key ] ); } } if ( ! empty( $data['errors'] ) ) { $errors = wp_json_encode( $data['errors'] ); return false === $errors ? __( 'Remote API returned errors.', 'ai-blog-poster' ) : sanitize_text_field( $errors ); } } return __( 'Remote API request failed.', 'ai-blog-poster' ); } /** * Keeps non-sensitive API error data short enough for debug history. * * @param mixed $data Decoded API response. * @param string $raw_body Raw response body. * @return string */ private function summarize_response_body( $data, $raw_body ) { $summary = is_array( $data ) ? wp_json_encode( $data ) : (string) $raw_body; if ( false === $summary ) { $summary = ''; } return substr( wp_strip_all_tags( (string) $summary ), 0, 500 ); } }