PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 138 releases
← All changes | classes/VectorStorageManager.php +84 -23 3.0.83.1.6 View file →
@@ -78,15 +78,45 @@
78 78 * Constructor
79 79 */
80 80 public function __construct() {
81 81 $this->board_id = WPF()->board->get_current( 'boardid' );
82 - $this->register_cron_hooks();
83 82
83 + // Register the callback for the cron hook so any already-scheduled
84 + // cron event from a prior connected state can still execute (and so
85 + // schedule_cron_jobs() can rely on the action being wired). Scheduling
86 + // of the event itself is gated by AI connection — see schedule_cron_jobs().
87 + add_action( 'wpforo_ai_cleanup_expired_cache', [ $this, 'cleanup_expired_cache' ] );
88 +
84 89 // Update board_id when WPF()->change_board() is called
85 90 add_action( 'wpforo_after_change_board', [ $this, 'on_board_change' ] );
86 91 }
87 92
88 93 /**
94 + * Schedule recurring local-storage maintenance crons.
95 + *
96 + * Called from AIClient::register_ai_crons() on tenant connect.
97 + * Safe to call repeatedly — it skips if already scheduled.
98 + */
99 + public function schedule_cron_jobs() {
100 + if ( ! wp_next_scheduled( 'wpforo_ai_cleanup_expired_cache' ) ) {
101 + wp_schedule_event( time(), 'hourly', 'wpforo_ai_cleanup_expired_cache' );
102 + }
103 + }
104 +
105 + /**
106 + * Unschedule recurring local-storage maintenance crons.
107 + *
108 + * Called from AIClient::unregister_ai_crons() on tenant disconnect.
109 + */
110 + public function unschedule_cron_jobs() {
111 + $ts = wp_next_scheduled( 'wpforo_ai_cleanup_expired_cache' );
112 + if ( $ts ) {
113 + wp_unschedule_event( $ts, 'wpforo_ai_cleanup_expired_cache' );
114 + }
115 + wp_clear_scheduled_hook( 'wpforo_ai_cleanup_expired_cache' );
116 + }
117 +
118 + /**
89 119 * Handle board change event
90 120 *
91 121 * Updates internal board_id to match the new board context.
92 122 * Called by wpforo_after_change_board hook.
@@ -98,21 +128,8 @@
98 128 $this->storage_mode = null; // Reset cached storage mode
99 129 }
100 130
101 131 /**
102 - * Register cron hooks for local storage maintenance
103 - */
104 - private function register_cron_hooks() {
105 - // Register cleanup action - must be done here so callback exists when cron fires
106 - add_action( 'wpforo_ai_cleanup_expired_cache', [ $this, 'cleanup_expired_cache' ] );
107 -
108 - // Schedule if not already scheduled
109 - if ( ! wp_next_scheduled( 'wpforo_ai_cleanup_expired_cache' ) ) {
110 - wp_schedule_event( time(), 'hourly', 'wpforo_ai_cleanup_expired_cache' );
111 - }
112 - }
113 -
114 - /**
115 132 * Cleanup expired cache entries (cron callback)
116 133 */
117 134 public function cleanup_expired_cache() {
118 135 if ( $this->is_local_mode() ) {
@@ -314,8 +331,12 @@
314 331 *
315 332 * Reads the queue and settings options to calculate how many topics
316 333 * have been processed out of the total queued for indexing.
317 334 *
335 + * Checks both queue key patterns:
336 + * - wpforo_ai_indexing_queue_{board_id} (manual reindex via UI)
337 + * - wpforo_ai_indexing_queue_local_{board_id} (auto-indexing new topics)
338 + *
318 339 * @param int $board_id Board ID
319 340 * @return int Progress percentage (0-100), or 0 if no indexing in progress
320 341 */
321 342 private function calculate_local_indexing_progress( $board_id ) {
@@ -327,13 +348,21 @@
327 348 if ( $total <= 0 ) {
328 349 return 0;
329 350 }
330 351
331 - // Count remaining topics in queue
332 - $queue_key = 'wpforo_ai_indexing_queue_' . $board_id;
333 - $pending = get_option( $queue_key, [] );
334 - $remaining = is_array( $pending ) ? count( $pending ) : 0;
352 + // Count remaining topics in both queue patterns:
353 + // - Manual reindex queue (legacy pattern without mode prefix)
354 + // - Auto-indexing queue (new pattern with mode prefix)
355 + $manual_queue_key = 'wpforo_ai_indexing_queue_' . $board_id;
356 + $auto_queue_key = 'wpforo_ai_indexing_queue_local_' . $board_id;
335 357
358 + $manual_pending = get_option( $manual_queue_key, [] );
359 + $auto_pending = get_option( $auto_queue_key, [] );
360 +
361 + $manual_remaining = is_array( $manual_pending ) ? count( $manual_pending ) : 0;
362 + $auto_remaining = is_array( $auto_pending ) ? count( $auto_pending ) : 0;
363 + $remaining = $manual_remaining + $auto_remaining;
364 +
336 365 // Calculate progress
337 366 $processed = $total - $remaining;
338 367 if ( $processed < 0 ) {
339 368 $processed = 0; // Safety: queue grew larger than original total
@@ -356,11 +385,16 @@
356 385 if ( is_wp_error( $rag_status ) ) {
357 386 $rag_status = [];
358 387 }
359 388
360 - // Only report is_indexing when the backend is actively processing.
361 - // Queued WP Cron topics are reported separately via pending_cron_jobs.
362 - $is_indexing = (bool) ( $rag_status['is_indexing'] ?? false );
389 + // Report is_indexing when:
390 + // 1. Backend is actively processing (from API response), OR
391 + // 2. WP-Cron is actively processing cloud queue locally (lock transient held)
392 + // This ensures UI shows indexing state even when API response is stale.
393 + $board_id = $this->board_id;
394 + $backend_indexing = (bool) ( $rag_status['is_indexing'] ?? false );
395 + $local_cron_indexing = (bool) get_transient( 'wpforo_ai_indexing_lock_cloud_' . $board_id );
396 + $is_indexing = $backend_indexing || $local_cron_indexing;
363 397
364 398 // Use local cloud column for accurate count (reflects manual changes)
365 399 // Cache for 5 minutes to avoid heavy COUNT on large forums
366 400 $cache_key = 'wpforo_ai_cloud_indexed_count_' . $this->board_id;
@@ -529,13 +563,15 @@
529 563 if ( isset( $topic['status'] ) && (int) $topic['status'] !== 0 ) {
530 564 return new \WP_Error( 'unapproved_topic', wpforo_phrase( 'Unapproved topics cannot be indexed', false ) );
531 565 }
532 566
533 - // Get all posts for this topic ordered by creation date
567 + // Get all approved posts for this topic ordered by creation date
568 + // Only index approved posts (status=0) to prevent unapproved content from appearing in search
534 569 $posts = WPF()->post->get_posts( [
535 570 'topicid' => $topicid,
536 571 'orderby' => 'created',
537 572 'order' => 'ASC',
573 + 'status' => 0,
538 574 ] );
539 575 if ( empty( $posts ) ) {
540 576 return new \WP_Error( 'no_posts', wpforo_phrase( 'No posts found for topic', false ) );
541 577 }
@@ -792,12 +828,14 @@
792 828 }
793 829
794 830 // Bypass user permission check - this is admin-initiated backend indexing.
795 831 // Private/unapproved topics are already filtered above.
832 + // Only index approved posts (status=0) to prevent unapproved content from appearing in search.
796 833 $posts = WPF()->post->get_posts( [
797 834 'topicid' => $topicid,
798 835 'orderby' => 'created',
799 836 'order' => 'ASC',
837 + 'status' => 0,
800 838 'check_private' => false,
801 839 ] );
802 840
803 841 if ( empty( $posts ) ) {
@@ -3216,8 +3254,15 @@
3216 3254
3217 3255 $column = $this->is_local_mode() ? 'local' : 'cloud';
3218 3256 $topics_table = WPF()->tables->topics;
3219 3257
3258 + // Cache for 1 day - these counts don't change frequently
3259 + $cache_key = 'wpforo_ai_status_breakdown_' . $this->board_id . '_' . $this->get_storage_mode();
3260 + $cached = get_transient( $cache_key );
3261 + if ( false !== $cached && is_array( $cached ) ) {
3262 + return $cached;
3263 + }
3264 +
3220 3265 // Get counts for each category in a single query
3221 3266 $results = $wpdb->get_row(
3222 3267 "SELECT
3223 3268 COUNT(*) as total,
@@ -3239,9 +3284,9 @@
3239 3284 'storage_mode' => $this->get_storage_mode(),
3240 3285 ];
3241 3286 }
3242 3287
3243 - return [
3288 + $breakdown = [
3244 3289 'total' => (int) $results['total'],
3245 3290 'indexed' => (int) $results['indexed'],
3246 3291 'pending' => (int) $results['pending'],
3247 3292 'private' => (int) $results['private_topics'],
@@ -3247,8 +3292,24 @@
3247 3292 'private' => (int) $results['private_topics'],
3248 3293 'unapproved' => (int) $results['unapproved'],
3249 3294 'storage_mode' => $this->get_storage_mode(),
3250 3295 ];
3296 +
3297 + set_transient( $cache_key, $breakdown, DAY_IN_SECONDS );
3298 +
3299 + return $breakdown;
3300 + }
3301 +
3302 + /**
3303 + * Clear the indexing status breakdown cache
3304 + *
3305 + * Should be called when topics are approved/unapproved or privacy changes.
3306 + */
3307 + public function clear_indexing_status_breakdown_cache() {
3308 + $cache_key_local = 'wpforo_ai_status_breakdown_' . $this->board_id . '_local';
3309 + $cache_key_cloud = 'wpforo_ai_status_breakdown_' . $this->board_id . '_cloud';
3310 + delete_transient( $cache_key_local );
3311 + delete_transient( $cache_key_cloud );
3251 3312 }
3252 3313
3253 3314 /**
3254 3315 * Get sample topics that are pending indexing