logger = $logger; $this->recommender = $recommender; add_action( 'ait_airce_post_indexed', [ $this, 'invalidate_post_cache' ] ); } public function invalidate_post_cache( $post_id ) { global $wpdb; $t_cache = AIT_AIRCE_DB::table( 'reco_cache' ); $wpdb->delete( $t_cache, [ 'source_post_id' => intval( $post_id ), 'algo_version' => 1 ], [ '%d', '%d' ] ); } /** * Generate and store related items for a post. */ public function generate_for_post( $post_id, $context_key = 'default' ) { global $wpdb; $t_cache = AIT_AIRCE_DB::table( 'reco_cache' ); $max_items = max( 1, intval( AIT_AIRCE_Settings::get( 'related_count' ) ) ); $items = $this->recommender->get_related( $post_id, $max_items ); $wpdb->replace( $t_cache, [ 'source_post_id' => intval( $post_id ), 'context_key' => $context_key, 'algo_version' => 1, 'generated_at_gmt' => gmdate( 'Y-m-d H:i:s' ), 'items_json' => wp_json_encode( $items ), ], [ '%d', '%s', '%d', '%s', '%s' ] ); $this->logger->log( 'Generated cache', [ 'post_id' => $post_id, 'count' => count( $items ) ] ); return $items; } /** * Read cache for a post. */ public function get_cached( $post_id, $context_key = 'default' ) { global $wpdb; $t_cache = esc_sql( AIT_AIRCE_DB::table( 'reco_cache' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from trusted source $row = $wpdb->get_row( $wpdb->prepare( "SELECT items_json FROM {$t_cache} WHERE source_post_id = %d AND context_key = %s AND algo_version = 1", intval( $post_id ), $context_key ), ARRAY_A ); if ( ! $row ) { return null; } $items = json_decode( $row['items_json'], true ); return is_array( $items ) ? $items : null; } /** * Admin AJAX tools */ public function init_admin_tools_ajax() { add_action( 'wp_ajax_ait_airce_rebuild_index', [ $this, 'ajax_rebuild_index' ] ); add_action( 'wp_ajax_ait_airce_regenerate_cache', [ $this, 'ajax_regenerate_cache' ] ); } protected function verify_ajax() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Forbidden' ], 403 ); } check_ajax_referer( 'ait-airce-admin', 'nonce' ); } public function ajax_rebuild_index() { $this->verify_ajax(); // Batch: index first N eligible items to avoid timeouts (v1). $post_types = AIT_AIRCE_Settings::get_enabled_post_types(); $limit = intval( AIT_AIRCE_Settings::get( 'index_max_items' ) ); if ( $limit <= 0 ) { $limit = 100; } $q = new WP_Query( [ 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => $limit, 'fields' => 'ids', 'no_found_rows' => true, ] ); $logger = $this->logger; $extractor = new AIT_AIRCE_Content_Extractor(); $ai = new AIT_AIRCE_AI_Embeddings( $logger ); $indexer = new AIT_AIRCE_Indexer( $logger, $extractor, $ai ); $indexed = 0; foreach ( (array) $q->posts as $pid ) { $res = $indexer->index_post( intval( $pid ) ); if ( ! is_wp_error( $res ) ) { $indexed++; } } wp_send_json_success( [ 'message' => sprintf( 'Indexed %d item(s).', $indexed ) ] ); } public function ajax_regenerate_cache() { $this->verify_ajax(); $post_types = AIT_AIRCE_Settings::get_enabled_post_types(); $limit = intval( AIT_AIRCE_Settings::get( 'index_max_items' ) ); if ( $limit <= 0 ) { $limit = 100; } $q = new WP_Query( [ 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => $limit, 'fields' => 'ids', 'no_found_rows' => true, ] ); $generated = 0; foreach ( (array) $q->posts as $pid ) { $this->generate_for_post( intval( $pid ) ); $generated++; } wp_send_json_success( [ 'message' => sprintf( 'Generated cache for %d item(s).', $generated ) ] ); } }