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 +60 -16 3.0.93.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() ) {
@@ -546,13 +563,15 @@
546 563 if ( isset( $topic['status'] ) && (int) $topic['status'] !== 0 ) {
547 564 return new \WP_Error( 'unapproved_topic', wpforo_phrase( 'Unapproved topics cannot be indexed', false ) );
548 565 }
549 566
550 - // 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
551 569 $posts = WPF()->post->get_posts( [
552 570 'topicid' => $topicid,
553 571 'orderby' => 'created',
554 572 'order' => 'ASC',
573 + 'status' => 0,
555 574 ] );
556 575 if ( empty( $posts ) ) {
557 576 return new \WP_Error( 'no_posts', wpforo_phrase( 'No posts found for topic', false ) );
558 577 }
@@ -809,12 +828,14 @@
809 828 }
810 829
811 830 // Bypass user permission check - this is admin-initiated backend indexing.
812 831 // Private/unapproved topics are already filtered above.
832 + // Only index approved posts (status=0) to prevent unapproved content from appearing in search.
813 833 $posts = WPF()->post->get_posts( [
814 834 'topicid' => $topicid,
815 835 'orderby' => 'created',
816 836 'order' => 'ASC',
837 + 'status' => 0,
817 838 'check_private' => false,
818 839 ] );
819 840
820 841 if ( empty( $posts ) ) {
@@ -3233,8 +3254,15 @@
3233 3254
3234 3255 $column = $this->is_local_mode() ? 'local' : 'cloud';
3235 3256 $topics_table = WPF()->tables->topics;
3236 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 +
3237 3265 // Get counts for each category in a single query
3238 3266 $results = $wpdb->get_row(
3239 3267 "SELECT
3240 3268 COUNT(*) as total,
@@ -3256,9 +3284,9 @@
3256 3284 'storage_mode' => $this->get_storage_mode(),
3257 3285 ];
3258 3286 }
3259 3287
3260 - return [
3288 + $breakdown = [
3261 3289 'total' => (int) $results['total'],
3262 3290 'indexed' => (int) $results['indexed'],
3263 3291 'pending' => (int) $results['pending'],
3264 3292 'private' => (int) $results['private_topics'],
@@ -3264,8 +3292,24 @@
3264 3292 'private' => (int) $results['private_topics'],
3265 3293 'unapproved' => (int) $results['unapproved'],
3266 3294 'storage_mode' => $this->get_storage_mode(),
3267 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 );
3268 3312 }
3269 3313
3270 3314 /**
3271 3315 * Get sample topics that are pending indexing