PluginProbe
wpForo Forum / 3.2.0
wpForo Forum v3.2.0
3.2.0 3.1.7 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 All 140 releases
wpforo / admin / pages / tabs / ai-features-tab-wp-indexing.php

ai-features-tab-wp-indexing.php in wpForo Forum 3.2.0, at admin/pages/tabs/ai-features-tab-wp-indexing.php

433 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AI Features - WordPress Content Indexing Tab
4 *
5 * Completely isolated tab for WordPress post/page/CPT indexing.
6 * Separate from forum content indexing in AI Content Indexing tab.
7 *
8 * @package wpForo
9 * @subpackage Admin
10 * @since 3.0.0
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Render WordPress Content Indexing tab content
19 *
20 * @param bool $is_connected Whether tenant is connected to AI service
21 * @param array $status Tenant status data from API
22 */
23 function wpforo_ai_render_wp_indexing_tab( $is_connected, $status ) {
24 if ( ! $is_connected ) {
25 ?>
26 <div class="wpforo-ai-box wpforo-ai-not-connected-notice">
27 <div class="wpforo-ai-box-body">
28 <div class="wpforo-ai-status-badge status-warning">
29 <span class="dashicons dashicons-warning"></span>
30 <?php _e( 'Not Connected', 'wpforo' ); ?>
31 </div>
32 <p><?php _e( 'Please connect to wpForo AI API first in the Overview tab to enable WordPress Content Indexing.', 'wpforo' ); ?></p>
33 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforo-ai&tab=overview' ) ); ?>" class="button button-primary">
34 <?php _e( 'Go to Overview', 'wpforo' ); ?>
35 </a>
36 </div>
37 </div>
38 <?php
39 return;
40 }
41
42 // Feature gate check - Business+ plan required
43 $wpf_ai_wp_indexing_available = isset( WPF()->ai_client ) && WPF()->ai_client->is_feature_available( 'wordpress_content_indexing' );
44 if ( ! $wpf_ai_wp_indexing_available ) {
45 ?>
46 <div class="wpforo-ai-box wpforo-ai-upgrade-notice">
47 <div class="wpforo-ai-box-body">
48 <div class="wpforo-ai-status-badge status-warning">
49 <span class="dashicons dashicons-lock"></span>
50 <?php _e( 'Business Plan Required', 'wpforo' ); ?>
51 </div>
52 <p><?php _e( 'WordPress Content Indexing is available on Business and Enterprise plans. Upgrade to index WordPress posts, pages, and custom post types for AI-powered search.', 'wpforo' ); ?></p>
53 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforo-ai&tab=overview' ) ); ?>" class="button button-primary">
54 <?php _e( 'View Plans', 'wpforo' ); ?>
55 </a>
56 </div>
57 </div>
58 <?php
59 return;
60 }
61
62 // Get storage mode from main board (WordPress content is global, not board-specific)
63 $storage_manager = WPF()->vector_storage->for_board( 0 );
64 $storage_mode = $storage_manager->get_storage_mode();
65
66 // Get WordPress auto-indexing option (global, not board-specific)
67 $wp_auto_indexing_enabled = (bool) get_option( 'wpforo_ai_wp_auto_indexing_enabled', 0 );
68
69 // Get WordPress image indexing option
70 $wp_image_indexing_enabled = (bool) get_option( 'wpforo_ai_wp_image_indexing_enabled', 0 );
71
72 // Get post types from the indexer
73 $wp_post_types = [];
74 if ( isset( WPF()->ai_wp_indexer ) ) {
75 $wp_post_types = WPF()->ai_wp_indexer->get_public_post_types();
76 }
77
78 // Calculate totals
79 $wp_total_content = 0;
80 foreach ( $wp_post_types as $type ) {
81 $wp_total_content += $type['count'];
82 }
83
84 // Get initial indexed counts for page load
85 $wp_initial_status = null;
86 $wp_initial_indexed = 0;
87 $wp_is_indexing = false;
88 $wp_last_activity = null;
89 if ( isset( WPF()->ai_wp_indexer ) ) {
90 $wp_initial_status = WPF()->ai_wp_indexer->get_indexing_status( true );
91 if ( ! is_wp_error( $wp_initial_status ) ) {
92 $wp_initial_indexed = (int) ( $wp_initial_status['total_indexed'] ?? 0 );
93 $wp_is_indexing = (bool) ( $wp_initial_status['is_indexing'] ?? false );
94 $wp_last_activity = $wp_initial_status['last_indexed_at'] ?? null;
95 }
96 }
97
98 // Get remaining credits from subscription data
99 $remaining_credits = 0;
100 if ( isset( $status['subscription']['credits_remaining'] ) ) {
101 $remaining_credits = (int) $status['subscription']['credits_remaining'];
102 }
103 ?>
104 <div class="wpforo-ai-wp-indexing-tab">
105
106 <!-- Indexing Status Box -->
107 <div id="wp-indexing-status-box" class="wpforo-ai-box wpforo-ai-wp-status-box">
108 <div class="wpforo-ai-box-header">
109 <h2>
110 <span class="dashicons dashicons-chart-bar"></span>
111 <?php _e( 'Indexing Status', 'wpforo' ); ?>
112 </h2>
113 <div class="wpforo-ai-header-actions">
114 <button type="button" class="button button-small wpforo-ai-wp-refresh-status" data-loading-text="<?php esc_attr_e( 'Refreshing...', 'wpforo' ); ?>">
115 <span class="dashicons dashicons-update"></span>
116 <?php _e( 'Refresh Status', 'wpforo' ); ?>
117 </button>
118 </div>
119 </div>
120 <div class="wpforo-ai-box-body">
121 <div class="wpforo-ai-wp-status-grid">
122 <div class="wpforo-ai-wp-status-item">
123 <div class="status-label"><?php _e( 'Storage Mode', 'wpforo' ); ?></div>
124 <div class="status-value">
125 <?php if ( $storage_mode === 'cloud' ) : ?>
126 <span class="dashicons dashicons-cloud"></span>
127 <?php _e( 'Cloud (AWS S3 Vectors)', 'wpforo' ); ?>
128 <?php else : ?>
129 <span class="dashicons dashicons-database"></span>
130 <?php _e( 'Local (WordPress Database)', 'wpforo' ); ?>
131 <?php endif; ?>
132 </div>
133 <div class="status-action">
134 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforo-ai&tab=rag_indexing' ) ); ?>">
135 <?php _e( 'Change in AI Content Indexing', 'wpforo' ); ?> &rarr;
136 </a>
137 </div>
138 </div>
139
140 <div class="wpforo-ai-wp-status-item wpforo-ai-wp-stat-large">
141 <div class="status-label"><?php _e( 'Current Status', 'wpforo' ); ?></div>
142 <div class="status-value <?php echo $wp_is_indexing ? 'status-active' : 'status-idle'; ?>" id="wp-indexing-status">
143 <?php if ( $wp_is_indexing ) : ?>
144 <span class="dashicons dashicons-update-alt wpforo-wp-indexing-spin"></span>
145 <?php _e( 'Indexing...', 'wpforo' ); ?>
146 <?php else : ?>
147 <span class="dashicons dashicons-yes-alt"></span>
148 <?php _e( 'Idle', 'wpforo' ); ?>
149 <?php endif; ?>
150 </div>
151 </div>
152
153 <div class="wpforo-ai-wp-status-item wpforo-ai-wp-stat-large">
154 <div class="status-label"><?php _e( 'Last Activity', 'wpforo' ); ?></div>
155 <div class="status-value" id="wp-last-activity">
156 <?php
157 if ( $wp_last_activity ) {
158 echo esc_html( wpforo_ai_format_date( $wp_last_activity ) );
159 } else {
160 _e( 'No activity yet', 'wpforo' );
161 }
162 ?>
163 </div>
164 </div>
165
166 <div class="wpforo-ai-wp-status-item wpforo-ai-wp-stat-large">
167 <div class="status-label"><?php _e( 'Remaining Credits', 'wpforo' ); ?></div>
168 <div class="status-value" id="wp-remaining-credits">
169 <?php echo number_format( $remaining_credits ); ?>
170 </div>
171 </div>
172 </div>
173 </div>
174 </div>
175
176 <!-- WordPress Content Indexing Box -->
177 <div class="wpforo-ai-box wpforo-ai-wordpress-indexing-box">
178 <div class="wpforo-ai-box-header">
179 <h2>
180 <span class="dashicons dashicons-wordpress-alt"></span>
181 <?php _e( 'WordPress Content Indexing', 'wpforo' ); ?>
182 </h2>
183 <div class="wpforo-ai-header-actions">
184 <!-- Image indexing temporarily disabled - feature not yet implemented -->
185 <label class="wpforo-ai-auto-index-toggle" style="display:none;" title="<?php esc_attr_e( 'Include images in indexing (Business/Enterprise). Posts with images will consume +1 credit.', 'wpforo' ); ?>">
186 <span class="wpforo-ai-auto-index-label">
187 <?php _e( 'Index Images', 'wpforo' ); ?>
188 </span>
189 <div class="wpforo-ai-switch">
190 <input type="checkbox" id="wpforo-ai-wp-image-indexing" name="ai_wp_image_indexing_enabled" value="1" <?php checked( $wp_image_indexing_enabled ); ?> data-option-name="ai_wp_image_indexing_enabled">
191 <span class="wpforo-ai-switch-slider"></span>
192 </div>
193 </label>
194 <!-- Auto-indexing temporarily disabled - will be improved in future release -->
195 <label class="wpforo-ai-auto-index-toggle" style="display:none;" title="<?php esc_attr_e( 'Automatically index new and updated WordPress content', 'wpforo' ); ?>">
196 <span class="wpforo-ai-auto-index-label"><?php _e( 'Automatically index new content', 'wpforo' ); ?></span>
197 <div class="wpforo-ai-switch">
198 <input type="checkbox" id="wpforo-ai-wp-auto-indexing" name="ai_wp_auto_indexing_enabled" value="1" <?php checked( $wp_auto_indexing_enabled ); ?> data-option-name="ai_wp_auto_indexing_enabled">
199 <span class="wpforo-ai-switch-slider"></span>
200 </div>
201 </label>
202 </div>
203 </div>
204 <div class="wpforo-ai-box-body">
205 <p class="wpforo-ai-description">
206 <?php _e( 'Index WordPress posts, pages, and custom post types to enable AI-powered search across all your site content. This works alongside forum content indexing.', 'wpforo' ); ?>
207 </p>
208
209 <!-- WordPress Content Stats -->
210 <div class="wpforo-ai-wp-stats">
211 <div class="rag-stat-item">
212 <div class="stat-icon">
213 <span class="dashicons dashicons-admin-page"></span>
214 </div>
215 <div class="stat-info">
216 <div class="stat-value" id="wp-total-content"><?php echo number_format( $wp_total_content ); ?></div>
217 <div class="stat-label"><?php _e( 'Total WordPress Content', 'wpforo' ); ?></div>
218 </div>
219 </div>
220 <div class="rag-stat-item">
221 <div class="stat-icon">
222 <span class="dashicons dashicons-database-view"></span>
223 </div>
224 <div class="stat-info">
225 <div class="stat-value" id="wp-total-indexed"><?php echo number_format( $wp_initial_indexed ); ?></div>
226 <div class="stat-label"><?php _e( 'Total Indexed', 'wpforo' ); ?></div>
227 </div>
228 </div>
229 <div class="rag-stat-item">
230 <div class="stat-icon">
231 <span class="dashicons dashicons-chart-pie"></span>
232 </div>
233 <div class="stat-info">
234 <?php
235 $wp_percentage = $wp_total_content > 0 ? round( ( $wp_initial_indexed / $wp_total_content ) * 100, 1 ) : 0;
236 ?>
237 <div class="stat-value" id="wp-indexed-percentage"><?php echo $wp_percentage; ?>%</div>
238 <div class="stat-label"><?php _e( 'Coverage', 'wpforo' ); ?></div>
239 </div>
240 </div>
241 </div>
242
243 <?php if ( empty( $wp_post_types ) ) : ?>
244 <div class="wpforo-ai-notice wpforo-ai-notice-warning">
245 <span class="dashicons dashicons-warning"></span>
246 <p><?php _e( 'No public post types with content found.', 'wpforo' ); ?></p>
247 </div>
248 <?php else : ?>
249
250 <div class="wpforo-ai-ingest-grid wpforo-ai-wp-ingest-grid">
251 <!-- Taxonomy Selection Column -->
252 <div class="wpforo-ai-ingest-column">
253 <h3><?php _e( 'Index by Categories/Taxonomies', 'wpforo' ); ?></h3>
254 <p class="description">
255 <?php _e( 'Select a taxonomy and term to index all content in that category.', 'wpforo' ); ?>
256 </p>
257
258 <form class="wpforo-ai-wp-taxonomy-form">
259 <?php wp_nonce_field( 'wpforo_admin_ajax', 'security' ); ?>
260
261 <!-- Date Range Filter -->
262 <div class="form-row wpforo-ai-date-range">
263 <label><?php _e( 'Date Range (optional)', 'wpforo' ); ?></label>
264 <div class="wpforo-ai-date-inputs">
265 <input type="date" id="wp-tax-date-from" name="date_from" class="regular-text">
266 <span class="wpforo-ai-date-separator"><?php _e( 'to', 'wpforo' ); ?></span>
267 <input type="date" id="wp-tax-date-to" name="date_to" class="regular-text">
268 </div>
269 <p class="description">
270 <?php _e( 'Filter by content publication date within the selected category.', 'wpforo' ); ?>
271 </p>
272 </div>
273
274 <div class="form-row">
275 <label for="wp-taxonomy-select">
276 <?php _e( 'Taxonomy', 'wpforo' ); ?>
277 </label>
278 <select id="wp-taxonomy-select" name="taxonomy" class="regular-text">
279 <option value=""><?php _e( 'Select a taxonomy...', 'wpforo' ); ?></option>
280 <?php
281 // Get taxonomies for the first post type by default
282 $first_type = reset( $wp_post_types );
283 if ( $first_type && isset( WPF()->ai_wp_indexer ) ) {
284 $taxonomies = WPF()->ai_wp_indexer->get_taxonomies_for_post_type( $first_type['name'] );
285 foreach ( $taxonomies as $tax ) :
286 ?>
287 <option value="<?php echo esc_attr( $tax['name'] ); ?>">
288 <?php echo esc_html( $tax['label'] ); ?> (<?php echo number_format( $tax['term_count'] ); ?> terms)
289 </option>
290 <?php
291 endforeach;
292 }
293 ?>
294 </select>
295 </div>
296
297 <div class="form-row">
298 <label>
299 <?php _e( 'Terms/Categories', 'wpforo' ); ?>
300 </label>
301 <div id="wp-terms-container" class="wpforo-ai-terms-container">
302 <div class="wpforo-ai-terms-placeholder">
303 <?php _e( 'Select a taxonomy first to load terms...', 'wpforo' ); ?>
304 </div>
305 </div>
306 <div id="wp-terms-actions" class="wpforo-ai-terms-actions" style="display: none;">
307 <button type="button" class="button button-small wpforo-ai-wp-select-all-terms">
308 <?php _e( 'Select All', 'wpforo' ); ?>
309 </button>
310 <button type="button" class="button button-small wpforo-ai-wp-deselect-all-terms">
311 <?php _e( 'Deselect All', 'wpforo' ); ?>
312 </button>
313 </div>
314 </div>
315
316 <div class="form-actions">
317 <button type="submit" class="button button-primary button-large wpforo-ai-wp-index-taxonomy" disabled>
318 <span class="dashicons dashicons-upload"></span>
319 <?php _e( 'Index Selected Terms', 'wpforo' ); ?>
320 </button>
321 </div>
322 </form>
323 </div>
324
325 <!-- Custom Indexing Column -->
326 <div class="wpforo-ai-ingest-column wpforo-ai-wp-custom-index">
327 <h3><?php _e( 'Index by Content Type', 'wpforo' ); ?></h3>
328 <p class="description">
329 <?php _e( 'Select content types and optionally filter by date range.', 'wpforo' ); ?>
330 </p>
331
332 <form class="wpforo-ai-wp-custom-form">
333 <?php wp_nonce_field( 'wpforo_admin_ajax', 'security' ); ?>
334
335 <!-- Date Range Filter -->
336 <div class="form-row wpforo-ai-date-range">
337 <label><?php _e( 'Date Range (optional)', 'wpforo' ); ?></label>
338 <div class="wpforo-ai-date-inputs">
339 <input type="date" id="wp-date-from" name="date_from" class="regular-text">
340 <span class="wpforo-ai-date-separator"><?php _e( 'to', 'wpforo' ); ?></span>
341 <input type="date" id="wp-date-to" name="date_to" class="regular-text">
342 </div>
343 <p class="description">
344 <?php _e( 'Leave empty to index all content of selected types.', 'wpforo' ); ?>
345 </p>
346 </div>
347
348 <!-- Content Types Selection -->
349 <div class="form-row">
350 <label><?php _e( 'Content Types', 'wpforo' ); ?></label>
351 <p class="description">
352 <?php _e( 'Not all posts may be indexed. Content generated dynamically by shortcodes or page builders may not contain indexable text. Posts with very short content (less than 10 characters after stripping HTML and shortcodes) are also skipped. The indexed count may differ from the total if such content exists.', 'wpforo' ); ?>
353 </p>
354 <div class="wpforo-ai-wp-types-grid wpforo-ai-wp-types-compact">
355 <?php foreach ( $wp_post_types as $type ) : ?>
356 <label class="wpforo-ai-wp-type-item">
357 <input type="checkbox"
358 name="wp_post_types[]"
359 value="<?php echo esc_attr( $type['name'] ); ?>"
360 data-count="<?php echo esc_attr( $type['count'] ); ?>"
361 class="wpforo-ai-wp-type-checkbox">
362 <span class="type-label"><?php echo esc_html( $type['label'] ); ?></span>
363 <span class="type-count">(<?php echo number_format( $type['count'] ); ?>)</span>
364 <?php
365 $type_key = 'wp_' . $type['name'];
366 $type_indexed_count = 0;
367 if ( $wp_initial_status && ! is_wp_error( $wp_initial_status ) && isset( $wp_initial_status['by_type'][ $type_key ]['indexed'] ) ) {
368 $type_indexed_count = (int) $wp_initial_status['by_type'][ $type_key ]['indexed'];
369 }
370 ?>
371 <span class="type-indexed" id="wp-indexed-<?php echo esc_attr( $type['name'] ); ?>">
372 <span class="indexed-count"><?php echo (int) $type_indexed_count; ?></span> <?php _e( 'indexed', 'wpforo' ); ?>
373 </span>
374 </label>
375 <?php endforeach; ?>
376 </div>
377 </div>
378
379 <div class="form-actions">
380 <button type="submit" class="button button-primary button-large wpforo-ai-wp-index-custom">
381 <span class="dashicons dashicons-upload"></span>
382 <?php _e( 'Index Selected Content', 'wpforo' ); ?>
383 </button>
384 </div>
385 </form>
386
387 <div class="wpforo-ai-section-divider wpforo-ai-section-divider-small">
388 <span><?php _e( 'Or index by specific IDs:', 'wpforo' ); ?></span>
389 </div>
390
391 <!-- Specific IDs Form (Independent) -->
392 <form class="wpforo-ai-wp-ids-form">
393 <?php wp_nonce_field( 'wpforo_admin_ajax', 'security' ); ?>
394
395 <div class="form-row">
396 <label for="wp-post-ids">
397 <?php _e( 'Post/Page IDs', 'wpforo' ); ?>
398 </label>
399 <textarea id="wp-post-ids" name="post_ids" class="large-text" rows="2" placeholder="<?php esc_attr_e( 'e.g., 123, 456, 789', 'wpforo' ); ?>"></textarea>
400 <p class="description">
401 <?php _e( 'Comma-separated post IDs. Indexes regardless of content type or date.', 'wpforo' ); ?>
402 </p>
403 </div>
404
405 <div class="form-actions">
406 <button type="submit" class="button button-large button-secondary wpforo-ai-wp-index-ids">
407 <span class="dashicons dashicons-upload"></span>
408 <?php _e( 'Index by IDs', 'wpforo' ); ?>
409 </button>
410 </div>
411 </form>
412 </div>
413 </div>
414
415 <!-- Quick Actions -->
416 <div class="wpforo-ai-wp-quick-actions">
417 <button type="button" class="button wpforo-ai-wp-cleanup-session" data-confirm="<?php esc_attr_e( 'Reset stuck WordPress indexing session? This clears the pending post queue, cron jobs and cached status. Already-indexed posts are NOT affected.', 'wpforo' ); ?>" title="<?php esc_attr_e( 'Clears pending WordPress post/page indexing queue, WP-Cron jobs and status cache. Use this if WordPress indexing is stuck on "Indexing..." but nothing is progressing. Works for both local and cloud storage modes. Does not delete any indexed data.', 'wpforo' ); ?>">
418 <span class="dashicons dashicons-update-alt"></span>
419 <?php _e( 'Cleanup Indexing Session', 'wpforo' ); ?>
420 </button>
421 <button type="button" class="button button-link-delete wpforo-ai-wp-clear-index" data-confirm="<?php esc_attr_e( 'This will remove all WordPress content from the AI index. Continue?', 'wpforo' ); ?>">
422 <span class="dashicons dashicons-trash"></span>
423 <?php _e( 'Clear WordPress Index', 'wpforo' ); ?>
424 </button>
425 </div>
426 <?php endif; ?>
427 </div>
428 </div>
429
430 </div>
431 <?php
432 }
433