ai->getAiGeneratorApiUrl() . 'insights/' . $path ); } /** * Creates a new SEO report. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function createReport( $request ) { $keyword = ! empty( $request['keyword'] ) ? sanitize_text_field( $request['keyword'] ) : ''; if ( empty( $keyword ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Keyword is required.' ], 400 ); } $rateLimit = aioseo()->core->cache->get( 'ai_insights_rate_limit' ); if ( ! empty( $rateLimit['reached'] ) && $rateLimit['reached'] ) { return new \WP_REST_Response( [ 'success' => false, 'message' => ! empty( $rateLimit['message'] ) ? $rateLimit['message'] : 'Rate limit exceeded. Please try again later.', 'code' => 'rate_limit_exceeded' ], 429 ); } $response = aioseo()->helpers->wpRemotePost( self::getApiUrl( 'keyword-reports/' ), [ 'timeout' => 30, 'headers' => aioseo()->ai->getRequestHeaders(), 'body' => wp_json_encode( [ 'keyword' => $keyword ] ) ] ); if ( is_wp_error( $response ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => $response->get_error_message() ], $response->get_error_code() ); } $responseCode = wp_remote_retrieve_response_code( $response ); $responseBody = wp_remote_retrieve_body( $response ); $decodedBody = json_decode( $responseBody, true ); // Check for rate limit (429 status code). if ( 429 === $responseCode ) { // Cache the rate limit status for 1 hour. $message = ! empty( $decodedBody['message'] ) ? $decodedBody['message'] : 'Rate limit exceeded. Please try again later.'; aioseo()->core->cache->update( 'ai_insights_rate_limit', [ 'reached' => true, 'message' => $message ], HOUR_IN_SECONDS ); return new \WP_REST_Response( [ 'success' => false, 'message' => $message, 'code' => 'rate_limit_exceeded' ], 429 ); } if ( empty( $decodedBody['success'] ) || empty( $decodedBody['data']['report']['uuid'] ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Failed to create report.' ], 500 ); } $reportUuid = $decodedBody['data']['report']['uuid']; // Clear rate limit cache on successful request. aioseo()->core->cache->delete( 'ai_insights_rate_limit' ); // Insert or update report in database. $report = Models\AiInsightsKeywordReport::getByUuid( $reportUuid ); if ( ! $report->exists() ) { $report->uuid = $reportUuid; $report->keyword = $keyword; $report->status = 'pending'; $report->save(); } return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'uuid' => $decodedBody['data']['report']['uuid'] ] ], 200 ); } /** * Retrieves all reports. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function getReports( $request ) { // Get pagination and ordering parameters from request (query params for GET). $orderBy = $request->get_param( 'orderBy' ) ? sanitize_text_field( $request->get_param( 'orderBy' ) ) : 'created'; $orderDir = $request->get_param( 'orderDir' ) ? strtoupper( sanitize_text_field( $request->get_param( 'orderDir' ) ) ) : 'DESC'; $limit = $request->get_param( 'limit' ) ? intval( $request->get_param( 'limit' ) ) : 20; $offset = $request->get_param( 'offset' ) ? intval( $request->get_param( 'offset' ) ) : 0; $searchTerm = $request->get_param( 'searchTerm' ) ? sanitize_text_field( $request->get_param( 'searchTerm' ) ) : ''; $status = $request->get_param( 'status' ) ? sanitize_text_field( $request->get_param( 'status' ) ) : 'all'; // Validate order direction. if ( ! in_array( $orderDir, [ 'ASC', 'DESC' ], true ) ) { $orderDir = 'DESC'; } // Validate order by field. $allowedOrderBy = [ 'created', 'updated', 'keyword', 'status' ]; if ( ! in_array( $orderBy, $allowedOrderBy, true ) ) { $orderBy = 'created'; } // Build query. $query = aioseo()->core->db->start( 'aioseo_ai_insights_keyword_reports' ) ->select( '*' ); // Create separate query for total count. $totalQuery = aioseo()->core->db->noConflict()->start( 'aioseo_ai_insights_keyword_reports' ); // Filter by status. if ( 'all' !== $status ) { $query->where( 'status', $status ); $totalQuery->where( 'status', $status ); } // Search by keyword. if ( ! empty( $searchTerm ) ) { $escapedSearchTerm = esc_sql( aioseo()->core->db->db->esc_like( $searchTerm ) ); $query->whereRaw( "keyword LIKE '%{$escapedSearchTerm}%'" ); $totalQuery->whereRaw( "keyword LIKE '%{$escapedSearchTerm}%'" ); } // Get total count before pagination. $totalCount = $totalQuery->count(); // Apply ordering and pagination. $reports = $query ->orderBy( $orderBy . ' ' . $orderDir ) ->limit( $limit, $offset ) ->run() ->models( 'AIOSEO\Plugin\Common\Models\AiInsightsKeywordReport' ); // Convert models to arrays for JSON response. $reportsData = []; foreach ( $reports as $report ) { $reportsData[] = $report->jsonSerialize(); } return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'reports' => $reportsData, 'total' => $totalCount ] ], 200 ); } /** * Retrieves a specific report by UUID from local database. * If status is pending or processing, checks API for updates. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function getReport( $request ) { $uuid = ! empty( $request['uuid'] ) ? sanitize_text_field( $request['uuid'] ) : ''; if ( empty( $uuid ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'UUID is required.' ], 400 ); } // Get report from local database. $report = Models\AiInsightsKeywordReport::getByUuid( $uuid ); if ( ! $report->exists() ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Report not found.' ], 404 ); } // If status is pending or processing, check API for updates. if ( in_array( $report->status, [ 'pending', 'processing' ], true ) ) { $url = self::getApiUrl( 'keyword-reports/' . sanitize_text_field( $uuid ) . '/' ); $response = aioseo()->helpers->wpRemoteGet( $url, [ 'headers' => aioseo()->ai->getRequestHeaders(), 'timeout' => 30 ] ); if ( is_wp_error( $response ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => $response->get_error_message() ], 500 ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! empty( $data['success'] ) && ! empty( $data['data']['report'] ) ) { $apiReport = $data['data']['report']; $report->status = $apiReport['status']; $report->brands_mentioned = intval( $apiReport['brands_mentioned'] ); $report->results = $apiReport['results']; $report->brands = $apiReport['brands']; $report->save(); } } return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'report' => $report->jsonSerialize() ] ], 200 ); } /** * Regenerates a report by creating a new one with the same keyword. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function regenerateReport( $request ) { $uuid = ! empty( $request['uuid'] ) ? sanitize_text_field( $request['uuid'] ) : ''; if ( empty( $uuid ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'UUID is required.' ], 400 ); } // Get report from local database. $report = Models\AiInsightsKeywordReport::getByUuid( $uuid ); if ( ! $report->exists() ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Report not found.' ], 404 ); } if ( empty( $report->keyword ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Report keyword is missing.' ], 400 ); } $rateLimit = aioseo()->core->cache->get( 'ai_insights_rate_limit' ); if ( ! empty( $rateLimit['reached'] ) && $rateLimit['reached'] ) { return new \WP_REST_Response( [ 'success' => false, 'message' => ! empty( $rateLimit['message'] ) ? $rateLimit['message'] : 'Rate limit exceeded. Please try again later.', 'code' => 'rate_limit_exceeded' ], 429 ); } $response = aioseo()->helpers->wpRemotePost( self::getApiUrl( 'keyword-reports/' ), [ 'timeout' => 30, 'headers' => aioseo()->ai->getRequestHeaders(), 'body' => wp_json_encode( [ 'keyword' => $report->keyword, 'refresh' => true ] ) ] ); if ( is_wp_error( $response ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => $response->get_error_message() ], $response->get_error_code() ); } $responseCode = wp_remote_retrieve_response_code( $response ); $responseBody = wp_remote_retrieve_body( $response ); $decodedBody = json_decode( $responseBody, true ); // Check for rate limit (429 status code). if ( 429 === $responseCode ) { // Cache the rate limit status for 1 hour. $message = ! empty( $decodedBody['message'] ) ? $decodedBody['message'] : 'Rate limit exceeded. Please try again later.'; aioseo()->core->cache->update( 'ai_insights_rate_limit', [ 'reached' => true, 'message' => $message ], HOUR_IN_SECONDS ); return new \WP_REST_Response( [ 'success' => false, 'message' => $message, 'code' => 'rate_limit_exceeded' ], 429 ); } if ( empty( $decodedBody['success'] ) || empty( $decodedBody['data']['report']['uuid'] ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Failed to regenerate report.' ], 500 ); } $reportUuid = $decodedBody['data']['report']['uuid']; // Clear rate limit cache on successful request. aioseo()->core->cache->delete( 'ai_insights_rate_limit' ); // Insert or update report in database. $newReport = Models\AiInsightsKeywordReport::getByUuid( $reportUuid ); if ( ! $newReport->exists() ) { $newReport->uuid = $reportUuid; $newReport->keyword = $report->keyword; $newReport->status = 'pending'; $newReport->save(); } // Delete the old report. $report->delete(); return new \WP_REST_Response( [ 'success' => true, 'data' => [ 'uuid' => $decodedBody['data']['report']['uuid'] ] ], 200 ); } /** * Deletes a report. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function deleteReport( $request ) { $uuid = ! empty( $request['uuid'] ) ? sanitize_text_field( $request['uuid'] ) : ''; if ( empty( $uuid ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'UUID is required.' ], 400 ); } // Get report from local database. $report = Models\AiInsightsKeywordReport::getByUuid( $uuid ); if ( ! $report->exists() ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Report not found.' ], 404 ); } // Delete from local database. $report->delete(); return new \WP_REST_Response( [ 'success' => true ], 200 ); } /** * Subscribes an email to the brand tracker newsletter. * * @since 4.9.1 * * @param \WP_REST_Request $request The REST Request. * @return \WP_REST_Response The response. */ public static function subscribeBrandTracker( $request ) { $email = ! empty( $request['email'] ) ? sanitize_email( $request['email'] ) : ''; if ( empty( $email ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Email is required.' ], 400 ); } if ( ! is_email( $email ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => 'Invalid email address.' ], 400 ); } // Get marketing site URL. $marketingSiteUrl = defined( 'AIOSEO_MARKETING_SITE_URL' ) && AIOSEO_MARKETING_SITE_URL ? AIOSEO_MARKETING_SITE_URL : 'https://aioseo.com/'; $marketingSiteUrl = trailingslashit( $marketingSiteUrl ); // Construct the endpoint URL for the marketing site REST API. $endpointUrl = $marketingSiteUrl . 'wp-json/aioseo-site/v1/newsletter/subscribe'; $response = aioseo()->helpers->wpRemotePost( $endpointUrl, [ 'timeout' => 30, 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode( [ 'email' => $email, 'source' => 'ai-insights-brand-tracker' ] ) ] ); if ( is_wp_error( $response ) ) { return new \WP_REST_Response( [ 'success' => false, 'message' => $response->get_error_message() ], $response->get_error_code() ); } $responseCode = wp_remote_retrieve_response_code( $response ); $responseBody = wp_remote_retrieve_body( $response ); $decodedBody = json_decode( $responseBody, true ); if ( 200 !== $responseCode && 201 !== $responseCode ) { $message = ! empty( $decodedBody['message'] ) ? $decodedBody['message'] : 'Failed to subscribe to newsletter.'; return new \WP_REST_Response( [ 'success' => false, 'message' => $message ], $responseCode ); } return new \WP_REST_Response( [ 'success' => true, 'message' => ! empty( $decodedBody['message'] ) ? $decodedBody['message'] : 'Successfully subscribed to newsletter.' ], 200 ); } }