PluginProbe
wpForo Forum / 3.1.0
wpForo Forum v3.1.0
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 1.4.2 All 137 releases
wpforo / admin / pages / ai-features.php

ai-features.php in wpForo Forum 3.1.0, at admin/pages/ai-features.php

374 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * wpForo AI Features - Admin Page
4 *
5 * Main administration page for managing AI features integration, subscription,
6 * and usage monitoring.
7 *
8 * @since 3.0.0
9 */
10
11 // Security: Check permissions
12 if ( ! wpforo_current_user_is( 'admin' ) && ! WPF()->usergroup->can( 'mai' ) ) {
13 wp_die( __( 'You do not have sufficient permissions to access this page.', 'wpforo' ) );
14 }
15
16 // Include helper functions and tab content files FIRST (before using them)
17 require_once __DIR__ . '/tabs/ai-features-helpers.php';
18 require_once __DIR__ . '/tabs/ai-features-tab-overview.php';
19 require_once __DIR__ . '/tabs/ai-features-tab-rag-indexing.php';
20 require_once __DIR__ . '/tabs/ai-features-tab-wp-indexing.php';
21 require_once __DIR__ . '/tabs/ai-features-tab-ai-tools.php';
22 require_once __DIR__ . '/tabs/ai-features-tab-ai-tasks.php';
23 require_once __DIR__ . '/tabs/ai-features-tab-analytics.php';
24 require_once __DIR__ . '/tabs/ai-features-tab-ai-logs.php';
25
26 // Determine current tab early (needed for conditional script loading)
27 $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'overview';
28
29 // Enqueue AI Features scripts and styles
30 wp_enqueue_style( 'wpforo-ai-features', WPFORO_URL . '/admin/assets/css/ai-features.css', [], WPFORO_VERSION );
31
32 // Chart.js is only needed on the analytics tab - don't load it elsewhere
33 $ai_features_deps = [ 'jquery', 'suggest' ];
34 if ( $current_tab === 'analytics' ) {
35 wp_enqueue_script( 'wpforo-chart-js', WPFORO_URL . '/admin/assets/js/chart.min.js', [], '4.4.1', true );
36 $ai_features_deps[] = 'wpforo-chart-js';
37 }
38 wp_enqueue_script( 'wpforo-ai-features', WPFORO_URL . '/admin/assets/js/ai-features.js', $ai_features_deps, WPFORO_VERSION, false );
39
40 // WordPress Indexing tab - load isolated scripts/styles
41 if ( $current_tab === 'wp_indexing' ) {
42 wp_enqueue_style( 'wpforo-ai-wp-indexing', WPFORO_URL . '/admin/assets/css/ai-features-wp-indexing.css', [ 'wpforo-ai-features' ], WPFORO_VERSION );
43 wp_enqueue_script( 'wpforo-ai-wp-indexing', WPFORO_URL . '/admin/assets/js/ai-features-wp-indexing.js', [ 'jquery' ], WPFORO_VERSION, true );
44 }
45
46 // File Indexing tab - load isolated scripts/styles and media library
47 if ( $current_tab === 'ai_tools' ) {
48 wp_enqueue_media();
49 wp_enqueue_style( 'wpforo-ai-tools', WPFORO_URL . '/admin/assets/css/ai-features-tools.css', [ 'wpforo-ai-features' ], WPFORO_VERSION );
50 wp_enqueue_script( 'wpforo-ai-tools', WPFORO_URL . '/admin/assets/js/ai-features-tools.js', [ 'jquery', 'wpforo-ai-features' ], WPFORO_VERSION, true );
51 }
52
53 // Localize script with AJAX URL and nonce
54 wp_localize_script( 'wpforo-ai-features', 'wpforoAIAdmin', [
55 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
56 'nonce' => wp_create_nonce( 'wpforo_ai_features_nonce' ),
57 'adminNonce' => wp_create_nonce( 'wpforo_admin_ajax' ), // For WordPress content indexing AJAX calls
58 'debugMode' => (bool) wpforo_setting( 'general', 'debug_mode' ),
59 'strings' => [
60 'indexing' => __( 'Indexing...', 'wpforo' ),
61 'idle' => __( 'Idle', 'wpforo' ),
62 'noActivity' => __( 'No activity yet', 'wpforo' ),
63 ],
64 ] );
65
66 // Localize i18n strings for AI logs (wpforoAI is used by WpForoAILogs module)
67 wp_localize_script( 'wpforo-ai-features', 'wpforoAI', [
68 'i18n' => [
69 'confirmDelete' => __( 'Are you sure you want to delete this log?', 'wpforo' ),
70 'confirmDeleteSelected' => __( 'Are you sure you want to delete the selected logs?', 'wpforo' ),
71 'deleteAllLogs' => __( 'Delete All Logs', 'wpforo' ),
72 'noLogs' => __( 'No logs found.', 'wpforo' ),
73 ],
74 ] );
75
76 // Initialize AI Client
77 if ( ! isset( WPF()->ai_client ) ) {
78 WPF()->ai_client = new \wpforo\classes\AIClient();
79 }
80
81 // Pricing is now static - no cache refresh needed
82
83 // Handle form submissions
84 $notice = wpforo_ai_handle_form_actions();
85
86 // Clear WordPress object cache for options (in case of external cache plugins)
87 // This ensures fresh values from DB after form submissions
88 wp_cache_delete( 'alloptions', 'options' );
89 wp_cache_delete( 'notoptions', 'options' );
90
91 // Note: Status transient is only cleared by specific actions (connect, disconnect, refresh)
92 // NOT on every page load - the 5-minute cache in get_tenant_status() prevents excessive API calls
93
94 // Get current connection status (fresh from database)
95 // Use global options (shared across all boards) to check connection
96 $api_key = WPF()->ai_client->get_api_key();
97 $tenant_id = WPF()->ai_client->get_tenant_id();
98 $is_connected = ! empty( $api_key ) && ! empty( $tenant_id );
99
100 // Check if returning from Freemius purchase (for status badge update)
101 $is_post_purchase = (isset( $_GET['upgraded'] ) && $_GET['upgraded'] == '1') || (isset( $_GET['credits_purchased'] ) && $_GET['credits_purchased'] == '1');
102 $purchased_plan = isset( $_GET['plan'] ) ? sanitize_key( $_GET['plan'] ) : '';
103
104 // Get tenant status if connected
105 // Force fresh fetch when returning from purchase to get updated credits
106 $status = null;
107 if ( $is_connected ) {
108 $status = WPF()->ai_client->get_tenant_status( $is_post_purchase );
109 if ( is_wp_error( $status ) ) {
110 // Connection exists but status fetch failed - show error state
111 $connection_error = $status;
112 $is_connected = false;
113 }
114 }
115
116 // Determine current state
117 $current_state = wpforo_ai_get_current_state( $is_connected, $status );
118
119 // If pending_approval and status is missing/error, construct from transient
120 if ( $current_state === 'pending_approval' && ( ! $status || is_wp_error( $status ) ) ) {
121 $pending = get_transient( 'wpforo_ai_pending_approval' );
122 if ( $pending ) {
123 $status = [
124 'subscription' => [
125 'status' => 'pending_approval',
126 'plan' => 'free_trial',
127 'credits_total' => wpfval( $pending, 'credits_total' ) ?: 500,
128 ],
129 ];
130 }
131 }
132
133 ?>
134
135 <div class="wrap wpforo-ai-wrap">
136 <h1 class="wpforo-ai-title">
137 <svg width="50px" height="50px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
138 <title>ai</title>
139 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
140 <g id="icon" fill="#000000" transform="translate(64.000000, 64.000000)">
141 <path d="M320,64 L320,320 L64,320 L64,64 L320,64 Z M171.749388,128 L146.817842,128 L99.4840387,256 L121.976629,256 L130.913039,230.977 L187.575039,230.977 L196.319607,256 L220.167172,256 L171.749388,128 Z M260.093778,128 L237.691519,128 L237.691519,256 L260.093778,256 L260.093778,128 Z M159.094727,149.47526 L181.409039,213.333 L137.135039,213.333 L159.094727,149.47526 Z M341.333333,256 L384,256 L384,298.666667 L341.333333,298.666667 L341.333333,256 Z M85.3333333,341.333333 L128,341.333333 L128,384 L85.3333333,384 L85.3333333,341.333333 Z M170.666667,341.333333 L213.333333,341.333333 L213.333333,384 L170.666667,384 L170.666667,341.333333 Z M85.3333333,0 L128,0 L128,42.6666667 L85.3333333,42.6666667 L85.3333333,0 Z M256,341.333333 L298.666667,341.333333 L298.666667,384 L256,384 L256,341.333333 Z M170.666667,0 L213.333333,0 L213.333333,42.6666667 L170.666667,42.6666667 L170.666667,0 Z M256,0 L298.666667,0 L298.666667,42.6666667 L256,42.6666667 L256,0 Z M341.333333,170.666667 L384,170.666667 L384,213.333333 L341.333333,213.333333 L341.333333,170.666667 Z M0,256 L42.6666667,256 L42.6666667,298.666667 L0,298.666667 L0,256 Z M341.333333,85.3333333 L384,85.3333333 L384,128 L341.333333,128 L341.333333,85.3333333 Z M0,170.666667 L42.6666667,170.666667 L42.6666667,213.333333 L0,213.333333 L0,170.666667 Z M0,85.3333333 L42.6666667,85.3333333 L42.6666667,128 L0,128 L0,85.3333333 Z" id="Combined-Shape">
142
143 </path>
144 </g>
145 </g>
146 </svg>
147 <?php _e( 'wpForo AI Features', 'wpforo' ); ?>
148 </h1>
149
150 <?php
151 // Display notices
152 if ( $notice ) {
153 wpforo_ai_display_notice( $notice );
154 }
155 ?>
156
157 <!-- Tab Navigation -->
158 <?php
159 // $current_tab is set at top of file (for conditional script loading)
160 $tabs = array(
161 'overview' => __( 'Overview', 'wpforo' ),
162 );
163
164 // Only show AI feature tabs when subscription is active or trial
165 // For expired, inactive, pending_approval, not_connected, or error states - only show Overview
166 if ( in_array( $current_state, [ 'free_trial', 'paid_plan' ], true ) ) {
167 $tabs['rag_indexing'] = __( 'Forum Indexing', 'wpforo' );
168
169 // WordPress Indexing tab - only show if feature is available (Business+ plan)
170 if ( isset( WPF()->ai_client ) && WPF()->ai_client->is_feature_available( 'wordpress_content_indexing' ) ) {
171 $tabs['wp_indexing'] = __( 'WordPress Indexing', 'wpforo' );
172 }
173
174 // File Indexing tab - only show if custom_knowledge feature is available (Business+ plan + cloud storage)
175 if ( isset( WPF()->ai_client ) && WPF()->ai_client->is_feature_available( 'custom_knowledge' ) ) {
176 $tabs['ai_tools'] = __( 'File Indexing', 'wpforo' );
177 }
178
179 $tabs['ai_tasks'] = __( 'AI Tasks', 'wpforo' );
180 $tabs['analytics'] = __( 'AI Analytics', 'wpforo' );
181 $tabs['ai_logs'] = __( 'AI Logs', 'wpforo' );
182 }
183
184 // Force redirect to overview tab if user tries to access restricted tab
185 if ( ! isset( $tabs[ $current_tab ] ) ) {
186 $current_tab = 'overview';
187 }
188 ?>
189 <?php
190 // Get all active boards for AI Settings tab
191 $all_boards = WPF()->board->get_boards( [ 'status' => true ] );
192 $is_multiboard = count( $all_boards ) > 1;
193 ?>
194 <nav class="nav-tab-wrapper wpforo-ai-tabs">
195 <?php foreach ( $tabs as $tab_key => $tab_label ) : ?>
196 <?php
197 $tab_url = add_query_arg( array(
198 'page' => 'wpforo-ai',
199 'tab' => $tab_key,
200 ), admin_url( 'admin.php' ) );
201 $active_class = ( $current_tab === $tab_key ) ? 'nav-tab-active' : '';
202 ?>
203 <a href="<?php echo esc_url( $tab_url ); ?>" class="nav-tab <?php echo esc_attr( $active_class ); ?>">
204 <?php echo esc_html( $tab_label ); ?>
205 </a>
206 <?php endforeach; ?>
207
208 <?php
209 // AI Settings tab - links to board settings pages
210 // Only show when subscription is active (not expired, inactive, etc.)
211 $show_settings_tab = $is_connected && in_array( $current_state, [ 'free_trial', 'paid_plan' ], true );
212 ?>
213 <?php if ( $show_settings_tab && $is_multiboard ) : ?>
214 <span class="nav-tab wpforo-ai-settings-tab">
215 <span style="color: #000;"><?php _e( 'AI Settings', 'wpforo' ); ?></span>
216 [ <?php
217 $board_links = [];
218 foreach ( $all_boards as $board ) {
219 $boardid = (int) $board['boardid'];
220 // Build settings page URL: wpforo-settings for board 0, wpforo-{id}-settings for others
221 $settings_page = ( $boardid === 0 ) ? 'wpforo-settings' : 'wpforo-' . $boardid . '-settings';
222 $settings_url = admin_url( 'admin.php?page=' . $settings_page . '&wpf_tab=ai#wpf-settings-tab');
223 $board_links[] = sprintf(
224 '<a href="%s">%s</a>',
225 esc_url( $settings_url ),
226 esc_html( $board['title'] )
227 );
228 }
229 echo implode( ' | ', $board_links );
230 ?> ]
231 </span>
232 <?php elseif ( $show_settings_tab ) : ?>
233 <?php
234 // Single board - direct link to settings
235 $settings_url = admin_url( 'admin.php?page=wpforo-settings&wpf_tab=ai#wpf-settings-tab' );
236 ?>
237 <a href="<?php echo esc_url( $settings_url ); ?>" class="nav-tab wpforo-ai-settings-tab" style="cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 7px;">
238 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12,8a4,4,0,1,0,4,4A4,4,0,0,0,12,8Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,14Z"></path><path d="M21.294,13.9l-.444-.256a9.1,9.1,0,0,0,0-3.29l.444-.256a3,3,0,1,0-3-5.2l-.445.257A8.977,8.977,0,0,0,15,3.513V3A3,3,0,0,0,9,3v.513A8.977,8.977,0,0,0,6.152,5.159L5.705,4.9a3,3,0,0,0-3,5.2l.444.256a9.1,9.1,0,0,0,0,3.29l-.444.256a3,3,0,1,0,3,5.2l.445-.257A8.977,8.977,0,0,0,9,20.487V21a3,3,0,0,0,6,0v-.513a8.977,8.977,0,0,0,2.848-1.646l.447.258a3,3,0,0,0,3-5.2Zm-2.548-3.776a7.048,7.048,0,0,1,0,3.75,1,1,0,0,0,.464,1.133l1.084.626a1,1,0,0,1-1,1.733l-1.086-.628a1,1,0,0,0-1.215.165,6.984,6.984,0,0,1-3.243,1.875,1,1,0,0,0-.751.969V21a1,1,0,0,1-2,0V19.748a1,1,0,0,0-.751-.969A6.984,6.984,0,0,1,7.006,16.9a1,1,0,0,0-1.215-.165l-1.084.627a1,1,0,1,1-1-1.732l1.084-.626a1,1,0,0,0,.464-1.133,7.048,7.048,0,0,1,0-3.75A1,1,0,0,0,4.79,8.992L3.706,8.366a1,1,0,0,1,1-1.733l1.086.628A1,1,0,0,0,7.006,7.1a6.984,6.984,0,0,1,3.243-1.875A1,1,0,0,0,11,4.252V3a1,1,0,0,1,2,0V4.252a1,1,0,0,0,.751.969A6.984,6.984,0,0,1,16.994,7.1a1,1,0,0,0,1.215.165l1.084-.627a1,1,0,1,1,1,1.732l-1.084.626A1,1,0,0,0,18.746,10.125Z"></path></svg>
239 <?php _e( 'AI Settings', 'wpforo' ); ?>
240 </a>
241 <?php endif; ?>
242 </nav>
243
244 <!-- Tab Content -->
245 <div class="wpforo-ai-tab-content">
246 <?php
247 switch ( $current_tab ) {
248 case 'rag_indexing':
249 wpforo_ai_render_rag_indexing_tab( $is_connected, $status );
250 break;
251
252 case 'wp_indexing':
253 wpforo_ai_render_wp_indexing_tab( $is_connected, $status );
254 break;
255
256 case 'ai_tools':
257 wpforo_ai_render_ai_tools_tab( $is_connected, $status );
258 break;
259
260 case 'ai_tasks':
261 wpforo_ai_render_ai_tasks_tab( $is_connected, $status );
262 break;
263
264 case 'analytics':
265 wpforo_ai_render_analytics_tab( $is_connected, $status );
266 break;
267
268 case 'ai_logs':
269 wpforo_ai_render_ai_logs_tab( $is_connected, $status );
270 break;
271
272 case 'overview':
273 default:
274 // Display appropriate state
275 switch ( $current_state ) {
276 case 'not_connected':
277 wpforo_ai_render_not_connected_state();
278 break;
279
280 case 'pending_approval':
281 wpforo_ai_render_pending_approval_state( $status );
282 break;
283
284 case 'inactive':
285 wpforo_ai_render_inactive_state( $status );
286 break;
287
288 case 'free_trial':
289 wpforo_ai_render_free_trial_state( $status, $is_post_purchase );
290 break;
291
292 case 'paid_plan':
293 wpforo_ai_render_paid_plan_state( $status, $is_post_purchase );
294 break;
295
296 case 'expired':
297 wpforo_ai_render_expired_state( $status );
298 break;
299
300 case 'cancelled':
301 wpforo_ai_render_cancelled_state( $status );
302 break;
303
304 case 'error':
305 wpforo_ai_render_error_state( $connection_error ?? $status );
306 break;
307 }
308 break;
309 }
310 ?>
311 </div>
312
313 </div>
314
315 <div style="margin-bottom: 150px;">&nbsp;</div>
316
317 <!-- Global JavaScript for all tabs -->
318 <script type="text/javascript">
319 jQuery(document).ready(function($) {
320 // Set AJAX nonce (ai-features.js will call init() automatically)
321 if (typeof window.WpForoAI !== 'undefined') {
322 WpForoAI.ajaxNonce = '<?php echo wp_create_nonce( 'wpforo_ai_features_nonce' ); ?>';
323 }
324
325 // Post-purchase notifications (works on all tabs)
326 <?php
327 $upgraded = isset( $_GET['upgraded'] ) && $_GET['upgraded'] == '1';
328 $credits_purchased = isset( $_GET['credits_purchased'] ) && $_GET['credits_purchased'] == '1';
329 $plan = isset( $_GET['plan'] ) ? sanitize_key( $_GET['plan'] ) : '';
330 $pack = isset( $_GET['pack'] ) ? sanitize_key( $_GET['pack'] ) : '';
331
332 if ( $upgraded || $credits_purchased ) {
333 // Clear status cache to force fresh fetch
334 delete_transient( 'wpforo_ai_tenant_status' );
335 }
336 ?>
337
338 <?php if ( $upgraded ) : ?>
339 // Purchase detected - show processing message
340 // The checkPostPurchaseRefresh() function will auto-refresh after 60 seconds
341 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
342 WpForoAI.showNotice('Processing your <?php echo esc_js( ucfirst( $plan ) ); ?> plan purchase... Page will refresh in 60 seconds.', 'info');
343 }
344 <?php endif; ?>
345
346 <?php if ( $credits_purchased ) : ?>
347 // Credit pack purchase detected
348 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
349 WpForoAI.showNotice('Processing your <?php echo esc_js( $pack ); ?> credits purchase... Your credits will be added shortly.', 'info');
350 }
351 <?php endif; ?>
352
353 <?php
354 // Show success messages after plan activation or credits added
355 $plan_activated = isset( $_GET['plan_activated'] ) && $_GET['plan_activated'] == '1';
356 $credits_added = isset( $_GET['credits_added'] ) && $_GET['credits_added'] == '1';
357 ?>
358
359 <?php if ( $plan_activated ) : ?>
360 // Plan successfully activated
361 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
362 WpForoAI.showNotice('✓ Your subscription plan has been successfully activated!', 'success');
363 }
364 <?php endif; ?>
365
366 <?php if ( $credits_added ) : ?>
367 // Credits successfully added
368 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
369 WpForoAI.showNotice('✓ Credits have been successfully added to your account!', 'success');
370 }
371 <?php endif; ?>
372 });
373 </script>
374