PluginProbe
wpForo Forum / 3.0.2
wpForo Forum v3.0.2
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.0.2, at admin/pages/ai-features.php

334 lines 14.3 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-ai-tasks.php';
21 require_once __DIR__ . '/tabs/ai-features-tab-analytics.php';
22 require_once __DIR__ . '/tabs/ai-features-tab-ai-logs.php';
23
24 // Determine current tab early (needed for conditional script loading)
25 $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'overview';
26
27 // Enqueue AI Features scripts and styles
28 wp_enqueue_style( 'wpforo-ai-features', WPFORO_URL . '/admin/assets/css/ai-features.css', [], WPFORO_VERSION );
29
30 // Chart.js is only needed on the analytics tab - don't load it elsewhere
31 $ai_features_deps = [ 'jquery', 'suggest' ];
32 if ( $current_tab === 'analytics' ) {
33 wp_enqueue_script( 'wpforo-chart-js', WPFORO_URL . '/admin/assets/js/chart.min.js', [], '4.4.1', true );
34 $ai_features_deps[] = 'wpforo-chart-js';
35 }
36 wp_enqueue_script( 'wpforo-ai-features', WPFORO_URL . '/admin/assets/js/ai-features.js', $ai_features_deps, WPFORO_VERSION, false );
37
38 // Localize script with AJAX URL and nonce
39 wp_localize_script( 'wpforo-ai-features', 'wpforoAIAdmin', [
40 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
41 'nonce' => wp_create_nonce( 'wpforo_ai_features_nonce' ),
42 'adminNonce' => wp_create_nonce( 'wpforo_admin_ajax' ), // For WordPress content indexing AJAX calls
43 'debugMode' => (bool) wpforo_setting( 'general', 'debug_mode' ),
44 ] );
45
46 // Localize i18n strings for AI logs (wpforoAI is used by WpForoAILogs module)
47 wp_localize_script( 'wpforo-ai-features', 'wpforoAI', [
48 'i18n' => [
49 'confirmDelete' => __( 'Are you sure you want to delete this log?', 'wpforo' ),
50 'confirmDeleteSelected' => __( 'Are you sure you want to delete the selected logs?', 'wpforo' ),
51 'deleteAllLogs' => __( 'Delete All Logs', 'wpforo' ),
52 'noLogs' => __( 'No logs found.', 'wpforo' ),
53 ],
54 ] );
55
56 // Initialize AI Client
57 if ( ! isset( WPF()->ai_client ) ) {
58 WPF()->ai_client = new \wpforo\classes\AIClient();
59 }
60
61 // Pricing is now static - no cache refresh needed
62
63 // Handle form submissions
64 $notice = wpforo_ai_handle_form_actions();
65
66 // Clear WordPress object cache for options (in case of external cache plugins)
67 // This ensures fresh values from DB after form submissions
68 wp_cache_delete( 'alloptions', 'options' );
69 wp_cache_delete( 'notoptions', 'options' );
70
71 // Note: Status transient is only cleared by specific actions (connect, disconnect, refresh)
72 // NOT on every page load - the 5-minute cache in get_tenant_status() prevents excessive API calls
73
74 // Get current connection status (fresh from database)
75 // Use global options (shared across all boards) to check connection
76 $api_key = WPF()->ai_client->get_api_key();
77 $tenant_id = WPF()->ai_client->get_tenant_id();
78 $is_connected = ! empty( $api_key ) && ! empty( $tenant_id );
79
80 // Check if returning from Freemius purchase (for status badge update)
81 $is_post_purchase = (isset( $_GET['upgraded'] ) && $_GET['upgraded'] == '1') || (isset( $_GET['credits_purchased'] ) && $_GET['credits_purchased'] == '1');
82 $purchased_plan = isset( $_GET['plan'] ) ? sanitize_key( $_GET['plan'] ) : '';
83
84 // Get tenant status if connected
85 $status = null;
86 if ( $is_connected ) {
87 $status = WPF()->ai_client->get_tenant_status();
88 if ( is_wp_error( $status ) ) {
89 // Connection exists but status fetch failed - show error state
90 $connection_error = $status;
91 $is_connected = false;
92 }
93 }
94
95 // Determine current state
96 $current_state = wpforo_ai_get_current_state( $is_connected, $status );
97
98 // If pending_approval and status is missing/error, construct from transient
99 if ( $current_state === 'pending_approval' && ( ! $status || is_wp_error( $status ) ) ) {
100 $pending = get_transient( 'wpforo_ai_pending_approval' );
101 if ( $pending ) {
102 $status = [
103 'subscription' => [
104 'status' => 'pending_approval',
105 'plan' => 'free_trial',
106 'credits_total' => wpfval( $pending, 'credits_total' ) ?: 500,
107 ],
108 ];
109 }
110 }
111
112 ?>
113
114 <div class="wrap wpforo-ai-wrap">
115 <h1 class="wpforo-ai-title">
116 <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">
117 <title>ai</title>
118 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
119 <g id="icon" fill="#000000" transform="translate(64.000000, 64.000000)">
120 <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">
121
122 </path>
123 </g>
124 </g>
125 </svg>
126 <?php _e( 'wpForo AI Features', 'wpforo' ); ?>
127 </h1>
128
129 <?php
130 // Display notices
131 if ( $notice ) {
132 wpforo_ai_display_notice( $notice );
133 }
134 ?>
135
136 <!-- Tab Navigation -->
137 <?php
138 // $current_tab is set at top of file (for conditional script loading)
139 $tabs = array(
140 'overview' => __( 'Overview', 'wpforo' ),
141 );
142
143 // Only show AI feature tabs when subscription is active or trial
144 // For expired, inactive, pending_approval, not_connected, or error states - only show Overview
145 if ( in_array( $current_state, [ 'free_trial', 'paid_plan' ], true ) ) {
146 $tabs['rag_indexing'] = __( 'AI Content Indexing', 'wpforo' );
147 $tabs['ai_tasks'] = __( 'AI Tasks', 'wpforo' );
148 $tabs['analytics'] = __( 'AI Analytics', 'wpforo' );
149 $tabs['ai_logs'] = __( 'AI Logs', 'wpforo' );
150 }
151
152 // Force redirect to overview tab if user tries to access restricted tab
153 if ( ! isset( $tabs[ $current_tab ] ) ) {
154 $current_tab = 'overview';
155 }
156 ?>
157 <?php
158 // Get all active boards for AI Settings tab
159 $all_boards = WPF()->board->get_boards( [ 'status' => true ] );
160 $is_multiboard = count( $all_boards ) > 1;
161 ?>
162 <nav class="nav-tab-wrapper wpforo-ai-tabs">
163 <?php foreach ( $tabs as $tab_key => $tab_label ) : ?>
164 <?php
165 $tab_url = add_query_arg( array(
166 'page' => 'wpforo-ai',
167 'tab' => $tab_key,
168 ), admin_url( 'admin.php' ) );
169 $active_class = ( $current_tab === $tab_key ) ? 'nav-tab-active' : '';
170 ?>
171 <a href="<?php echo esc_url( $tab_url ); ?>" class="nav-tab <?php echo esc_attr( $active_class ); ?>">
172 <?php echo esc_html( $tab_label ); ?>
173 </a>
174 <?php endforeach; ?>
175
176 <?php
177 // AI Settings tab - links to board settings pages
178 // Only show when subscription is active (not expired, inactive, etc.)
179 $show_settings_tab = $is_connected && in_array( $current_state, [ 'free_trial', 'paid_plan' ], true );
180 ?>
181 <?php if ( $show_settings_tab && $is_multiboard ) : ?>
182 <span class="nav-tab wpforo-ai-settings-tab">
183 <span style="color: #000;"><?php _e( 'AI Settings', 'wpforo' ); ?></span>
184 [ <?php
185 $board_links = [];
186 foreach ( $all_boards as $board ) {
187 $boardid = (int) $board['boardid'];
188 // Build settings page URL: wpforo-settings for board 0, wpforo-{id}-settings for others
189 $settings_page = ( $boardid === 0 ) ? 'wpforo-settings' : 'wpforo-' . $boardid . '-settings';
190 $settings_url = admin_url( 'admin.php?page=' . $settings_page . '&wpf_tab=ai#wpf-settings-tab');
191 $board_links[] = sprintf(
192 '<a href="%s">%s</a>',
193 esc_url( $settings_url ),
194 esc_html( $board['title'] )
195 );
196 }
197 echo implode( ' | ', $board_links );
198 ?> ]
199 </span>
200 <?php elseif ( $show_settings_tab ) : ?>
201 <?php
202 // Single board - direct link to settings
203 $settings_url = admin_url( 'admin.php?page=wpforo-settings&wpf_tab=ai#wpf-settings-tab' );
204 ?>
205 <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;">
206 <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>
207 <?php _e( 'AI Settings', 'wpforo' ); ?>
208 </a>
209 <?php endif; ?>
210 </nav>
211
212 <!-- Tab Content -->
213 <div class="wpforo-ai-tab-content">
214 <?php
215 switch ( $current_tab ) {
216 case 'rag_indexing':
217 wpforo_ai_render_rag_indexing_tab( $is_connected, $status );
218 break;
219
220 case 'ai_tasks':
221 wpforo_ai_render_ai_tasks_tab( $is_connected, $status );
222 break;
223
224 case 'analytics':
225 wpforo_ai_render_analytics_tab( $is_connected, $status );
226 break;
227
228 case 'ai_logs':
229 wpforo_ai_render_ai_logs_tab( $is_connected, $status );
230 break;
231
232 case 'overview':
233 default:
234 // Display appropriate state
235 switch ( $current_state ) {
236 case 'not_connected':
237 wpforo_ai_render_not_connected_state();
238 break;
239
240 case 'pending_approval':
241 wpforo_ai_render_pending_approval_state( $status );
242 break;
243
244 case 'inactive':
245 wpforo_ai_render_inactive_state( $status );
246 break;
247
248 case 'free_trial':
249 wpforo_ai_render_free_trial_state( $status, $is_post_purchase );
250 break;
251
252 case 'paid_plan':
253 wpforo_ai_render_paid_plan_state( $status, $is_post_purchase );
254 break;
255
256 case 'expired':
257 wpforo_ai_render_expired_state( $status );
258 break;
259
260 case 'cancelled':
261 wpforo_ai_render_cancelled_state( $status );
262 break;
263
264 case 'error':
265 wpforo_ai_render_error_state( $connection_error ?? $status );
266 break;
267 }
268 break;
269 }
270 ?>
271 </div>
272
273 </div>
274
275 <div style="margin-bottom: 150px;">&nbsp;</div>
276
277 <!-- Global JavaScript for all tabs -->
278 <script type="text/javascript">
279 jQuery(document).ready(function($) {
280 // Set AJAX nonce (ai-features.js will call init() automatically)
281 if (typeof window.WpForoAI !== 'undefined') {
282 WpForoAI.ajaxNonce = '<?php echo wp_create_nonce( 'wpforo_ai_features_nonce' ); ?>';
283 }
284
285 // Post-purchase notifications (works on all tabs)
286 <?php
287 $upgraded = isset( $_GET['upgraded'] ) && $_GET['upgraded'] == '1';
288 $credits_purchased = isset( $_GET['credits_purchased'] ) && $_GET['credits_purchased'] == '1';
289 $plan = isset( $_GET['plan'] ) ? sanitize_key( $_GET['plan'] ) : '';
290 $pack = isset( $_GET['pack'] ) ? sanitize_key( $_GET['pack'] ) : '';
291
292 if ( $upgraded || $credits_purchased ) {
293 // Clear status cache to force fresh fetch
294 delete_transient( 'wpforo_ai_tenant_status' );
295 }
296 ?>
297
298 <?php if ( $upgraded ) : ?>
299 // Purchase detected - show processing message
300 // The checkPostPurchaseRefresh() function will auto-refresh after 60 seconds
301 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
302 WpForoAI.showNotice('Processing your <?php echo esc_js( ucfirst( $plan ) ); ?> plan purchase... Page will refresh in 60 seconds.', 'info');
303 }
304 <?php endif; ?>
305
306 <?php if ( $credits_purchased ) : ?>
307 // Credit pack purchase detected
308 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
309 WpForoAI.showNotice('Processing your <?php echo esc_js( $pack ); ?> credits purchase... Your credits will be added shortly.', 'info');
310 }
311 <?php endif; ?>
312
313 <?php
314 // Show success messages after plan activation or credits added
315 $plan_activated = isset( $_GET['plan_activated'] ) && $_GET['plan_activated'] == '1';
316 $credits_added = isset( $_GET['credits_added'] ) && $_GET['credits_added'] == '1';
317 ?>
318
319 <?php if ( $plan_activated ) : ?>
320 // Plan successfully activated
321 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
322 WpForoAI.showNotice('✓ Your subscription plan has been successfully activated!', 'success');
323 }
324 <?php endif; ?>
325
326 <?php if ( $credits_added ) : ?>
327 // Credits successfully added
328 if (typeof WpForoAI !== 'undefined' && typeof WpForoAI.showNotice === 'function') {
329 WpForoAI.showNotice('✓ Credits have been successfully added to your account!', 'success');
330 }
331 <?php endif; ?>
332 });
333 </script>
334