| 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;"> </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 |
|