current_user_has_visible_role() ) { return; } register_rest_route( 'aipseo/v1', '/score/(?P\d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_score' ], 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ] ); register_rest_route( 'aipseo/v1', '/ai/keywords/(?P\d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_ai_keywords' ], 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ] ); register_rest_route( 'aipseo/v1', '/ai/fixit/(?P\d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_ai_fixit' ], 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ] ); register_rest_route( 'aipseo/v1', '/internal-links/(?P\d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_internal_link_suggestions' ], 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ] ); } /** * Return the estimated SEO score for a post */ public function get_score( WP_REST_Request $request ) { $post_id = (int) $request['id']; $cached = get_post_meta( $post_id, '_ait_aipseo_score', true ); if ( ! empty( $cached ) && is_array( $cached ) ) { return rest_ensure_response( $cached ); } $analyzer = new AIT_AIPSEO_Analyzer(); $result = $analyzer->analyze( $post_id ); return rest_ensure_response( $result ); } /** * Return AI Semantic Keyword Hints (Lite). */ public function get_ai_keywords( WP_REST_Request $request ) { $post_id = (int) $request['id']; if ( $post_id <= 0 ) { return new WP_Error( 'aipseo_bad_id', __( 'Invalid post ID.', 'ai-powered-seo-suggestions' ), [ 'status' => 400 ] ); } $items = apply_filters( 'ait_aipseo_semantic_keywords', [], $post_id ); if ( ! is_array( $items ) ) { $items = []; } return rest_ensure_response( [ 'post_id' => $post_id, 'items' => array_values( array_map( 'wp_kses_post', $items ) ), ] ); } /** * Return AI Fix-It Tips (Lite). */ public function get_ai_fixit( WP_REST_Request $request ) { $post_id = (int) $request['id']; if ( $post_id <= 0 ) { return new WP_Error( 'aipseo_bad_id', __( 'Invalid post ID.', 'ai-powered-seo-suggestions' ), [ 'status' => 400 ] ); } $items = apply_filters( 'ait_aipseo_fixit_tips', [], $post_id ); if ( ! is_array( $items ) ) { $items = []; } return rest_ensure_response( [ 'post_id' => $post_id, 'items' => array_values( array_map( 'wp_kses_post', $items ) ), ] ); } /** * Suggest internal link opportunities based on the post content and other site posts. * * Returns objects like: * [ 'anchor' => 'iPad', 'title' => 'iPad Accessories', 'url' => 'https://example.com/ipad-accessories/' ] */ public function get_internal_link_suggestions( WP_REST_Request $request ) { $post_id = (int) $request['id']; if ( $post_id <= 0 ) { return new WP_Error( 'aipseo_bad_id', __( 'Invalid post ID.', 'ai-powered-seo-suggestions' ), [ 'status' => 400 ] ); } $post = get_post( $post_id ); if ( ! $post || 'trash' === $post->post_status ) { return rest_ensure_response( [ 'post_id' => $post_id, 'items' => [], ] ); } // Strip tags / shortcodes for a simple text search. $content = wp_strip_all_tags( $post->post_content ); $content_lc = mb_strtolower( $content ); // Fetch a reasonable pool of internal targets. $query = new WP_Query( [ 'post_type' => 'any', 'post_status' => 'publish', 'posts_per_page' => 200, 'post__not_in' => [ $post_id ], 'fields' => 'ids', ] ); $suggestions = []; if ( $query->have_posts() ) { foreach ( $query->posts as $target_id ) { $title = get_the_title( $target_id ); $slug = get_post_field( 'post_name', $target_id ); if ( ! $title && ! $slug ) { continue; } $candidates = []; // From slug: ipad-pro-11 => [ 'ipad', 'pro', '11' ] if ( $slug ) { foreach ( preg_split( '/[-_]+/', mb_strtolower( $slug ) ) as $part ) { $part = trim( $part ); if ( mb_strlen( $part ) >= 3 ) { $candidates[] = $part; } } } // From title words if ( $title ) { foreach ( preg_split( '/\s+/', mb_strtolower( wp_strip_all_tags( $title ) ) ) as $word ) { $word = preg_replace( '/[^\p{L}\p{N}]+/u', '', $word ); if ( mb_strlen( $word ) >= 5 ) { $candidates[] = $word; } } } $candidates = array_unique( $candidates ); $matched_anchor = null; foreach ( $candidates as $word ) { if ( ! $word ) { continue; } // Word-boundary match to avoid weird partials. if ( preg_match( '/\b' . preg_quote( $word, '/' ) . '\b/ui', $content_lc ) ) { $matched_anchor = $word; break; } } if ( ! $matched_anchor ) { continue; } $suggestions[] = [ 'anchor' => ucfirst( $matched_anchor ), 'title' => $title, 'url' => get_permalink( $target_id ), ]; if ( count( $suggestions ) >= 20 ) { break; } } return rest_ensure_response( [ 'post_id' => $post_id, 'items' => array_values( $suggestions ), ] ); } /** * Filter internal link suggestions. * * @param array $suggestions Array of [ anchor, title, url ]. * @param int $post_id Current post ID. */ $suggestions = apply_filters( 'ait_aipseo_internal_link_suggestions', $suggestions, $post_id ); return rest_ensure_response( [ 'post_id' => $post_id, 'items' => $suggestions, ] ); } private function current_user_has_visible_role(): bool { $allowed = AIT_AIPSEO_Settings::get('visible_roles'); $user = wp_get_current_user(); if ( empty( $user->roles ) ) { return false; } return (bool) array_intersect( $allowed, (array) $user->roles ); } }