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
wpforo / admin / pages / tabs / ai-features-tab-analytics.php

ai-features-tab-analytics.php in wpForo Forum 3.1.6, at admin/pages/tabs/ai-features-tab-analytics.php

3,184 lines 119.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 - Analytics Tab
4 *
5 * Provides comprehensive analytics dashboard for AI feature usage:
6 * - AI Usage Analytics (credits, API calls, response times)
7 * - Forum Activity Analytics (posts, topics, engagement)
8 * - User Engagement Analytics (registrations, retention)
9 * - Content Performance Analytics (views, replies)
10 * - AI-Powered Insights (sentiment, trending topics)
11 *
12 * @package wpForo
13 * @subpackage Admin
14 */
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Render Analytics tab content
22 *
23 * @param bool $is_connected Whether tenant is connected to AI service
24 * @param array $status Tenant status data from API
25 */
26 function wpforo_ai_render_analytics_tab( $is_connected, $status ) {
27 if ( ! $is_connected ) {
28 ?>
29 <div class="wpforo-ai-box wpforo-ai-not-connected-notice">
30 <div class="wpforo-ai-box-body">
31 <div class="wpforo-ai-status-badge status-warning">
32 <span class="dashicons dashicons-warning"></span>
33 <?php _e( 'Not Connected', 'wpforo' ); ?>
34 </div>
35 <p><?php _e( 'Please connect to wpForo AI API first in the Overview tab to enable Analytics.', 'wpforo' ); ?></p>
36 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforo-ai&tab=overview' ) ); ?>" class="button button-primary">
37 <?php _e( 'Go to Overview', 'wpforo' ); ?>
38 </a>
39 </div>
40 </div>
41 <?php
42 return;
43 }
44
45 // Check if user's plan allows AI Analytics (requires Professional or higher)
46 $subscription = isset( $status['subscription'] ) && is_array( $status['subscription'] ) ? $status['subscription'] : [];
47 $current_plan = isset( $subscription['plan'] ) ? strtolower( $subscription['plan'] ) : 'free_trial';
48
49 // AI Analytics require Professional plan or higher
50 if ( ! wpforo_ai_is_feature_enabled_by_plan( $current_plan, 'professional' ) ) {
51 ?>
52 <div class="wpforo-ai-box wpforo-ai-upgrade-notice">
53 <div class="wpforo-ai-box-body">
54 <div class="wpforo-ai-status-badge status-info">
55 <span class="dashicons dashicons-lock"></span>
56 <?php _e( 'Upgrade to Professional Plan to Unlock', 'wpforo' ); ?>&nbsp;
57 </div>
58 <h3><?php _e( 'AI Analytics & Insights', 'wpforo' ); ?></h3>
59 <p><?php _e( 'AI Analytics & Insights are available on Professional, Business, and Enterprise plans.', 'wpforo' ); ?></p>
60 <ul class="wpforo-ai-feature-list">
61 <li><span class="dashicons dashicons-yes-alt"></span> <?php _e( 'Track AI feature usage and credit consumption', 'wpforo' ); ?></li>
62 <li><span class="dashicons dashicons-yes-alt"></span> <?php _e( 'Monitor forum activity and engagement metrics', 'wpforo' ); ?></li>
63 <li><span class="dashicons dashicons-yes-alt"></span> <?php _e( 'Analyze content performance and trends', 'wpforo' ); ?></li>
64 <li><span class="dashicons dashicons-yes-alt"></span> <?php _e( 'Get AI-powered insights and recommendations', 'wpforo' ); ?></li>
65 </ul>
66 <p>
67 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforo-ai&tab=overview' ) ); ?>" class="button button-primary">
68 <?php _e( 'Upgrade to Professional', 'wpforo' ); ?>
69 </a>
70 </p>
71 </div>
72 </div>
73 <?php
74 return;
75 }
76
77 // Get all boards and determine current board
78 $boards = WPF()->board->get_boards();
79 $current_boardid = isset( $_GET['boardid'] ) ? intval( $_GET['boardid'] ) : 0;
80
81 // Validate boardid exists
82 $valid_boardids = array_column( $boards, 'boardid' );
83 if ( ! in_array( $current_boardid, $valid_boardids ) ) {
84 $current_boardid = 0; // Default to main board
85 }
86
87 // Switch to the selected board context
88 if ( $current_boardid > 0 ) {
89 WPF()->change_board( $current_boardid );
90 }
91
92 // Render board sub-tabs if multiple boards exist
93 if ( count( $boards ) > 1 ) :
94 // Find current board title (from settings, not label)
95 $current_board_title = __( 'Board', 'wpforo' );
96 foreach ( $boards as $board ) {
97 if ( isset( $board['boardid'] ) && (int) $board['boardid'] === $current_boardid ) {
98 $current_board_title = isset( $board['settings']['title'] ) ? esc_html( $board['settings']['title'] ) : ( isset( $board['title'] ) ? esc_html( $board['title'] ) : $current_board_title );
99 break;
100 }
101 }
102 ?>
103 <div class="wpforo-ai-board-tabs">
104 <div class="wpforo-ai-board-current">
105 <?php echo esc_html__( 'Board:', 'wpforo' ); ?> <strong><?php echo $current_board_title; ?></strong>
106 </div>
107 <div class="wpforo-ai-board-tabs-list">
108 <?php foreach ( $boards as $board ) :
109 $board_id = isset( $board['boardid'] ) ? (int) $board['boardid'] : 0;
110 $board_label = isset( $board['title'] ) ? esc_html( $board['title'] ) : __( 'Board', 'wpforo' ) . ' ' . $board_id;
111 $tab_url = add_query_arg( array(
112 'page' => 'wpforo-ai',
113 'tab' => 'analytics',
114 'boardid' => $board_id,
115 ), admin_url( 'admin.php' ) );
116 $active_class = ( $current_boardid === $board_id ) ? 'active' : '';
117 ?>
118 <a href="<?php echo esc_url( $tab_url ); ?>" class="wpforo-ai-board-tab <?php echo esc_attr( $active_class ); ?>">
119 <span class="dashicons dashicons-category"></span>
120 <?php echo $board_label; ?>
121 </a>
122 <?php endforeach; ?>
123 </div>
124 </div>
125 <?php
126 endif;
127
128 // Get available credits from status
129 $subscription = isset( $status['subscription'] ) && is_array( $status['subscription'] ) ? $status['subscription'] : [];
130 $credits_remaining = isset( $subscription['credits_remaining'] ) ? (int) $subscription['credits_remaining'] : 0;
131
132 // Get current sub-tab
133 $current_subtab = isset( $_GET['subtab'] ) ? sanitize_key( $_GET['subtab'] ) : 'ai_usage';
134 $subtabs = array(
135 'ai_usage' => __( 'AI Usage Analytics', 'wpforo' ),
136 'forum_activity' => __( 'Forum Activity', 'wpforo' ),
137 'user_engagement' => __( 'User Engagement', 'wpforo' ),
138 'content' => __( 'Content Performance', 'wpforo' ),
139 'ai_insights' => __( 'AI Insights', 'wpforo' ),
140 );
141
142 ?>
143 <div class="wpforo-ai-analytics wpforo-ai-analytics-tab">
144
145 <!-- Sub-tabs Navigation -->
146 <div class="wpforo-ai-box wpforo-ai-analytics-subtabs-box">
147 <div class="wpforo-ai-box-body">
148 <nav class="wpforo-ai-analytics-subtabs">
149 <?php foreach ( $subtabs as $subtab_key => $subtab_label ) :
150 $subtab_url = add_query_arg( array(
151 'page' => 'wpforo-ai',
152 'tab' => 'analytics',
153 'subtab' => $subtab_key,
154 'boardid' => $current_boardid,
155 ), admin_url( 'admin.php' ) );
156 $active_class = ( $current_subtab === $subtab_key ) ? 'active' : '';
157 $is_disabled = false; // All sub-tabs are now enabled
158 $disabled_class = '';
159 ?>
160 <a href="<?php echo $is_disabled ? '#' : esc_url( $subtab_url ); ?>"
161 class="wpforo-ai-analytics-subtab <?php echo esc_attr( $active_class ); ?> <?php echo esc_attr( $disabled_class ); ?>"
162 <?php echo $is_disabled ? 'onclick="return false;" title="' . esc_attr__( 'Coming soon', 'wpforo' ) . '"' : ''; ?>>
163 <?php echo esc_html( $subtab_label ); ?>
164 <?php if ( $is_disabled ) : ?>
165 <span class="wpforo-ai-coming-soon"><?php _e( 'Soon', 'wpforo' ); ?></span>
166 <?php endif; ?>
167 </a>
168 <?php endforeach; ?>
169 </nav>
170 </div>
171 </div>
172
173 <?php
174 // Render sub-tab content
175 switch ( $current_subtab ) {
176 case 'ai_usage':
177 wpforo_ai_render_usage_analytics_subtab( $current_boardid, $status );
178 break;
179 case 'forum_activity':
180 wpforo_ai_render_forum_activity_subtab( $current_boardid );
181 break;
182 case 'user_engagement':
183 wpforo_ai_render_user_engagement_subtab( $current_boardid );
184 break;
185 case 'content':
186 wpforo_ai_render_content_performance_subtab( $current_boardid );
187 break;
188 case 'ai_insights':
189 wpforo_ai_render_ai_insights_subtab( $current_boardid, $status );
190 break;
191 default:
192 wpforo_ai_render_usage_analytics_subtab( $current_boardid, $status );
193 break;
194 }
195 ?>
196
197 </div>
198 <?php
199 }
200
201 /**
202 * Render AI Usage Analytics sub-tab
203 *
204 * @param int $board_id Current board ID
205 * @param array $status Tenant status data
206 */
207 function wpforo_ai_render_usage_analytics_subtab( $board_id, $status ) {
208 // Time range presets
209 $time_ranges = array(
210 'today' => __( 'Today', 'wpforo' ),
211 '7days' => __( 'Last 7 Days', 'wpforo' ),
212 '30days' => __( 'Last 30 Days', 'wpforo' ),
213 '90days' => __( 'Last 90 Days', 'wpforo' ),
214 'this_year' => __( 'This Year', 'wpforo' ),
215 'all_time' => __( 'All Time', 'wpforo' ),
216 'custom' => __( 'Custom Range', 'wpforo' ),
217 );
218
219 $current_range = isset( $_GET['range'] ) ? sanitize_key( $_GET['range'] ) : '7days';
220 $custom_start = isset( $_GET['start_date'] ) ? sanitize_text_field( $_GET['start_date'] ) : '';
221 $custom_end = isset( $_GET['end_date'] ) ? sanitize_text_field( $_GET['end_date'] ) : '';
222
223 // Calculate time range
224 $time_info = wpforo_ai_calculate_time_range( $current_range, $custom_start, $custom_end );
225 ?>
226
227 <!-- Time Range Selector -->
228 <div class="wpforo-ai-box wpforo-ai-time-range-box">
229 <div class="wpforo-ai-box-body">
230 <div class="wpforo-ai-time-range-selector">
231 <div class="wpforo-ai-time-range-label">
232 <span class="dashicons dashicons-calendar-alt"></span>
233 <?php _e( 'Time Range:', 'wpforo' ); ?>
234 </div>
235 <div class="wpforo-ai-time-range-buttons">
236 <?php foreach ( $time_ranges as $range_key => $range_label ) :
237 if ( $range_key === 'custom' ) continue; // Handle custom separately
238 $range_url = add_query_arg( array(
239 'page' => 'wpforo-ai',
240 'tab' => 'analytics',
241 'subtab' => 'ai_usage',
242 'boardid' => $board_id,
243 'range' => $range_key,
244 ), admin_url( 'admin.php' ) );
245 $active_class = ( $current_range === $range_key ) ? 'active' : '';
246 ?>
247 <a href="<?php echo esc_url( $range_url ); ?>"
248 class="wpforo-ai-time-range-btn <?php echo esc_attr( $active_class ); ?>"
249 data-range="<?php echo esc_attr( $range_key ); ?>">
250 <?php echo esc_html( $range_label ); ?>
251 </a>
252 <?php endforeach; ?>
253 <button type="button" class="wpforo-ai-time-range-btn wpforo-ai-custom-range-toggle <?php echo $current_range === 'custom' ? 'active' : ''; ?>">
254 <?php _e( 'Custom', 'wpforo' ); ?>
255 </button>
256 </div>
257 <div class="wpforo-ai-custom-range-picker" style="<?php echo $current_range === 'custom' ? '' : 'display: none;'; ?>">
258 <form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>">
259 <input type="hidden" name="page" value="wpforo-ai">
260 <input type="hidden" name="tab" value="analytics">
261 <input type="hidden" name="subtab" value="ai_usage">
262 <input type="hidden" name="boardid" value="<?php echo esc_attr( $board_id ); ?>">
263 <input type="hidden" name="range" value="custom">
264 <label>
265 <?php _e( 'From:', 'wpforo' ); ?>
266 <input type="date" name="start_date" value="<?php echo esc_attr( $custom_start ); ?>" required>
267 </label>
268 <label>
269 <?php _e( 'To:', 'wpforo' ); ?>
270 <input type="date" name="end_date" value="<?php echo esc_attr( $custom_end ); ?>" required>
271 </label>
272 <button type="submit" class="button button-primary">
273 <?php _e( 'Apply', 'wpforo' ); ?>
274 </button>
275 </form>
276 </div>
277 <div class="wpforo-ai-time-range-info">
278 <span class="wpforo-ai-date-range-display">
279 <?php echo esc_html( $time_info['display'] ); ?>
280 </span>
281 </div>
282 </div>
283 </div>
284 </div>
285
286 <!-- Main Loading Overlay -->
287 <div class="wpforo-ai-analytics-main-loading" id="wpforo-ai-analytics-main-loading">
288 <span class="wpforo-ai-loading-spinner"></span>
289 <span><?php _e( 'Loading analytics data...', 'wpforo' ); ?></span>
290 </div>
291
292 <!-- Analytics Content (hidden until loaded) -->
293 <div class="wpforo-ai-analytics-content" id="wpforo-ai-analytics-content">
294
295 <!-- Summary Cards -->
296 <div class="wpforo-ai-analytics-summary-cards" id="wpforo-ai-analytics-summary">
297 <div class="wpforo-ai-analytics-card">
298 <div class="wpforo-ai-analytics-card-icon">
299 <span class="dashicons dashicons-database"></span>
300 </div>
301 <div class="wpforo-ai-analytics-card-content">
302 <div class="wpforo-ai-analytics-card-value" id="total-credits-used">
303 <span class="wpforo-ai-loading-spinner"></span>
304 </div>
305 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Total Credits Used', 'wpforo' ); ?></div>
306 </div>
307 </div>
308 <div class="wpforo-ai-analytics-card">
309 <div class="wpforo-ai-analytics-card-icon">
310 <span class="dashicons dashicons-chart-line"></span>
311 </div>
312 <div class="wpforo-ai-analytics-card-content">
313 <div class="wpforo-ai-analytics-card-value" id="avg-credits-day">
314 <span class="wpforo-ai-loading-spinner"></span>
315 </div>
316 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Average per Day', 'wpforo' ); ?></div>
317 </div>
318 </div>
319 <div class="wpforo-ai-analytics-card">
320 <div class="wpforo-ai-analytics-card-icon">
321 <span class="dashicons dashicons-update"></span>
322 </div>
323 <div class="wpforo-ai-analytics-card-content">
324 <div class="wpforo-ai-analytics-card-value" id="total-api-calls">
325 <span class="wpforo-ai-loading-spinner"></span>
326 </div>
327 <div class="wpforo-ai-analytics-card-label"><?php _e( 'API Calls', 'wpforo' ); ?></div>
328 </div>
329 </div>
330 <div class="wpforo-ai-analytics-card">
331 <div class="wpforo-ai-analytics-card-icon">
332 <span class="dashicons dashicons-yes-alt"></span>
333 </div>
334 <div class="wpforo-ai-analytics-card-content">
335 <div class="wpforo-ai-analytics-card-value" id="success-rate">
336 <span class="wpforo-ai-loading-spinner"></span>
337 </div>
338 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Success Rate', 'wpforo' ); ?></div>
339 </div>
340 </div>
341 </div>
342
343 <!-- Charts Row -->
344 <div class="wpforo-ai-analytics-charts-row">
345 <!-- Credits Usage Over Time -->
346 <div class="wpforo-ai-box wpforo-ai-chart-box wpforo-ai-chart-wide">
347 <div class="wpforo-ai-box-header">
348 <h3>
349 <span class="dashicons dashicons-chart-area"></span>
350 <?php _e( 'Credits Usage Over Time', 'wpforo' ); ?>
351 </h3>
352 </div>
353 <div class="wpforo-ai-box-body">
354 <div class="wpforo-ai-chart-container">
355 <canvas id="credits-usage-chart"></canvas>
356 </div>
357 <div class="wpforo-ai-chart-loading" id="credits-usage-loading">
358 <span class="wpforo-ai-loading-spinner"></span>
359 <span><?php _e( 'Loading chart data...', 'wpforo' ); ?></span>
360 </div>
361 </div>
362 </div>
363 </div>
364
365 <div class="wpforo-ai-analytics-charts-row">
366 <!-- Credits by Feature -->
367 <div class="wpforo-ai-box wpforo-ai-chart-box">
368 <div class="wpforo-ai-box-header">
369 <h3>
370 <span class="dashicons dashicons-chart-pie"></span>
371 <?php _e( 'Credits by Feature', 'wpforo' ); ?>
372 </h3>
373 </div>
374 <div class="wpforo-ai-box-body">
375 <div class="wpforo-ai-chart-container wpforo-ai-chart-pie-container">
376 <canvas id="credits-by-feature-chart"></canvas>
377 </div>
378 <div class="wpforo-ai-chart-loading" id="credits-feature-loading">
379 <span class="wpforo-ai-loading-spinner"></span>
380 <span><?php _e( 'Loading...', 'wpforo' ); ?></span>
381 </div>
382 </div>
383 </div>
384
385 <!-- Moderation Statistics -->
386 <div class="wpforo-ai-box wpforo-ai-chart-box">
387 <div class="wpforo-ai-box-header">
388 <h3>
389 <span class="dashicons dashicons-shield"></span>
390 <?php _e( 'Moderation Statistics', 'wpforo' ); ?>
391 </h3>
392 </div>
393 <div class="wpforo-ai-box-body">
394 <div class="wpforo-ai-chart-container">
395 <canvas id="moderation-stats-chart"></canvas>
396 </div>
397 <div class="wpforo-ai-chart-loading" id="moderation-loading">
398 <span class="wpforo-ai-loading-spinner"></span>
399 <span><?php _e( 'Loading...', 'wpforo' ); ?></span>
400 </div>
401 </div>
402 </div>
403 </div>
404
405 <!-- Feature Usage Table -->
406 <div class="wpforo-ai-box wpforo-ai-usage-table-box">
407 <div class="wpforo-ai-box-header">
408 <h3>
409 <span class="dashicons dashicons-list-view"></span>
410 <?php _e( 'Feature Usage Details', 'wpforo' ); ?>
411 </h3>
412 </div>
413 <div class="wpforo-ai-box-body">
414 <table class="wpforo-ai-usage-table widefat striped" id="feature-usage-table">
415 <thead>
416 <tr>
417 <th><?php _e( 'Feature', 'wpforo' ); ?></th>
418 <th><?php _e( 'Requests', 'wpforo' ); ?></th>
419 <th><?php _e( 'Credits', 'wpforo' ); ?></th>
420 <th><?php _e( 'Avg Response', 'wpforo' ); ?></th>
421 <th><?php _e( 'Success Rate', 'wpforo' ); ?></th>
422 </tr>
423 </thead>
424 <tbody id="feature-usage-tbody">
425 <tr class="wpforo-ai-loading-row">
426 <td colspan="5">
427 <span class="wpforo-ai-loading-spinner"></span>
428 <?php _e( 'Loading usage data...', 'wpforo' ); ?>
429 </td>
430 </tr>
431 </tbody>
432 </table>
433 </div>
434 </div>
435
436 </div><!-- /.wpforo-ai-analytics-content -->
437
438 <?php
439 // Output JavaScript data
440 $analytics_data = array(
441 'boardId' => $board_id,
442 'startTime' => $time_info['start'],
443 'endTime' => $time_info['end'],
444 'range' => $current_range,
445 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
446 'nonce' => wp_create_nonce( 'wpforo_ai_analytics_nonce' ),
447 'i18n' => array(
448 'loading' => __( 'Loading...', 'wpforo' ),
449 'noData' => __( 'No data available for this period', 'wpforo' ),
450 'error' => __( 'Error loading data', 'wpforo' ),
451 'credits' => __( 'Credits', 'wpforo' ),
452 'requests' => __( 'Requests', 'wpforo' ),
453 'spamBlocked' => __( 'Spam Blocked', 'wpforo' ),
454 'toxicDetected' => __( 'Toxic Detected', 'wpforo' ),
455 'policyViolations' => __( 'Policy Violations', 'wpforo' ),
456 'cleanPassed' => __( 'Clean Passed', 'wpforo' ),
457 ),
458 'featureNames' => wpforo_ai_get_feature_display_names(),
459 'featureColors' => wpforo_ai_get_feature_colors(),
460 );
461 ?>
462 <script type="text/javascript">
463 var wpforoAIAnalytics = <?php echo wp_json_encode( $analytics_data ); ?>;
464 </script>
465 <?php
466 }
467
468 /**
469 * Calculate time range based on preset or custom dates
470 *
471 * @param string $range Time range preset
472 * @param string $custom_start Custom start date (Y-m-d)
473 * @param string $custom_end Custom end date (Y-m-d)
474 * @return array Time range info with start, end timestamps and display string
475 */
476 function wpforo_ai_calculate_time_range( $range, $custom_start = '', $custom_end = '' ) {
477 $now = current_time( 'timestamp' );
478 $today_start = strtotime( 'today midnight', $now );
479 $today_end = strtotime( 'tomorrow midnight', $now ) - 1;
480
481 switch ( $range ) {
482 case 'today':
483 $start = $today_start;
484 $end = $today_end;
485 $display = date_i18n( get_option( 'date_format' ), $start );
486 break;
487
488 case '7days':
489 $start = strtotime( '-6 days midnight', $now );
490 $end = $today_end;
491 $display = sprintf(
492 '%s - %s',
493 date_i18n( get_option( 'date_format' ), $start ),
494 date_i18n( get_option( 'date_format' ), $end )
495 );
496 break;
497
498 case '30days':
499 $start = strtotime( '-29 days midnight', $now );
500 $end = $today_end;
501 $display = sprintf(
502 '%s - %s',
503 date_i18n( get_option( 'date_format' ), $start ),
504 date_i18n( get_option( 'date_format' ), $end )
505 );
506 break;
507
508 case '90days':
509 $start = strtotime( '-89 days midnight', $now );
510 $end = $today_end;
511 $display = sprintf(
512 '%s - %s',
513 date_i18n( get_option( 'date_format' ), $start ),
514 date_i18n( get_option( 'date_format' ), $end )
515 );
516 break;
517
518 case 'this_year':
519 $start = strtotime( 'first day of January ' . date( 'Y', $now ) . ' midnight' );
520 $end = $today_end;
521 $display = sprintf( __( 'Year %s', 'wpforo' ), date( 'Y', $now ) );
522 break;
523
524 case 'all_time':
525 // Use a date far in the past
526 $start = strtotime( '2020-01-01 00:00:00' );
527 $end = $today_end;
528 $display = __( 'All Time', 'wpforo' );
529 break;
530
531 case 'custom':
532 if ( ! empty( $custom_start ) && ! empty( $custom_end ) ) {
533 $start = strtotime( $custom_start . ' 00:00:00' );
534 $end = strtotime( $custom_end . ' 23:59:59' );
535 $display = sprintf(
536 '%s - %s',
537 date_i18n( get_option( 'date_format' ), $start ),
538 date_i18n( get_option( 'date_format' ), $end )
539 );
540 } else {
541 // Fallback to 7 days if custom dates not provided
542 $start = strtotime( '-6 days midnight', $now );
543 $end = $today_end;
544 $display = sprintf(
545 '%s - %s',
546 date_i18n( get_option( 'date_format' ), $start ),
547 date_i18n( get_option( 'date_format' ), $end )
548 );
549 }
550 break;
551
552 default:
553 // Default to 7 days
554 $start = strtotime( '-6 days midnight', $now );
555 $end = $today_end;
556 $display = sprintf(
557 '%s - %s',
558 date_i18n( get_option( 'date_format' ), $start ),
559 date_i18n( get_option( 'date_format' ), $end )
560 );
561 }
562
563 return array(
564 'start' => $start,
565 'end' => $end,
566 'display' => $display,
567 );
568 }
569
570 /**
571 * Get feature display names for analytics
572 *
573 * @return array Feature key => Display name mapping
574 */
575 function wpforo_ai_get_feature_display_names() {
576 return array(
577 'semantic_search' => __( 'AI Search', 'wpforo' ),
578 'search_enhance' => __( 'Search Enhancement', 'wpforo' ),
579 'multi_language_translation' => __( 'Translation', 'wpforo' ),
580 'topic_summary' => __( 'Topic Summary', 'wpforo' ),
581 'content_indexing' => __( 'Content Indexing', 'wpforo' ),
582 'multimodal_image_indexing' => __( 'Image Indexing', 'wpforo' ),
583 'document_indexing' => __( 'Document Indexing', 'wpforo' ),
584 'custom_knowledge_indexing' => __( 'Custom Knowledge Indexing', 'wpforo' ),
585 'ai_topic_generator' => __( 'Topic Generator', 'wpforo' ),
586 'ai_reply_generator' => __( 'Reply Generator', 'wpforo' ),
587 'auto_tag_generation' => __( 'Tag Maintenance', 'wpforo' ),
588 'ai_suggestion' => __( 'AI Suggestions', 'wpforo' ),
589 'ai_spam_detection' => __( 'Spam Detection', 'wpforo' ),
590 'ai_toxicity_detection' => __( 'Toxicity Detection', 'wpforo' ),
591 'ai_rule_compliance' => __( 'Rule Compliance', 'wpforo' ),
592 'unified_moderation' => __( 'Unified Moderation', 'wpforo' ),
593 'ai_chat' => __( 'AI Chatbot', 'wpforo' ),
594 'ai_insight_sentiment' => __( 'Insight: Sentiment', 'wpforo' ),
595 'ai_insight_trending' => __( 'Insight: Trending', 'wpforo' ),
596 'ai_insight_recommendations' => __( 'Insight: Recommendations', 'wpforo' ),
597 'ai_insight_deep_analysis' => __( 'Insight: Deep Analysis', 'wpforo' ),
598 'ai_insight_sentiment_trend' => __( 'Insight: Sentiment Trend', 'wpforo' ),
599 );
600 }
601
602 /**
603 * Get feature colors for charts
604 *
605 * @return array Feature key => Color code mapping
606 */
607 function wpforo_ai_get_feature_colors() {
608 return array(
609 'semantic_search' => '#4CAF50',
610 'search_enhance' => '#8BC34A',
611 'multi_language_translation' => '#2196F3',
612 'topic_summary' => '#03A9F4',
613 'content_indexing' => '#9C27B0',
614 'multimodal_image_indexing' => '#CE93D8',
615 'document_indexing' => '#AB47BC',
616 'custom_knowledge_indexing' => '#7B1FA2',
617 'ai_topic_generator' => '#FF9800',
618 'ai_reply_generator' => '#FF5722',
619 'auto_tag_generation' => '#795548',
620 'ai_suggestion' => '#00BCD4',
621 'ai_spam_detection' => '#F44336',
622 'ai_toxicity_detection' => '#E91E63',
623 'ai_rule_compliance' => '#673AB7',
624 'unified_moderation' => '#3F51B5',
625 'ai_chat' => '#009688',
626 'ai_insight_sentiment' => '#7C4DFF',
627 'ai_insight_trending' => '#536DFE',
628 'ai_insight_recommendations' => '#448AFF',
629 'ai_insight_deep_analysis' => '#40C4FF',
630 'ai_insight_sentiment_trend' => '#18FFFF',
631 );
632 }
633
634 /**
635 * Render Forum Activity Analytics sub-tab
636 *
637 * This is a FREE feature - all data comes from local wpForo database.
638 * No API calls required.
639 *
640 * @param int $board_id Current board ID
641 */
642 function wpforo_ai_render_forum_activity_subtab( $board_id ) {
643 // Time range presets
644 $time_ranges = array(
645 'today' => __( 'Today', 'wpforo' ),
646 '7days' => __( 'Last 7 Days', 'wpforo' ),
647 '30days' => __( 'Last 30 Days', 'wpforo' ),
648 '90days' => __( 'Last 90 Days', 'wpforo' ),
649 'this_year' => __( 'This Year', 'wpforo' ),
650 'all_time' => __( 'All Time', 'wpforo' ),
651 'custom' => __( 'Custom Range', 'wpforo' ),
652 );
653
654 $current_range = isset( $_GET['range'] ) ? sanitize_key( $_GET['range'] ) : '7days';
655 $custom_start = isset( $_GET['start_date'] ) ? sanitize_text_field( $_GET['start_date'] ) : '';
656 $custom_end = isset( $_GET['end_date'] ) ? sanitize_text_field( $_GET['end_date'] ) : '';
657
658 // Calculate time range
659 $time_info = wpforo_ai_calculate_time_range( $current_range, $custom_start, $custom_end );
660
661 // Get forum activity data
662 $activity_data = wpforo_ai_get_forum_activity_data( $board_id, $time_info['start'], $time_info['end'] );
663 ?>
664
665 <!-- Time Range Selector -->
666 <div class="wpforo-ai-box wpforo-ai-time-range-box">
667 <div class="wpforo-ai-box-body">
668 <div class="wpforo-ai-time-range-selector">
669 <div class="wpforo-ai-time-range-label">
670 <span class="dashicons dashicons-calendar-alt"></span>
671 <?php _e( 'Time Range:', 'wpforo' ); ?>
672 </div>
673 <div class="wpforo-ai-time-range-buttons">
674 <?php foreach ( $time_ranges as $range_key => $range_label ) :
675 if ( $range_key === 'custom' ) continue;
676 $range_url = add_query_arg( array(
677 'page' => 'wpforo-ai',
678 'tab' => 'analytics',
679 'subtab' => 'forum_activity',
680 'boardid' => $board_id,
681 'range' => $range_key,
682 ), admin_url( 'admin.php' ) );
683 $active_class = ( $current_range === $range_key ) ? 'active' : '';
684 ?>
685 <a href="<?php echo esc_url( $range_url ); ?>"
686 class="wpforo-ai-time-range-btn <?php echo esc_attr( $active_class ); ?>"
687 data-range="<?php echo esc_attr( $range_key ); ?>">
688 <?php echo esc_html( $range_label ); ?>
689 </a>
690 <?php endforeach; ?>
691 <button type="button" class="wpforo-ai-time-range-btn wpforo-ai-custom-range-toggle <?php echo $current_range === 'custom' ? 'active' : ''; ?>">
692 <?php _e( 'Custom', 'wpforo' ); ?>
693 </button>
694 </div>
695 <div class="wpforo-ai-custom-range-picker" style="<?php echo $current_range === 'custom' ? '' : 'display: none;'; ?>">
696 <form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>">
697 <input type="hidden" name="page" value="wpforo-ai">
698 <input type="hidden" name="tab" value="analytics">
699 <input type="hidden" name="subtab" value="forum_activity">
700 <input type="hidden" name="boardid" value="<?php echo esc_attr( $board_id ); ?>">
701 <input type="hidden" name="range" value="custom">
702 <label>
703 <?php _e( 'From:', 'wpforo' ); ?>
704 <input type="date" name="start_date" value="<?php echo esc_attr( $custom_start ); ?>" required>
705 </label>
706 <label>
707 <?php _e( 'To:', 'wpforo' ); ?>
708 <input type="date" name="end_date" value="<?php echo esc_attr( $custom_end ); ?>" required>
709 </label>
710 <button type="submit" class="button button-primary">
711 <?php _e( 'Apply', 'wpforo' ); ?>
712 </button>
713 </form>
714 </div>
715 <div class="wpforo-ai-time-range-info">
716 <span class="wpforo-ai-date-range-display">
717 <?php echo esc_html( $time_info['display'] ); ?>
718 </span>
719 </div>
720 </div>
721 </div>
722 </div>
723
724 <!-- Summary Cards -->
725 <div class="wpforo-ai-analytics-summary-cards">
726 <div class="wpforo-ai-analytics-card">
727 <div class="wpforo-ai-analytics-card-icon">
728 <span class="dashicons dashicons-format-chat"></span>
729 </div>
730 <div class="wpforo-ai-analytics-card-content">
731 <div class="wpforo-ai-analytics-card-value">
732 <?php echo number_format( $activity_data['summary']['total_topics'] ); ?>
733 </div>
734 <div class="wpforo-ai-analytics-card-label"><?php _e( 'New Topics', 'wpforo' ); ?></div>
735 </div>
736 </div>
737 <div class="wpforo-ai-analytics-card">
738 <div class="wpforo-ai-analytics-card-icon">
739 <span class="dashicons dashicons-admin-comments"></span>
740 </div>
741 <div class="wpforo-ai-analytics-card-content">
742 <div class="wpforo-ai-analytics-card-value">
743 <?php echo number_format( $activity_data['summary']['total_replies'] ); ?>
744 </div>
745 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Replies', 'wpforo' ); ?></div>
746 </div>
747 </div>
748 <div class="wpforo-ai-analytics-card">
749 <div class="wpforo-ai-analytics-card-icon">
750 <span class="dashicons dashicons-groups"></span>
751 </div>
752 <div class="wpforo-ai-analytics-card-content">
753 <div class="wpforo-ai-analytics-card-value">
754 <?php echo number_format( $activity_data['summary']['active_users'] ); ?>
755 </div>
756 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Active Users', 'wpforo' ); ?></div>
757 </div>
758 </div>
759 <div class="wpforo-ai-analytics-card">
760 <div class="wpforo-ai-analytics-card-icon">
761 <span class="dashicons dashicons-chart-bar"></span>
762 </div>
763 <div class="wpforo-ai-analytics-card-content">
764 <div class="wpforo-ai-analytics-card-value">
765 <?php echo number_format( $activity_data['summary']['avg_replies_per_topic'], 1 ); ?>
766 </div>
767 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Avg Replies/Topic', 'wpforo' ); ?></div>
768 </div>
769 </div>
770 </div>
771
772 <!-- Charts Row 1: Posts Over Time -->
773 <div class="wpforo-ai-analytics-charts-row">
774 <div class="wpforo-ai-box wpforo-ai-chart-box wpforo-ai-chart-wide">
775 <div class="wpforo-ai-box-header">
776 <h3>
777 <span class="dashicons dashicons-chart-area"></span>
778 <?php _e( 'Posts Over Time', 'wpforo' ); ?>
779 </h3>
780 </div>
781 <div class="wpforo-ai-box-body">
782 <div class="wpforo-ai-chart-container">
783 <canvas id="forum-posts-chart"></canvas>
784 </div>
785 </div>
786 </div>
787 </div>
788
789 <!-- Charts Row 2: Activity Heatmap and Top Forums -->
790 <div class="wpforo-ai-analytics-charts-row">
791 <!-- Activity Heatmap -->
792 <div class="wpforo-ai-box wpforo-ai-chart-box">
793 <div class="wpforo-ai-box-header">
794 <h3>
795 <span class="dashicons dashicons-calendar"></span>
796 <?php _e( 'Activity Heatmap', 'wpforo' ); ?>
797 </h3>
798 </div>
799 <div class="wpforo-ai-box-body">
800 <div class="wpforo-ai-heatmap-container" id="activity-heatmap">
801 <?php wpforo_ai_render_activity_heatmap( $activity_data['heatmap'] ); ?>
802 </div>
803 </div>
804 </div>
805
806 <!-- Top Forums by Activity -->
807 <div class="wpforo-ai-box wpforo-ai-chart-box">
808 <div class="wpforo-ai-box-header">
809 <h3>
810 <span class="dashicons dashicons-category"></span>
811 <?php _e( 'Top Forums by Activity', 'wpforo' ); ?>
812 </h3>
813 </div>
814 <div class="wpforo-ai-box-body">
815 <div class="wpforo-ai-chart-container">
816 <canvas id="top-forums-chart"></canvas>
817 </div>
818 </div>
819 </div>
820 </div>
821
822 <!-- What's New Table -->
823 <div class="wpforo-ai-box wpforo-ai-usage-table-box">
824 <div class="wpforo-ai-box-header">
825 <h3>
826 <span class="dashicons dashicons-list-view"></span>
827 <?php _e( 'Forum Activity Breakdown', 'wpforo' ); ?>
828 </h3>
829 </div>
830 <div class="wpforo-ai-box-body">
831 <table class="wpforo-ai-usage-table widefat striped">
832 <thead>
833 <tr>
834 <th><?php _e( 'Forum', 'wpforo' ); ?></th>
835 <th><?php _e( 'New Topics', 'wpforo' ); ?></th>
836 <th><?php _e( 'Replies', 'wpforo' ); ?></th>
837 <th><?php _e( 'Active Users', 'wpforo' ); ?></th>
838 <th><?php _e( 'Total Views', 'wpforo' ); ?></th>
839 </tr>
840 </thead>
841 <tbody>
842 <?php if ( ! empty( $activity_data['forums'] ) ) : ?>
843 <?php foreach ( $activity_data['forums'] as $forum ) : ?>
844 <tr>
845 <td>
846 <strong><?php echo esc_html( $forum['title'] ); ?></strong>
847 </td>
848 <td><?php echo number_format( $forum['topics'] ); ?></td>
849 <td><?php echo number_format( $forum['replies'] ); ?></td>
850 <td><?php echo number_format( $forum['active_users'] ); ?></td>
851 <td><?php echo number_format( $forum['views'] ); ?></td>
852 </tr>
853 <?php endforeach; ?>
854 <?php else : ?>
855 <tr>
856 <td colspan="5" class="wpforo-ai-no-data">
857 <?php _e( 'No forum activity in this period', 'wpforo' ); ?>
858 </td>
859 </tr>
860 <?php endif; ?>
861 </tbody>
862 </table>
863 </div>
864 </div>
865
866 <?php
867 // Output JavaScript data for charts
868 $chart_data = array(
869 'postsOverTime' => $activity_data['time_series'],
870 'topForums' => array_slice( $activity_data['forums'], 0, 10 ),
871 'i18n' => array(
872 'topics' => __( 'Topics', 'wpforo' ),
873 'replies' => __( 'Replies', 'wpforo' ),
874 'posts' => __( 'Posts', 'wpforo' ),
875 ),
876 );
877 ?>
878 <script type="text/javascript">
879 var wpforoForumActivity = <?php echo wp_json_encode( $chart_data ); ?>;
880 </script>
881 <?php
882 }
883
884 /**
885 * Get forum activity data from database
886 *
887 * @param int $board_id Board ID
888 * @param int $start_time Start timestamp
889 * @param int $end_time End timestamp
890 * @return array Activity data
891 */
892 function wpforo_ai_get_forum_activity_data( $board_id, $start_time, $end_time ) {
893 $start_date = date( 'Y-m-d H:i:s', $start_time );
894 $end_date = date( 'Y-m-d H:i:s', $end_time );
895
896 // Get summary stats
897 $summary = wpforo_ai_get_activity_summary( $start_date, $end_date );
898
899 // Get time series data (posts per day)
900 $time_series = wpforo_ai_get_posts_time_series( $start_date, $end_date );
901
902 // Get activity heatmap (hour vs day of week)
903 $heatmap = wpforo_ai_get_activity_heatmap( $start_date, $end_date );
904
905 // Get forum breakdown
906 $forums = wpforo_ai_get_forum_breakdown( $start_date, $end_date );
907
908 return array(
909 'summary' => $summary,
910 'time_series' => $time_series,
911 'heatmap' => $heatmap,
912 'forums' => $forums,
913 );
914 }
915
916 /**
917 * Get activity summary statistics
918 *
919 * @param string $start_date Start date (Y-m-d H:i:s)
920 * @param string $end_date End date (Y-m-d H:i:s)
921 * @return array Summary stats
922 */
923 function wpforo_ai_get_activity_summary( $start_date, $end_date ) {
924 // Total new topics
925 $total_topics = (int) WPF()->db->get_var( WPF()->db->prepare(
926 "SELECT COUNT(*) FROM " . WPF()->tables->topics . "
927 WHERE created BETWEEN %s AND %s
928 AND status = 0",
929 $start_date, $end_date
930 ) );
931
932 // Total replies (posts that are not first posts)
933 $total_replies = (int) WPF()->db->get_var( WPF()->db->prepare(
934 "SELECT COUNT(*) FROM " . WPF()->tables->posts . "
935 WHERE created BETWEEN %s AND %s
936 AND is_first_post = 0
937 AND status = 0",
938 $start_date, $end_date
939 ) );
940
941 // Active users (distinct users who posted)
942 $active_users = (int) WPF()->db->get_var( WPF()->db->prepare(
943 "SELECT COUNT(DISTINCT userid) FROM " . WPF()->tables->posts . "
944 WHERE created BETWEEN %s AND %s
945 AND userid > 0
946 AND status = 0",
947 $start_date, $end_date
948 ) );
949
950 // Average replies per topic
951 $avg_replies = $total_topics > 0 ? ( $total_replies / $total_topics ) : 0;
952
953 return array(
954 'total_topics' => $total_topics,
955 'total_replies' => $total_replies,
956 'active_users' => $active_users,
957 'avg_replies_per_topic' => $avg_replies,
958 );
959 }
960
961 /**
962 * Get posts time series (for area chart)
963 *
964 * @param string $start_date Start date
965 * @param string $end_date End date
966 * @return array Time series data
967 */
968 function wpforo_ai_get_posts_time_series( $start_date, $end_date ) {
969 $results = WPF()->db->get_results( WPF()->db->prepare(
970 "SELECT DATE(created) as date,
971 SUM(CASE WHEN is_first_post = 1 THEN 1 ELSE 0 END) as topics,
972 SUM(CASE WHEN is_first_post = 0 THEN 1 ELSE 0 END) as replies
973 FROM " . WPF()->tables->posts . "
974 WHERE created BETWEEN %s AND %s
975 AND status = 0
976 GROUP BY DATE(created)
977 ORDER BY date ASC",
978 $start_date, $end_date
979 ), ARRAY_A );
980
981 // Fill in missing dates with zeros
982 $time_series = array();
983 $current = strtotime( date( 'Y-m-d', strtotime( $start_date ) ) );
984 $end = strtotime( date( 'Y-m-d', strtotime( $end_date ) ) );
985
986 // Create a lookup for results
987 $data_lookup = array();
988 foreach ( $results as $row ) {
989 $data_lookup[ $row['date'] ] = $row;
990 }
991
992 // Fill in all dates
993 while ( $current <= $end ) {
994 $date_str = date( 'Y-m-d', $current );
995 if ( isset( $data_lookup[ $date_str ] ) ) {
996 $time_series[] = array(
997 'date' => $date_str,
998 'topics' => (int) $data_lookup[ $date_str ]['topics'],
999 'replies' => (int) $data_lookup[ $date_str ]['replies'],
1000 );
1001 } else {
1002 $time_series[] = array(
1003 'date' => $date_str,
1004 'topics' => 0,
1005 'replies' => 0,
1006 );
1007 }
1008 $current = strtotime( '+1 day', $current );
1009 }
1010
1011 return $time_series;
1012 }
1013
1014 /**
1015 * Get activity heatmap data (day of week vs hour)
1016 *
1017 * @param string $start_date Start date
1018 * @param string $end_date End date
1019 * @return array Heatmap data [day][hour] => count
1020 */
1021 function wpforo_ai_get_activity_heatmap( $start_date, $end_date ) {
1022 $results = WPF()->db->get_results( WPF()->db->prepare(
1023 "SELECT DAYOFWEEK(created) as day_of_week,
1024 HOUR(created) as hour_of_day,
1025 COUNT(*) as count
1026 FROM " . WPF()->tables->posts . "
1027 WHERE created BETWEEN %s AND %s
1028 AND status = 0
1029 GROUP BY DAYOFWEEK(created), HOUR(created)",
1030 $start_date, $end_date
1031 ), ARRAY_A );
1032
1033 // Initialize heatmap (7 days x 24 hours)
1034 $heatmap = array();
1035 for ( $day = 1; $day <= 7; $day++ ) {
1036 $heatmap[ $day ] = array_fill( 0, 24, 0 );
1037 }
1038
1039 // Fill in values
1040 $max_count = 0;
1041 foreach ( $results as $row ) {
1042 $day = (int) $row['day_of_week'];
1043 $hour = (int) $row['hour_of_day'];
1044 $count = (int) $row['count'];
1045 $heatmap[ $day ][ $hour ] = $count;
1046 $max_count = max( $max_count, $count );
1047 }
1048
1049 return array(
1050 'data' => $heatmap,
1051 'max_count' => $max_count,
1052 );
1053 }
1054
1055 /**
1056 * Get forum activity breakdown
1057 *
1058 * @param string $start_date Start date
1059 * @param string $end_date End date
1060 * @return array Forum stats
1061 */
1062 function wpforo_ai_get_forum_breakdown( $start_date, $end_date ) {
1063 $results = WPF()->db->get_results( WPF()->db->prepare(
1064 "SELECT f.forumid,
1065 f.title,
1066 COUNT(DISTINCT CASE WHEN p.is_first_post = 1 THEN p.topicid END) as topics,
1067 SUM(CASE WHEN p.is_first_post = 0 THEN 1 ELSE 0 END) as replies,
1068 COUNT(DISTINCT p.userid) as active_users,
1069 COALESCE(SUM(t.views), 0) as views
1070 FROM " . WPF()->tables->forums . " f
1071 LEFT JOIN " . WPF()->tables->posts . " p ON f.forumid = p.forumid
1072 AND p.created BETWEEN %s AND %s
1073 AND p.status = 0
1074 LEFT JOIN " . WPF()->tables->topics . " t ON p.topicid = t.topicid
1075 AND p.is_first_post = 1
1076 WHERE f.is_cat = 0
1077 GROUP BY f.forumid, f.title
1078 HAVING topics > 0 OR replies > 0
1079 ORDER BY (topics + replies) DESC",
1080 $start_date, $end_date
1081 ), ARRAY_A );
1082
1083 return $results ?: array();
1084 }
1085
1086 /**
1087 * Render activity heatmap HTML
1088 *
1089 * @param array $heatmap_data Heatmap data from wpforo_ai_get_activity_heatmap()
1090 */
1091 function wpforo_ai_render_activity_heatmap( $heatmap_data ) {
1092 $days = array(
1093 1 => __( 'Sun', 'wpforo' ),
1094 2 => __( 'Mon', 'wpforo' ),
1095 3 => __( 'Tue', 'wpforo' ),
1096 4 => __( 'Wed', 'wpforo' ),
1097 5 => __( 'Thu', 'wpforo' ),
1098 6 => __( 'Fri', 'wpforo' ),
1099 7 => __( 'Sat', 'wpforo' ),
1100 );
1101
1102 $data = $heatmap_data['data'];
1103 $max_count = max( 1, $heatmap_data['max_count'] ); // Avoid division by zero
1104 ?>
1105 <div class="wpforo-ai-heatmap">
1106 <div class="wpforo-ai-heatmap-header">
1107 <div class="wpforo-ai-heatmap-day-label"></div>
1108 <?php for ( $hour = 0; $hour < 24; $hour += 3 ) : ?>
1109 <div class="wpforo-ai-heatmap-hour-label"><?php echo sprintf( '%02d:00', $hour ); ?></div>
1110 <?php endfor; ?>
1111 </div>
1112 <?php foreach ( $days as $day_num => $day_name ) : ?>
1113 <div class="wpforo-ai-heatmap-row">
1114 <div class="wpforo-ai-heatmap-day-label"><?php echo esc_html( $day_name ); ?></div>
1115 <div class="wpforo-ai-heatmap-cells">
1116 <?php for ( $hour = 0; $hour < 24; $hour++ ) :
1117 $count = isset( $data[ $day_num ][ $hour ] ) ? $data[ $day_num ][ $hour ] : 0;
1118 $intensity = $max_count > 0 ? ( $count / $max_count ) : 0;
1119 $opacity = 0.1 + ( $intensity * 0.9 ); // Range from 0.1 to 1.0
1120 $bg_color = $count > 0 ? "rgba(76, 175, 80, {$opacity})" : 'rgba(0, 0, 0, 0.05)';
1121 ?>
1122 <div class="wpforo-ai-heatmap-cell"
1123 style="background-color: <?php echo esc_attr( $bg_color ); ?>"
1124 title="<?php echo esc_attr( sprintf( __( '%s %d:00 - %d posts', 'wpforo' ), $day_name, $hour, $count ) ); ?>">
1125 </div>
1126 <?php endfor; ?>
1127 </div>
1128 </div>
1129 <?php endforeach; ?>
1130 <div class="wpforo-ai-heatmap-legend">
1131 <span class="wpforo-ai-heatmap-legend-label"><?php _e( 'Less', 'wpforo' ); ?></span>
1132 <div class="wpforo-ai-heatmap-legend-scale">
1133 <?php for ( $i = 0; $i <= 4; $i++ ) :
1134 $opacity = 0.1 + ( $i * 0.225 );
1135 ?>
1136 <div class="wpforo-ai-heatmap-legend-cell" style="background-color: rgba(76, 175, 80, <?php echo $opacity; ?>)"></div>
1137 <?php endfor; ?>
1138 </div>
1139 <span class="wpforo-ai-heatmap-legend-label"><?php _e( 'More', 'wpforo' ); ?></span>
1140 </div>
1141 </div>
1142 <?php
1143 }
1144
1145 /**
1146 * Render User Engagement Analytics sub-tab
1147 *
1148 * FREE feature - all data comes from local wpForo/WordPress database.
1149 *
1150 * @param int $board_id Current board ID
1151 */
1152 function wpforo_ai_render_user_engagement_subtab( $board_id ) {
1153 // Time range presets
1154 $time_ranges = array(
1155 'today' => __( 'Today', 'wpforo' ),
1156 '7days' => __( 'Last 7 Days', 'wpforo' ),
1157 '30days' => __( 'Last 30 Days', 'wpforo' ),
1158 '90days' => __( 'Last 90 Days', 'wpforo' ),
1159 'this_year' => __( 'This Year', 'wpforo' ),
1160 'all_time' => __( 'All Time', 'wpforo' ),
1161 'custom' => __( 'Custom Range', 'wpforo' ),
1162 );
1163
1164 $current_range = isset( $_GET['range'] ) ? sanitize_key( $_GET['range'] ) : '30days';
1165 $custom_start = isset( $_GET['start_date'] ) ? sanitize_text_field( $_GET['start_date'] ) : '';
1166 $custom_end = isset( $_GET['end_date'] ) ? sanitize_text_field( $_GET['end_date'] ) : '';
1167
1168 // Calculate time range
1169 $time_info = wpforo_ai_calculate_time_range( $current_range, $custom_start, $custom_end );
1170
1171 // Get user engagement data
1172 $engagement_data = wpforo_ai_get_user_engagement_data( $board_id, $time_info['start'], $time_info['end'] );
1173 ?>
1174
1175 <!-- Time Range Selector -->
1176 <div class="wpforo-ai-box wpforo-ai-time-range-box">
1177 <div class="wpforo-ai-box-body">
1178 <div class="wpforo-ai-time-range-selector">
1179 <div class="wpforo-ai-time-range-label">
1180 <span class="dashicons dashicons-calendar-alt"></span>
1181 <?php _e( 'Time Range:', 'wpforo' ); ?>
1182 </div>
1183 <div class="wpforo-ai-time-range-buttons">
1184 <?php foreach ( $time_ranges as $range_key => $range_label ) :
1185 if ( $range_key === 'custom' ) continue;
1186 $range_url = add_query_arg( array(
1187 'page' => 'wpforo-ai',
1188 'tab' => 'analytics',
1189 'subtab' => 'user_engagement',
1190 'boardid' => $board_id,
1191 'range' => $range_key,
1192 ), admin_url( 'admin.php' ) );
1193 $active_class = ( $current_range === $range_key ) ? 'active' : '';
1194 ?>
1195 <a href="<?php echo esc_url( $range_url ); ?>"
1196 class="wpforo-ai-time-range-btn <?php echo esc_attr( $active_class ); ?>"
1197 data-range="<?php echo esc_attr( $range_key ); ?>">
1198 <?php echo esc_html( $range_label ); ?>
1199 </a>
1200 <?php endforeach; ?>
1201 <button type="button" class="wpforo-ai-time-range-btn wpforo-ai-custom-range-toggle <?php echo $current_range === 'custom' ? 'active' : ''; ?>">
1202 <?php _e( 'Custom', 'wpforo' ); ?>
1203 </button>
1204 </div>
1205 <div class="wpforo-ai-custom-range-picker" style="<?php echo $current_range === 'custom' ? '' : 'display: none;'; ?>">
1206 <form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>">
1207 <input type="hidden" name="page" value="wpforo-ai">
1208 <input type="hidden" name="tab" value="analytics">
1209 <input type="hidden" name="subtab" value="user_engagement">
1210 <input type="hidden" name="boardid" value="<?php echo esc_attr( $board_id ); ?>">
1211 <input type="hidden" name="range" value="custom">
1212 <label>
1213 <?php _e( 'From:', 'wpforo' ); ?>
1214 <input type="date" name="start_date" value="<?php echo esc_attr( $custom_start ); ?>" required>
1215 </label>
1216 <label>
1217 <?php _e( 'To:', 'wpforo' ); ?>
1218 <input type="date" name="end_date" value="<?php echo esc_attr( $custom_end ); ?>" required>
1219 </label>
1220 <button type="submit" class="button button-primary">
1221 <?php _e( 'Apply', 'wpforo' ); ?>
1222 </button>
1223 </form>
1224 </div>
1225 <div class="wpforo-ai-time-range-info">
1226 <span class="wpforo-ai-date-range-display">
1227 <?php echo esc_html( $time_info['display'] ); ?>
1228 </span>
1229 </div>
1230 </div>
1231 </div>
1232 </div>
1233
1234 <!-- Summary Cards -->
1235 <div class="wpforo-ai-analytics-summary-cards">
1236 <div class="wpforo-ai-analytics-card">
1237 <div class="wpforo-ai-analytics-card-icon">
1238 <span class="dashicons dashicons-admin-users"></span>
1239 </div>
1240 <div class="wpforo-ai-analytics-card-content">
1241 <div class="wpforo-ai-analytics-card-value">
1242 <?php echo number_format( $engagement_data['summary']['new_members'] ); ?>
1243 </div>
1244 <div class="wpforo-ai-analytics-card-label"><?php _e( 'New Members', 'wpforo' ); ?></div>
1245 </div>
1246 </div>
1247 <div class="wpforo-ai-analytics-card">
1248 <div class="wpforo-ai-analytics-card-icon">
1249 <span class="dashicons dashicons-groups"></span>
1250 </div>
1251 <div class="wpforo-ai-analytics-card-content">
1252 <div class="wpforo-ai-analytics-card-value">
1253 <?php echo number_format( $engagement_data['summary']['active_members'] ); ?>
1254 </div>
1255 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Active Members', 'wpforo' ); ?></div>
1256 </div>
1257 </div>
1258 <div class="wpforo-ai-analytics-card">
1259 <div class="wpforo-ai-analytics-card-icon">
1260 <span class="dashicons dashicons-chart-line"></span>
1261 </div>
1262 <div class="wpforo-ai-analytics-card-content">
1263 <div class="wpforo-ai-analytics-card-value">
1264 <?php echo number_format( $engagement_data['summary']['engagement_rate'], 1 ); ?>%
1265 </div>
1266 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Engagement Rate', 'wpforo' ); ?></div>
1267 </div>
1268 </div>
1269 <div class="wpforo-ai-analytics-card">
1270 <div class="wpforo-ai-analytics-card-icon">
1271 <span class="dashicons dashicons-star-filled"></span>
1272 </div>
1273 <div class="wpforo-ai-analytics-card-content">
1274 <div class="wpforo-ai-analytics-card-value wpforo-ai-card-value-small">
1275 <?php echo esc_html( $engagement_data['summary']['top_contributor_name'] ?: '-' ); ?>
1276 </div>
1277 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Top Contributor', 'wpforo' ); ?></div>
1278 </div>
1279 </div>
1280 </div>
1281
1282 <!-- Charts Row 1: Registrations Over Time -->
1283 <div class="wpforo-ai-analytics-charts-row">
1284 <div class="wpforo-ai-box wpforo-ai-chart-box wpforo-ai-chart-wide">
1285 <div class="wpforo-ai-box-header">
1286 <h3>
1287 <span class="dashicons dashicons-chart-line"></span>
1288 <?php _e( 'New Registrations Over Time', 'wpforo' ); ?>
1289 </h3>
1290 </div>
1291 <div class="wpforo-ai-box-body">
1292 <div class="wpforo-ai-chart-container">
1293 <canvas id="registrations-chart"></canvas>
1294 </div>
1295 </div>
1296 </div>
1297 </div>
1298
1299 <!-- Charts Row 2: Top Contributors and Activity Distribution -->
1300 <div class="wpforo-ai-analytics-charts-row">
1301 <!-- Top Contributors -->
1302 <div class="wpforo-ai-box wpforo-ai-chart-box">
1303 <div class="wpforo-ai-box-header">
1304 <h3>
1305 <span class="dashicons dashicons-awards"></span>
1306 <?php _e( 'Top Contributors', 'wpforo' ); ?>
1307 </h3>
1308 </div>
1309 <div class="wpforo-ai-box-body">
1310 <div class="wpforo-ai-top-contributors">
1311 <?php if ( ! empty( $engagement_data['top_contributors'] ) ) : ?>
1312 <div class="wpforo-ai-contributor-header">
1313 <div class="wpforo-ai-contributor-rank-header">#</div>
1314 <div class="wpforo-ai-contributor-avatar-header"></div>
1315 <div class="wpforo-ai-contributor-name-header"><?php _e( 'User', 'wpforo' ); ?></div>
1316 <div class="wpforo-ai-contributor-group-header"><?php _e( 'Group', 'wpforo' ); ?></div>
1317 <div class="wpforo-ai-contributor-topics-header"><?php _e( 'Topics', 'wpforo' ); ?></div>
1318 <div class="wpforo-ai-contributor-posts-header"><?php _e( 'Posts', 'wpforo' ); ?></div>
1319 </div>
1320 <?php foreach ( $engagement_data['top_contributors'] as $index => $contributor ) :
1321 $profile_url = WPF()->member->get_profile_url( $contributor['userid'] );
1322 ?>
1323 <div class="wpforo-ai-contributor-row">
1324 <div class="wpforo-ai-contributor-rank"><?php echo $index + 1; ?></div>
1325 <div class="wpforo-ai-contributor-avatar-col">
1326 <?php echo get_avatar( $contributor['userid'], 32, '', '', array( 'class' => 'wpforo-ai-contributor-avatar' ) ); ?>
1327 </div>
1328 <div class="wpforo-ai-contributor-name">
1329 <a href="<?php echo esc_url( $profile_url ); ?>" target="_blank" rel="noopener">
1330 <?php echo esc_html( $contributor['display_name'] ); ?>
1331 </a>
1332 </div>
1333 <div class="wpforo-ai-contributor-group">
1334 <span class="wpforo-ai-usergroup-badge" style="color: <?php echo esc_attr( $contributor['usergroup_color'] ); ?>">
1335 <?php echo esc_html( $contributor['usergroup_name'] ); ?>
1336 </span>
1337 </div>
1338 <div class="wpforo-ai-contributor-topics"><?php echo number_format( $contributor['topics'] ); ?></div>
1339 <div class="wpforo-ai-contributor-posts"><?php echo number_format( $contributor['replies'] ); ?></div>
1340 </div>
1341 <?php endforeach; ?>
1342 <?php else : ?>
1343 <div class="wpforo-ai-no-data"><?php _e( 'No contributors in this period', 'wpforo' ); ?></div>
1344 <?php endif; ?>
1345 </div>
1346 </div>
1347 </div>
1348
1349 <!-- User Activity Distribution -->
1350 <div class="wpforo-ai-box wpforo-ai-chart-box">
1351 <div class="wpforo-ai-box-header">
1352 <h3>
1353 <span class="dashicons dashicons-chart-pie"></span>
1354 <?php _e( 'User Activity Distribution', 'wpforo' ); ?>
1355 </h3>
1356 </div>
1357 <div class="wpforo-ai-box-body">
1358 <div class="wpforo-ai-chart-container wpforo-ai-chart-pie-container">
1359 <canvas id="user-distribution-chart"></canvas>
1360 </div>
1361 </div>
1362 </div>
1363 </div>
1364
1365 <?php
1366 // Output JavaScript data for charts
1367 $chart_data = array(
1368 'registrations' => $engagement_data['registrations_time_series'],
1369 'distribution' => $engagement_data['activity_distribution'],
1370 'i18n' => array(
1371 'registrations' => __( 'New Registrations', 'wpforo' ),
1372 'powerUsers' => __( 'Power Users (50+ posts)', 'wpforo' ),
1373 'activeUsers' => __( 'Active Users (10-49 posts)', 'wpforo' ),
1374 'occasional' => __( 'Occasional (2-9 posts)', 'wpforo' ),
1375 'oneTime' => __( 'One-time (1 post)', 'wpforo' ),
1376 'lurkers' => __( 'Lurkers (0 posts)', 'wpforo' ),
1377 ),
1378 );
1379 ?>
1380 <script type="text/javascript">
1381 var wpforoUserEngagement = <?php echo wp_json_encode( $chart_data ); ?>;
1382 </script>
1383 <?php
1384 }
1385
1386 /**
1387 * Get user engagement data from database
1388 *
1389 * @param int $board_id Board ID
1390 * @param int $start_time Start timestamp
1391 * @param int $end_time End timestamp
1392 * @return array Engagement data
1393 */
1394 function wpforo_ai_get_user_engagement_data( $board_id, $start_time, $end_time ) {
1395 $start_date = date( 'Y-m-d H:i:s', $start_time );
1396 $end_date = date( 'Y-m-d H:i:s', $end_time );
1397
1398 // Get summary stats
1399 $summary = wpforo_ai_get_engagement_summary( $start_date, $end_date );
1400
1401 // Get registrations time series
1402 $registrations = wpforo_ai_get_registrations_time_series( $start_date, $end_date );
1403
1404 // Get top contributors
1405 $contributors = wpforo_ai_get_top_contributors( $start_date, $end_date );
1406
1407 // Get user activity distribution
1408 $distribution = wpforo_ai_get_user_activity_distribution( $start_date, $end_date );
1409
1410 // Get recent registrations
1411 $recent = wpforo_ai_get_recent_registrations( $start_date, $end_date );
1412
1413 return array(
1414 'summary' => $summary,
1415 'registrations_time_series' => $registrations,
1416 'top_contributors' => $contributors,
1417 'activity_distribution' => $distribution,
1418 'recent_registrations' => $recent,
1419 );
1420 }
1421
1422 /**
1423 * Get engagement summary statistics
1424 *
1425 * @param string $start_date Start date
1426 * @param string $end_date End date
1427 * @return array Summary stats
1428 */
1429 function wpforo_ai_get_engagement_summary( $start_date, $end_date ) {
1430 global $wpdb;
1431
1432 // New members registered in period
1433 $new_members = (int) $wpdb->get_var( $wpdb->prepare(
1434 "SELECT COUNT(*) FROM {$wpdb->users}
1435 WHERE user_registered BETWEEN %s AND %s",
1436 $start_date, $end_date
1437 ) );
1438
1439 // Active members (users who posted in period)
1440 $active_members = (int) WPF()->db->get_var( WPF()->db->prepare(
1441 "SELECT COUNT(DISTINCT userid) FROM " . WPF()->tables->posts . "
1442 WHERE created BETWEEN %s AND %s
1443 AND userid > 0
1444 AND status = 0",
1445 $start_date, $end_date
1446 ) );
1447
1448 // Total registered users
1449 $total_users = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->users}" );
1450
1451 // Engagement rate (active / total)
1452 $engagement_rate = $total_users > 0 ? ( $active_members / $total_users ) * 100 : 0;
1453
1454 // Top contributor
1455 $top_contributor = WPF()->db->get_row( WPF()->db->prepare(
1456 "SELECT p.userid, u.display_name, COUNT(*) as posts
1457 FROM " . WPF()->tables->posts . " p
1458 JOIN {$wpdb->users} u ON p.userid = u.ID
1459 WHERE p.created BETWEEN %s AND %s
1460 AND p.userid > 0
1461 AND p.status = 0
1462 GROUP BY p.userid
1463 ORDER BY posts DESC
1464 LIMIT 1",
1465 $start_date, $end_date
1466 ), ARRAY_A );
1467
1468 return array(
1469 'new_members' => $new_members,
1470 'active_members' => $active_members,
1471 'engagement_rate' => $engagement_rate,
1472 'top_contributor_name' => $top_contributor ? $top_contributor['display_name'] : '',
1473 'top_contributor_posts' => $top_contributor ? (int) $top_contributor['posts'] : 0,
1474 );
1475 }
1476
1477 /**
1478 * Get registrations time series
1479 *
1480 * @param string $start_date Start date
1481 * @param string $end_date End date
1482 * @return array Time series data
1483 */
1484 function wpforo_ai_get_registrations_time_series( $start_date, $end_date ) {
1485 global $wpdb;
1486
1487 $results = $wpdb->get_results( $wpdb->prepare(
1488 "SELECT DATE(user_registered) as date, COUNT(*) as count
1489 FROM {$wpdb->users}
1490 WHERE user_registered BETWEEN %s AND %s
1491 GROUP BY DATE(user_registered)
1492 ORDER BY date ASC",
1493 $start_date, $end_date
1494 ), ARRAY_A );
1495
1496 // Fill in missing dates with zeros
1497 $time_series = array();
1498 $current = strtotime( date( 'Y-m-d', strtotime( $start_date ) ) );
1499 $end = strtotime( date( 'Y-m-d', strtotime( $end_date ) ) );
1500
1501 $data_lookup = array();
1502 foreach ( $results as $row ) {
1503 $data_lookup[ $row['date'] ] = (int) $row['count'];
1504 }
1505
1506 while ( $current <= $end ) {
1507 $date_str = date( 'Y-m-d', $current );
1508 $time_series[] = array(
1509 'date' => $date_str,
1510 'count' => isset( $data_lookup[ $date_str ] ) ? $data_lookup[ $date_str ] : 0,
1511 );
1512 $current = strtotime( '+1 day', $current );
1513 }
1514
1515 return $time_series;
1516 }
1517
1518 /**
1519 * Get top contributors
1520 *
1521 * @param string $start_date Start date
1522 * @param string $end_date End date
1523 * @return array Top contributors
1524 */
1525 function wpforo_ai_get_top_contributors( $start_date, $end_date ) {
1526 global $wpdb;
1527
1528 $results = WPF()->db->get_results( WPF()->db->prepare(
1529 "SELECT p.userid,
1530 u.display_name,
1531 SUM(CASE WHEN p.is_first_post = 1 THEN 1 ELSE 0 END) as topics,
1532 SUM(CASE WHEN p.is_first_post = 0 THEN 1 ELSE 0 END) as replies,
1533 COUNT(*) as total_posts,
1534 pr.groupid
1535 FROM " . WPF()->tables->posts . " p
1536 JOIN {$wpdb->users} u ON p.userid = u.ID
1537 LEFT JOIN " . WPF()->tables->profiles . " pr ON p.userid = pr.userid
1538 WHERE p.created BETWEEN %s AND %s
1539 AND p.userid > 0
1540 AND p.status = 0
1541 GROUP BY p.userid, u.display_name, pr.groupid
1542 ORDER BY total_posts DESC
1543 LIMIT 10",
1544 $start_date, $end_date
1545 ), ARRAY_A );
1546
1547 // Add usergroup names
1548 if ( $results ) {
1549 foreach ( $results as &$row ) {
1550 $groupid = (int) $row['groupid'];
1551 $usergroup = WPF()->usergroup->get_usergroup( $groupid );
1552 $row['usergroup_name'] = $usergroup ? $usergroup['name'] : '';
1553 $row['usergroup_color'] = $usergroup && ! empty( $usergroup['color'] ) ? $usergroup['color'] : '#666';
1554 }
1555 }
1556
1557 return $results ?: array();
1558 }
1559
1560 /**
1561 * Get user activity distribution
1562 *
1563 * @param string $start_date Start date
1564 * @param string $end_date End date
1565 * @return array Distribution data
1566 */
1567 function wpforo_ai_get_user_activity_distribution( $start_date, $end_date ) {
1568 global $wpdb;
1569
1570 // Get post counts for all users
1571 $user_posts = WPF()->db->get_results( WPF()->db->prepare(
1572 "SELECT userid, COUNT(*) as posts
1573 FROM " . WPF()->tables->posts . "
1574 WHERE created BETWEEN %s AND %s
1575 AND userid > 0
1576 AND status = 0
1577 GROUP BY userid",
1578 $start_date, $end_date
1579 ), ARRAY_A );
1580
1581 // Count users in each category
1582 $power_users = 0; // 50+ posts
1583 $active_users = 0; // 10-49 posts
1584 $occasional = 0; // 2-9 posts
1585 $one_time = 0; // 1 post
1586
1587 foreach ( $user_posts as $user ) {
1588 $posts = (int) $user['posts'];
1589 if ( $posts >= 50 ) {
1590 $power_users++;
1591 } elseif ( $posts >= 10 ) {
1592 $active_users++;
1593 } elseif ( $posts >= 2 ) {
1594 $occasional++;
1595 } else {
1596 $one_time++;
1597 }
1598 }
1599
1600 // Count lurkers (users registered in period who haven't posted)
1601 $registered_users = $wpdb->get_var( $wpdb->prepare(
1602 "SELECT COUNT(*) FROM {$wpdb->users}
1603 WHERE user_registered BETWEEN %s AND %s",
1604 $start_date, $end_date
1605 ) );
1606
1607 $posting_users = count( $user_posts );
1608 $lurkers = max( 0, (int) $registered_users - $posting_users );
1609
1610 return array(
1611 'power_users' => $power_users,
1612 'active_users' => $active_users,
1613 'occasional' => $occasional,
1614 'one_time' => $one_time,
1615 'lurkers' => $lurkers,
1616 );
1617 }
1618
1619 /**
1620 * Get recent registrations with post info
1621 *
1622 * @param string $start_date Start date
1623 * @param string $end_date End date
1624 * @return array Recent registrations
1625 */
1626 function wpforo_ai_get_recent_registrations( $start_date, $end_date ) {
1627 global $wpdb;
1628
1629 $users = $wpdb->get_results( $wpdb->prepare(
1630 "SELECT ID, display_name, user_registered
1631 FROM {$wpdb->users}
1632 WHERE user_registered BETWEEN %s AND %s
1633 ORDER BY user_registered DESC
1634 LIMIT 20",
1635 $start_date, $end_date
1636 ), ARRAY_A );
1637
1638 $result = array();
1639 foreach ( $users as $user ) {
1640 // Get post count and first post date for this user
1641 $post_info = WPF()->db->get_row( WPF()->db->prepare(
1642 "SELECT COUNT(*) as posts, MIN(created) as first_post
1643 FROM " . WPF()->tables->posts . "
1644 WHERE userid = %d AND status = 0",
1645 $user['ID']
1646 ), ARRAY_A );
1647
1648 $result[] = array(
1649 'userid' => $user['ID'],
1650 'display_name' => $user['display_name'],
1651 'registered_date' => wpforo_ai_format_date( $user['user_registered'] ),
1652 'posts' => $post_info ? (int) $post_info['posts'] : 0,
1653 'first_post_date' => ( $post_info && $post_info['first_post'] ) ? wpforo_ai_format_date( $post_info['first_post'] ) : null,
1654 );
1655 }
1656
1657 return $result;
1658 }
1659
1660 /**
1661 * Render Content Performance Analytics sub-tab
1662 *
1663 * FREE feature - all data comes from local wpForo database.
1664 *
1665 * @param int $board_id Current board ID
1666 */
1667 function wpforo_ai_render_content_performance_subtab( $board_id ) {
1668 // Time range presets
1669 $time_ranges = array(
1670 'today' => __( 'Today', 'wpforo' ),
1671 '7days' => __( 'Last 7 Days', 'wpforo' ),
1672 '30days' => __( 'Last 30 Days', 'wpforo' ),
1673 '90days' => __( 'Last 90 Days', 'wpforo' ),
1674 'this_year' => __( 'This Year', 'wpforo' ),
1675 'all_time' => __( 'All Time', 'wpforo' ),
1676 'custom' => __( 'Custom Range', 'wpforo' ),
1677 );
1678
1679 $current_range = isset( $_GET['range'] ) ? sanitize_key( $_GET['range'] ) : '30days';
1680 $custom_start = isset( $_GET['start_date'] ) ? sanitize_text_field( $_GET['start_date'] ) : '';
1681 $custom_end = isset( $_GET['end_date'] ) ? sanitize_text_field( $_GET['end_date'] ) : '';
1682
1683 // Calculate time range
1684 $time_info = wpforo_ai_calculate_time_range( $current_range, $custom_start, $custom_end );
1685
1686 // Get content performance data
1687 $content_data = wpforo_ai_get_content_performance_data( $board_id, $time_info['start'], $time_info['end'] );
1688 ?>
1689
1690 <!-- Time Range Selector -->
1691 <div class="wpforo-ai-box wpforo-ai-time-range-box">
1692 <div class="wpforo-ai-box-body">
1693 <div class="wpforo-ai-time-range-selector">
1694 <div class="wpforo-ai-time-range-label">
1695 <span class="dashicons dashicons-calendar-alt"></span>
1696 <?php _e( 'Time Range:', 'wpforo' ); ?>
1697 </div>
1698 <div class="wpforo-ai-time-range-buttons">
1699 <?php foreach ( $time_ranges as $range_key => $range_label ) :
1700 if ( $range_key === 'custom' ) continue;
1701 $range_url = add_query_arg( array(
1702 'page' => 'wpforo-ai',
1703 'tab' => 'analytics',
1704 'subtab' => 'content',
1705 'boardid' => $board_id,
1706 'range' => $range_key,
1707 ), admin_url( 'admin.php' ) );
1708 $active_class = ( $current_range === $range_key ) ? 'active' : '';
1709 ?>
1710 <a href="<?php echo esc_url( $range_url ); ?>"
1711 class="wpforo-ai-time-range-btn <?php echo esc_attr( $active_class ); ?>"
1712 data-range="<?php echo esc_attr( $range_key ); ?>">
1713 <?php echo esc_html( $range_label ); ?>
1714 </a>
1715 <?php endforeach; ?>
1716 <button type="button" class="wpforo-ai-time-range-btn wpforo-ai-custom-range-toggle <?php echo $current_range === 'custom' ? 'active' : ''; ?>">
1717 <?php _e( 'Custom', 'wpforo' ); ?>
1718 </button>
1719 </div>
1720 <div class="wpforo-ai-custom-range-picker" style="<?php echo $current_range === 'custom' ? '' : 'display: none;'; ?>">
1721 <form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>">
1722 <input type="hidden" name="page" value="wpforo-ai">
1723 <input type="hidden" name="tab" value="analytics">
1724 <input type="hidden" name="subtab" value="content">
1725 <input type="hidden" name="boardid" value="<?php echo esc_attr( $board_id ); ?>">
1726 <input type="hidden" name="range" value="custom">
1727 <label>
1728 <?php _e( 'From:', 'wpforo' ); ?>
1729 <input type="date" name="start_date" value="<?php echo esc_attr( $custom_start ); ?>" required>
1730 </label>
1731 <label>
1732 <?php _e( 'To:', 'wpforo' ); ?>
1733 <input type="date" name="end_date" value="<?php echo esc_attr( $custom_end ); ?>" required>
1734 </label>
1735 <button type="submit" class="button button-primary">
1736 <?php _e( 'Apply', 'wpforo' ); ?>
1737 </button>
1738 </form>
1739 </div>
1740 <div class="wpforo-ai-time-range-info">
1741 <span class="wpforo-ai-date-range-display">
1742 <?php echo esc_html( $time_info['display'] ); ?>
1743 </span>
1744 </div>
1745 </div>
1746 </div>
1747 </div>
1748
1749 <!-- Summary Cards -->
1750 <div class="wpforo-ai-analytics-summary-cards">
1751 <div class="wpforo-ai-analytics-card">
1752 <div class="wpforo-ai-analytics-card-icon">
1753 <span class="dashicons dashicons-visibility"></span>
1754 </div>
1755 <div class="wpforo-ai-analytics-card-content">
1756 <div class="wpforo-ai-analytics-card-value">
1757 <?php echo number_format( $content_data['summary']['total_views'] ); ?>
1758 </div>
1759 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Total Views', 'wpforo' ); ?></div>
1760 </div>
1761 </div>
1762 <div class="wpforo-ai-analytics-card">
1763 <div class="wpforo-ai-analytics-card-icon">
1764 <span class="dashicons dashicons-format-chat"></span>
1765 </div>
1766 <div class="wpforo-ai-analytics-card-content">
1767 <div class="wpforo-ai-analytics-card-value">
1768 <?php echo number_format( $content_data['summary']['total_topics'] ); ?>
1769 </div>
1770 <div class="wpforo-ai-analytics-card-label"><?php _e( 'New Topics', 'wpforo' ); ?></div>
1771 </div>
1772 </div>
1773 <div class="wpforo-ai-analytics-card">
1774 <div class="wpforo-ai-analytics-card-icon">
1775 <span class="dashicons dashicons-warning"></span>
1776 </div>
1777 <div class="wpforo-ai-analytics-card-content">
1778 <div class="wpforo-ai-analytics-card-value">
1779 <?php echo number_format( $content_data['summary']['unanswered_count'] ); ?>
1780 </div>
1781 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Unanswered', 'wpforo' ); ?></div>
1782 </div>
1783 </div>
1784 <div class="wpforo-ai-analytics-card">
1785 <div class="wpforo-ai-analytics-card-icon">
1786 <span class="dashicons dashicons-yes-alt"></span>
1787 </div>
1788 <div class="wpforo-ai-analytics-card-content">
1789 <div class="wpforo-ai-analytics-card-value">
1790 <?php echo number_format( $content_data['summary']['solved_rate'], 1 ); ?>%
1791 </div>
1792 <div class="wpforo-ai-analytics-card-label"><?php _e( 'Solved Rate', 'wpforo' ); ?></div>
1793 </div>
1794 </div>
1795 </div>
1796
1797 <!-- Charts Row 1: Most Viewed and Most Discussed -->
1798 <div class="wpforo-ai-analytics-charts-row">
1799 <!-- Most Viewed Topics -->
1800 <div class="wpforo-ai-box wpforo-ai-chart-box">
1801 <div class="wpforo-ai-box-header">
1802 <h3><?php _e( 'Most Viewed Topics', 'wpforo' ); ?></h3>
1803 </div>
1804 <div class="wpforo-ai-box-body">
1805 <?php if ( ! empty( $content_data['most_viewed'] ) ) : ?>
1806 <div class="wpforo-ai-content-perf-list">
1807 <div class="wpforo-ai-content-perf-header">
1808 <span class="wpforo-ai-content-perf-col-rank">#</span>
1809 <span class="wpforo-ai-content-perf-col-title"><?php _e( 'Topic', 'wpforo' ); ?></span>
1810 <span class="wpforo-ai-content-perf-col-views"><?php _e( 'Views', 'wpforo' ); ?></span>
1811 <span class="wpforo-ai-content-perf-col-replies"><?php _e( 'Replies', 'wpforo' ); ?></span>
1812 </div>
1813 <?php foreach ( $content_data['most_viewed'] as $index => $topic ) :
1814 $topic_url = WPF()->topic->get_url( $topic['topicid'] );
1815 ?>
1816 <div class="wpforo-ai-content-perf-row">
1817 <span class="wpforo-ai-content-perf-col-rank"><?php echo $index + 1; ?></span>
1818 <span class="wpforo-ai-content-perf-col-title">
1819 <a href="<?php echo esc_url( $topic_url ); ?>" target="_blank" rel="noopener">
1820 <?php echo esc_html( wp_trim_words( $topic['title'], 10 ) ); ?>
1821 </a>
1822 <small><?php echo esc_html( $topic['forum_title'] ); ?></small>
1823 </span>
1824 <span class="wpforo-ai-content-perf-col-views"><?php echo number_format( $topic['views'] ); ?></span>
1825 <span class="wpforo-ai-content-perf-col-replies"><?php echo number_format( $topic['replies'] ); ?></span>
1826 </div>
1827 <?php endforeach; ?>
1828 </div>
1829 <?php else : ?>
1830 <div class="wpforo-ai-no-data"><?php _e( 'No topics in this period', 'wpforo' ); ?></div>
1831 <?php endif; ?>
1832 </div>
1833 </div>
1834
1835 <!-- Most Discussed Topics -->
1836 <div class="wpforo-ai-box wpforo-ai-chart-box">
1837 <div class="wpforo-ai-box-header">
1838 <h3><?php _e( 'Most Discussed Topics', 'wpforo' ); ?></h3>
1839 </div>
1840 <div class="wpforo-ai-box-body">
1841 <?php if ( ! empty( $content_data['most_discussed'] ) ) : ?>
1842 <div class="wpforo-ai-content-perf-list">
1843 <div class="wpforo-ai-content-perf-header">
1844 <span class="wpforo-ai-content-perf-col-rank">#</span>
1845 <span class="wpforo-ai-content-perf-col-title"><?php _e( 'Topic', 'wpforo' ); ?></span>
1846 <span class="wpforo-ai-content-perf-col-replies"><?php _e( 'Replies', 'wpforo' ); ?></span>
1847 <span class="wpforo-ai-content-perf-col-views"><?php _e( 'Views', 'wpforo' ); ?></span>
1848 </div>
1849 <?php foreach ( $content_data['most_discussed'] as $index => $topic ) :
1850 $topic_url = WPF()->topic->get_url( $topic['topicid'] );
1851 ?>
1852 <div class="wpforo-ai-content-perf-row">
1853 <span class="wpforo-ai-content-perf-col-rank"><?php echo $index + 1; ?></span>
1854 <span class="wpforo-ai-content-perf-col-title">
1855 <a href="<?php echo esc_url( $topic_url ); ?>" target="_blank" rel="noopener">
1856 <?php echo esc_html( wp_trim_words( $topic['title'], 10 ) ); ?>
1857 </a>
1858 <small><?php echo esc_html( $topic['forum_title'] ); ?></small>
1859 </span>
1860 <span class="wpforo-ai-content-perf-col-replies"><?php echo number_format( $topic['replies'] ); ?></span>
1861 <span class="wpforo-ai-content-perf-col-views"><?php echo number_format( $topic['views'] ); ?></span>
1862 </div>
1863 <?php endforeach; ?>
1864 </div>
1865 <?php else : ?>
1866 <div class="wpforo-ai-no-data"><?php _e( 'No topics in this period', 'wpforo' ); ?></div>
1867 <?php endif; ?>
1868 </div>
1869 </div>
1870 </div>
1871
1872 <!-- Content by Forum Distribution -->
1873 <div class="wpforo-ai-box wpforo-ai-usage-table-box">
1874 <div class="wpforo-ai-box-header">
1875 <h3><?php _e( 'Content by Forum', 'wpforo' ); ?></h3>
1876 </div>
1877 <div class="wpforo-ai-box-body">
1878 <div class="wpforo-ai-chart-container wpforo-ai-chart-pie-container">
1879 <canvas id="content-distribution-chart"></canvas>
1880 </div>
1881 </div>
1882 </div>
1883
1884 <!-- Popular Tags -->
1885 <div class="wpforo-ai-box wpforo-ai-usage-table-box">
1886 <div class="wpforo-ai-box-header">
1887 <h3><?php _e( 'Popular Tags', 'wpforo' ); ?></h3>
1888 </div>
1889 <div class="wpforo-ai-box-body">
1890 <?php if ( ! empty( $content_data['popular_tags'] ) ) : ?>
1891 <div class="wpforo-ai-tag-cloud">
1892 <?php
1893 $max_count = max( array_column( $content_data['popular_tags'], 'count' ) );
1894 $forum_url = wpforo_home_url();
1895 foreach ( $content_data['popular_tags'] as $tag ) :
1896 $size = 0.8 + ( ( $tag['count'] / $max_count ) * 0.8 );
1897 $tag_url = add_query_arg( array(
1898 'wpfin' => 'tag',
1899 'wpfs' => $tag['tag'],
1900 ), $forum_url );
1901 ?>
1902 <a href="<?php echo esc_url( $tag_url ); ?>" class="wpforo-ai-tag" target="_blank" rel="noopener" style="font-size: <?php echo $size; ?>em;">
1903 <?php echo esc_html( $tag['tag'] ); ?>
1904 <span class="wpforo-ai-tag-count"><?php echo number_format( $tag['count'] ); ?></span>
1905 </a>
1906 <?php endforeach; ?>
1907 </div>
1908 <?php else : ?>
1909 <div class="wpforo-ai-no-data"><?php _e( 'No tags found', 'wpforo' ); ?></div>
1910 <?php endif; ?>
1911 </div>
1912 </div>
1913
1914 <?php
1915 // Output JavaScript data for charts
1916 $chart_data = array(
1917 'forumDistribution' => $content_data['forum_distribution'],
1918 'i18n' => array(
1919 'topics' => __( 'Topics', 'wpforo' ),
1920 ),
1921 );
1922 ?>
1923 <script type="text/javascript">
1924 var wpforoContentPerformance = <?php echo wp_json_encode( $chart_data ); ?>;
1925 </script>
1926 <?php
1927 }
1928
1929 /**
1930 * Get content performance data from database
1931 *
1932 * @param int $board_id Board ID
1933 * @param int $start_time Start timestamp
1934 * @param int $end_time End timestamp
1935 * @return array Content performance data
1936 */
1937 function wpforo_ai_get_content_performance_data( $board_id, $start_time, $end_time ) {
1938 $start_date = date( 'Y-m-d H:i:s', $start_time );
1939 $end_date = date( 'Y-m-d H:i:s', $end_time );
1940
1941 // Get summary stats
1942 $summary = wpforo_ai_get_content_summary( $start_date, $end_date );
1943
1944 // Get most viewed topics
1945 $most_viewed = wpforo_ai_get_most_viewed_topics( $start_date, $end_date );
1946
1947 // Get most discussed topics
1948 $most_discussed = wpforo_ai_get_most_discussed_topics( $start_date, $end_date );
1949
1950 // Get unanswered topics
1951 $unanswered = wpforo_ai_get_unanswered_topics( $start_date, $end_date );
1952
1953 // Get forum distribution
1954 $forum_distribution = wpforo_ai_get_forum_distribution( $start_date, $end_date );
1955
1956 // Get popular tags
1957 $popular_tags = wpforo_ai_get_popular_tags( $start_date, $end_date );
1958
1959 return array(
1960 'summary' => $summary,
1961 'most_viewed' => $most_viewed,
1962 'most_discussed' => $most_discussed,
1963 'unanswered' => $unanswered,
1964 'forum_distribution' => $forum_distribution,
1965 'popular_tags' => $popular_tags,
1966 );
1967 }
1968
1969 /**
1970 * Get content summary statistics
1971 *
1972 * @param string $start_date Start date
1973 * @param string $end_date End date
1974 * @return array Summary stats
1975 */
1976 function wpforo_ai_get_content_summary( $start_date, $end_date ) {
1977 // Total views of topics created in period
1978 $total_views = (int) WPF()->db->get_var( WPF()->db->prepare(
1979 "SELECT COALESCE(SUM(views), 0) FROM " . WPF()->tables->topics . "
1980 WHERE created BETWEEN %s AND %s
1981 AND status = 0",
1982 $start_date, $end_date
1983 ) );
1984
1985 // Total topics created
1986 $total_topics = (int) WPF()->db->get_var( WPF()->db->prepare(
1987 "SELECT COUNT(*) FROM " . WPF()->tables->topics . "
1988 WHERE created BETWEEN %s AND %s
1989 AND status = 0",
1990 $start_date, $end_date
1991 ) );
1992
1993 // Unanswered topics (created in period with only 1 post - the original)
1994 $unanswered_count = (int) WPF()->db->get_var( WPF()->db->prepare(
1995 "SELECT COUNT(*) FROM " . WPF()->tables->topics . "
1996 WHERE created BETWEEN %s AND %s
1997 AND posts = 1
1998 AND status = 0",
1999 $start_date, $end_date
2000 ) );
2001
2002 // Solved topics (if solved field exists)
2003 $solved_count = (int) WPF()->db->get_var( WPF()->db->prepare(
2004 "SELECT COUNT(*) FROM " . WPF()->tables->topics . "
2005 WHERE created BETWEEN %s AND %s
2006 AND solved = 1
2007 AND status = 0",
2008 $start_date, $end_date
2009 ) );
2010
2011 // Calculate solved rate
2012 $solved_rate = $total_topics > 0 ? ( $solved_count / $total_topics ) * 100 : 0;
2013
2014 return array(
2015 'total_views' => $total_views,
2016 'total_topics' => $total_topics,
2017 'unanswered_count' => $unanswered_count,
2018 'solved_rate' => $solved_rate,
2019 );
2020 }
2021
2022 /**
2023 * Get most viewed topics
2024 *
2025 * @param string $start_date Start date
2026 * @param string $end_date End date
2027 * @return array Most viewed topics
2028 */
2029 function wpforo_ai_get_most_viewed_topics( $start_date, $end_date ) {
2030 $results = WPF()->db->get_results( WPF()->db->prepare(
2031 "SELECT t.topicid, t.title, t.views, (t.posts - 1) as replies, f.title as forum_title
2032 FROM " . WPF()->tables->topics . " t
2033 LEFT JOIN " . WPF()->tables->forums . " f ON t.forumid = f.forumid
2034 WHERE t.created BETWEEN %s AND %s
2035 AND t.status = 0
2036 ORDER BY t.views DESC
2037 LIMIT 10",
2038 $start_date, $end_date
2039 ), ARRAY_A );
2040
2041 return $results ?: array();
2042 }
2043
2044 /**
2045 * Get most discussed topics
2046 *
2047 * @param string $start_date Start date
2048 * @param string $end_date End date
2049 * @return array Most discussed topics
2050 */
2051 function wpforo_ai_get_most_discussed_topics( $start_date, $end_date ) {
2052 $results = WPF()->db->get_results( WPF()->db->prepare(
2053 "SELECT t.topicid, t.title, t.views, (t.posts - 1) as replies, f.title as forum_title
2054 FROM " . WPF()->tables->topics . " t
2055 LEFT JOIN " . WPF()->tables->forums . " f ON t.forumid = f.forumid
2056 WHERE t.created BETWEEN %s AND %s
2057 AND t.status = 0
2058 ORDER BY t.posts DESC
2059 LIMIT 10",
2060 $start_date, $end_date
2061 ), ARRAY_A );
2062
2063 return $results ?: array();
2064 }
2065
2066 /**
2067 * Get unanswered topics
2068 *
2069 * @param string $start_date Start date
2070 * @param string $end_date End date
2071 * @return array Unanswered topics
2072 */
2073 function wpforo_ai_get_unanswered_topics( $start_date, $end_date ) {
2074 $results = WPF()->db->get_results( WPF()->db->prepare(
2075 "SELECT t.topicid, t.title, t.created, f.title as forum_title
2076 FROM " . WPF()->tables->topics . " t
2077 LEFT JOIN " . WPF()->tables->forums . " f ON t.forumid = f.forumid
2078 WHERE t.created BETWEEN %s AND %s
2079 AND t.posts = 1
2080 AND t.status = 0
2081 ORDER BY t.created DESC
2082 LIMIT 15",
2083 $start_date, $end_date
2084 ), ARRAY_A );
2085
2086 return $results ?: array();
2087 }
2088
2089 /**
2090 * Get forum distribution for pie chart
2091 *
2092 * @param string $start_date Start date
2093 * @param string $end_date End date
2094 * @return array Forum distribution
2095 */
2096 function wpforo_ai_get_forum_distribution( $start_date, $end_date ) {
2097 $results = WPF()->db->get_results( WPF()->db->prepare(
2098 "SELECT f.forumid, f.title, COUNT(t.topicid) as topics
2099 FROM " . WPF()->tables->forums . " f
2100 LEFT JOIN " . WPF()->tables->topics . " t ON f.forumid = t.forumid
2101 AND t.created BETWEEN %s AND %s
2102 AND t.status = 0
2103 WHERE f.is_cat = 0
2104 GROUP BY f.forumid, f.title
2105 HAVING topics > 0
2106 ORDER BY topics DESC
2107 LIMIT 8",
2108 $start_date, $end_date
2109 ), ARRAY_A );
2110
2111 return $results ?: array();
2112 }
2113
2114 /**
2115 * Get popular tags
2116 *
2117 * @param string $start_date Start date
2118 * @param string $end_date End date
2119 * @return array Popular tags
2120 */
2121 function wpforo_ai_get_popular_tags( $start_date, $end_date ) {
2122 // Get topics with tags in the period
2123 $topics_with_tags = WPF()->db->get_results( WPF()->db->prepare(
2124 "SELECT tags FROM " . WPF()->tables->topics . "
2125 WHERE created BETWEEN %s AND %s
2126 AND tags IS NOT NULL AND tags != ''
2127 AND status = 0",
2128 $start_date, $end_date
2129 ), ARRAY_A );
2130
2131 // Count tag occurrences
2132 $tag_counts = array();
2133 foreach ( $topics_with_tags as $topic ) {
2134 $tags = maybe_unserialize( $topic['tags'] );
2135 if ( is_array( $tags ) ) {
2136 foreach ( $tags as $tag ) {
2137 $tag = trim( $tag );
2138 if ( $tag ) {
2139 if ( ! isset( $tag_counts[ $tag ] ) ) {
2140 $tag_counts[ $tag ] = 0;
2141 }
2142 $tag_counts[ $tag ]++;
2143 }
2144 }
2145 } elseif ( is_string( $tags ) && $tags ) {
2146 // Handle comma-separated tags
2147 $tag_array = explode( ',', $tags );
2148 foreach ( $tag_array as $tag ) {
2149 $tag = trim( $tag );
2150 if ( $tag ) {
2151 if ( ! isset( $tag_counts[ $tag ] ) ) {
2152 $tag_counts[ $tag ] = 0;
2153 }
2154 $tag_counts[ $tag ]++;
2155 }
2156 }
2157 }
2158 }
2159
2160 // Sort by count and limit
2161 arsort( $tag_counts );
2162 $tag_counts = array_slice( $tag_counts, 0, 30, true );
2163
2164 // Format for output
2165 $result = array();
2166 foreach ( $tag_counts as $tag => $count ) {
2167 $result[] = array(
2168 'tag' => $tag,
2169 'count' => $count,
2170 );
2171 }
2172
2173 return $result;
2174 }
2175
2176 /**
2177 * Render AI-Powered Insights sub-tab
2178 *
2179 * Premium features that use credits for AI analysis:
2180 * - Sentiment Analysis: Analyze community mood from recent posts
2181 * - Trending Topics: AI-detected emerging discussions
2182 * - Growth Recommendations: AI-generated actionable suggestions
2183 *
2184 * @param int $board_id Current board ID
2185 * @param array $status Tenant status data
2186 */
2187 function wpforo_ai_render_ai_insights_subtab( $board_id, $status ) {
2188 // Get available credits
2189 $subscription = isset( $status['subscription'] ) && is_array( $status['subscription'] ) ? $status['subscription'] : [];
2190 $credits_remaining = isset( $subscription['credits_remaining'] ) ? (int) $subscription['credits_remaining'] : 0;
2191 $credits_total = isset( $subscription['credits_total'] ) ? (int) $subscription['credits_total'] : 0;
2192
2193 // Define insight types with their credit costs
2194 // Top row: 2 column grid (half width each)
2195 $insight_types_top = array(
2196 'sentiment' => array(
2197 'label' => __( 'Community Sentiment', 'wpforo' ),
2198 'description' => __( 'Analyze mood with different emotion categories: happy, excited, frustrated, angry...', 'wpforo' ),
2199 'period' => __( 'Based on last 200 posts', 'wpforo' ),
2200 'credits' => 2,
2201 'icon' => 'chart-pie',
2202 ),
2203 'trending' => array(
2204 'label' => __( 'Trending Topics', 'wpforo' ),
2205 'description' => __( 'Discover emerging discussions and hot topics in your forum using AI pattern detection.', 'wpforo' ),
2206 'period' => __( 'Last 30 days', 'wpforo' ),
2207 'credits' => 1,
2208 'icon' => 'chart-line',
2209 ),
2210 );
2211
2212 // Full-width sections below
2213 $insight_types_full = array(
2214 'deep_analysis' => array(
2215 'label' => __( 'Community Deep Dive', 'wpforo' ),
2216 'description' => __( 'Comprehensive analysis of user engagement patterns, content metrics, keyword sentiment hotspots, top contributors mood, and activity patterns.', 'wpforo' ),
2217 'period' => __( 'Last 30 days', 'wpforo' ),
2218 'credits' => 5,
2219 'icon' => 'analytics',
2220 'full_width' => true,
2221 ),
2222 'sentiment_trend' => array(
2223 'label' => __( 'Sentiment Trend Over Time', 'wpforo' ),
2224 'description' => __( 'Track how community mood has changed over time with visualization of sentiment shifts, key events, and mood forecasting.', 'wpforo' ),
2225 'period' => __( 'Last 30 days', 'wpforo' ),
2226 'credits' => 4,
2227 'icon' => 'chart-area',
2228 'full_width' => true,
2229 ),
2230 'recommendations' => array(
2231 'label' => __( 'Growth Recommendations', 'wpforo' ),
2232 'description' => __( 'Get AI-generated actionable moderation suggestions based on your forum\'s data. Available once per day.', 'wpforo' ),
2233 'period' => __( 'All-time statistics with recent activity', 'wpforo' ),
2234 'credits' => 1,
2235 'icon' => 'lightbulb',
2236 'full_width' => true,
2237 'daily_limit' => true,
2238 ),
2239 );
2240
2241 // Combined for cache lookup
2242 $insight_types = array_merge( $insight_types_top, $insight_types_full );
2243
2244 // Check for cached results
2245 $cached_insights = wpforo_ai_get_cached_insights( $board_id );
2246 ?>
2247
2248 <div class="wpforo-ai-insights-container">
2249
2250 <!-- Top Row Insights Grid (3 columns) -->
2251 <div class="wpforo-ai-insights-grid wpforo-ai-insights-grid-top">
2252
2253 <?php foreach ( $insight_types_top as $type => $insight ) :
2254 $cached_data = isset( $cached_insights[ $type ] ) ? $cached_insights[ $type ] : null;
2255 $has_cached = ! empty( $cached_data ) && isset( $cached_data['data'] );
2256 $cached_time = $has_cached && isset( $cached_data['timestamp'] ) ? human_time_diff( $cached_data['timestamp'], time() ) : '';
2257 $is_outdated = $has_cached && isset( $cached_data['timestamp'] ) && wpforo_ai_is_insight_outdated( $cached_data['timestamp'] );
2258 ?>
2259
2260 <!-- <?php echo esc_html( $insight['label'] ); ?> Widget -->
2261 <div class="wpforo-ai-box wpforo-ai-insights-widget" data-insight-type="<?php echo esc_attr( $type ); ?>">
2262 <div class="wpforo-ai-box-header">
2263 <h3>
2264 <span class="dashicons dashicons-<?php echo esc_attr( $insight['icon'] ); ?>"></span>
2265 <?php echo esc_html( $insight['label'] ); ?>
2266 </h3>
2267 <span class="wpforo-ai-insights-credits-cost">
2268 <?php echo esc_html( $insight['credits'] ); ?> <?php _e( 'credits', 'wpforo' ); ?>
2269 </span>
2270 </div>
2271 <div class="wpforo-ai-box-body">
2272 <p class="wpforo-ai-insights-description"><?php echo esc_html( $insight['description'] ); ?></p>
2273 <?php if ( ! empty( $insight['period'] ) ) : ?>
2274 <p class="wpforo-ai-insights-period">
2275 <span class="dashicons dashicons-calendar-alt"></span>
2276 <?php echo esc_html( $insight['period'] ); ?>
2277 </p>
2278 <?php endif; ?>
2279
2280 <!-- Results Container (hidden until analysis is run) -->
2281 <div class="wpforo-ai-insights-results" id="wpforo-ai-insight-results-<?php echo esc_attr( $type ); ?>" style="<?php echo $has_cached ? '' : 'display: none;'; ?>">
2282 <?php if ( $has_cached ) : ?>
2283 <?php wpforo_ai_render_insight_results( $type, $cached_data['data'] ); ?>
2284 <div class="wpforo-ai-insights-cached-notice">
2285 <span class="dashicons dashicons-clock"></span>
2286 <?php
2287 /* translators: %s: time ago */
2288 printf( __( 'Cached %s ago', 'wpforo' ), $cached_time );
2289 ?>
2290 </div>
2291 <?php endif; ?>
2292 </div>
2293
2294 <!-- Loading State -->
2295 <div class="wpforo-ai-insights-loading" style="display: none;">
2296 <span class="spinner is-active"></span>
2297 <span><?php _e( 'Analyzing...', 'wpforo' ); ?></span>
2298 </div>
2299
2300 <!-- Error Message -->
2301 <div class="wpforo-ai-insights-error" style="display: none;"></div>
2302
2303 <!-- Action Button -->
2304 <div class="wpforo-ai-insights-actions">
2305 <button type="button"
2306 class="button button-primary wpforo-ai-run-insight-btn"
2307 data-insight-type="<?php echo esc_attr( $type ); ?>"
2308 data-credits="<?php echo esc_attr( $insight['credits'] ); ?>"
2309 <?php echo $credits_remaining < $insight['credits'] ? 'disabled' : ''; ?>>
2310 <?php if ( $has_cached ) : ?>
2311 <span class="dashicons dashicons-update"></span>
2312 <?php _e( 'Refresh Analysis', 'wpforo' ); ?>
2313 <?php else : ?>
2314 <span class="dashicons dashicons-yes"></span>
2315 <?php _e( 'Run Analysis', 'wpforo' ); ?>
2316 <?php endif; ?>
2317 </button>
2318 <?php if ( $is_outdated ) : ?>
2319 <span class="wpforo-ai-insights-outdated-notice">
2320 <span class="dashicons dashicons-warning"></span>
2321 <?php _e( 'Data may be outdated. Refresh for current information.', 'wpforo' ); ?>
2322 </span>
2323 <?php endif; ?>
2324 <?php if ( $credits_remaining < $insight['credits'] ) : ?>
2325 <span class="wpforo-ai-insights-insufficient">
2326 <?php _e( 'Insufficient credits', 'wpforo' ); ?>
2327 </span>
2328 <?php endif; ?>
2329 </div>
2330 </div>
2331 </div>
2332
2333 <?php endforeach; ?>
2334
2335 </div>
2336
2337 <!-- Full-Width Insight Sections -->
2338 <?php foreach ( $insight_types_full as $type => $insight ) :
2339 $cached_data = isset( $cached_insights[ $type ] ) ? $cached_insights[ $type ] : null;
2340 $has_cached = ! empty( $cached_data ) && isset( $cached_data['data'] );
2341 $cached_time = $has_cached && isset( $cached_data['timestamp'] ) ? human_time_diff( $cached_data['timestamp'], time() ) : '';
2342 $is_outdated = $has_cached && isset( $cached_data['timestamp'] ) && wpforo_ai_is_insight_outdated( $cached_data['timestamp'] );
2343
2344 // Check for daily limit
2345 $has_daily_limit = isset( $insight['daily_limit'] ) && $insight['daily_limit'];
2346 $used_today = false;
2347 if ( $has_daily_limit && $has_cached && isset( $cached_data['timestamp'] ) ) {
2348 // Check if cached result is from today
2349 $cached_date = date( 'Y-m-d', $cached_data['timestamp'] );
2350 $today_date = date( 'Y-m-d', current_time( 'timestamp' ) );
2351 $used_today = ( $cached_date === $today_date );
2352 }
2353 ?>
2354
2355 <!-- <?php echo esc_html( $insight['label'] ); ?> Full-Width Section -->
2356 <div class="wpforo-ai-box wpforo-ai-insights-widget wpforo-ai-insights-widget-full" data-insight-type="<?php echo esc_attr( $type ); ?>">
2357 <div class="wpforo-ai-box-header">
2358 <h3>
2359 <span class="dashicons dashicons-<?php echo esc_attr( $insight['icon'] ); ?>"></span>
2360 <?php echo esc_html( $insight['label'] ); ?>
2361 </h3>
2362 <span class="wpforo-ai-insights-credits-cost wpforo-ai-insights-credits-cost-premium">
2363 <span class="dashicons dashicons-star-filled"></span>
2364 <?php echo esc_html( $insight['credits'] ); ?> <?php _e( 'credits', 'wpforo' ); ?>
2365 <?php if ( $has_daily_limit ) : ?>
2366 <span class="wpforo-ai-daily-limit-badge"><?php _e( 'Can be refreshed one time per day', 'wpforo' ); ?></span>
2367 <?php endif; ?>
2368 </span>
2369 </div>
2370 <div class="wpforo-ai-box-body">
2371 <p class="wpforo-ai-insights-description"><?php echo esc_html( $insight['description'] ); ?></p>
2372 <?php if ( ! empty( $insight['period'] ) ) : ?>
2373 <p class="wpforo-ai-insights-period">
2374 <span class="dashicons dashicons-calendar-alt"></span>
2375 <?php echo esc_html( $insight['period'] ); ?>
2376 </p>
2377 <?php endif; ?>
2378
2379 <!-- Results Container (hidden until analysis is run) -->
2380 <div class="wpforo-ai-insights-results wpforo-ai-insights-results-full" id="wpforo-ai-insight-results-<?php echo esc_attr( $type ); ?>" style="<?php echo $has_cached ? '' : 'display: none;'; ?>">
2381 <?php if ( $has_cached ) : ?>
2382 <?php wpforo_ai_render_insight_results( $type, $cached_data['data'] ); ?>
2383 <div class="wpforo-ai-insights-cached-notice">
2384 <span class="dashicons dashicons-clock"></span>
2385 <?php
2386 /* translators: %s: time ago */
2387 printf( __( 'Cached %s ago', 'wpforo' ), $cached_time );
2388 ?>
2389 </div>
2390 <?php endif; ?>
2391 </div>
2392
2393 <!-- Loading State -->
2394 <div class="wpforo-ai-insights-loading" style="display: none;">
2395 <span class="spinner is-active"></span>
2396 <span><?php _e( 'Analyzing... This may take a moment for comprehensive analysis.', 'wpforo' ); ?></span>
2397 </div>
2398
2399 <!-- Error Message -->
2400 <div class="wpforo-ai-insights-error" style="display: none;"></div>
2401
2402 <!-- Action Button -->
2403 <div class="wpforo-ai-insights-actions">
2404 <?php
2405 $btn_disabled = $credits_remaining < $insight['credits'] || $used_today;
2406 ?>
2407 <button type="button"
2408 class="button button-primary wpforo-ai-run-insight-btn"
2409 data-insight-type="<?php echo esc_attr( $type ); ?>"
2410 data-credits="<?php echo esc_attr( $insight['credits'] ); ?>"
2411 <?php echo $btn_disabled ? 'disabled' : ''; ?>>
2412 <?php if ( $has_cached ) : ?>
2413 <span class="dashicons dashicons-update"></span>
2414 <?php _e( 'Refresh Analysis', 'wpforo' ); ?>
2415 <?php else : ?>
2416 <span class="dashicons dashicons-yes"></span>
2417 <?php _e( 'Run Analysis', 'wpforo' ); ?>
2418 <?php endif; ?>
2419 </button>
2420 <?php if ( $is_outdated ) : ?>
2421 <span class="wpforo-ai-insights-outdated-notice">
2422 <span class="dashicons dashicons-warning"></span>
2423 <?php _e( 'Data may be outdated. Refresh for current information.', 'wpforo' ); ?>
2424 </span>
2425 <?php endif; ?>
2426 <?php if ( $used_today ) : ?>
2427 <span class="wpforo-ai-insights-daily-used">
2428 <span class="dashicons dashicons-calendar-alt"></span>
2429 <?php _e( 'Available again tomorrow', 'wpforo' ); ?>
2430 </span>
2431 <?php elseif ( $credits_remaining < $insight['credits'] ) : ?>
2432 <span class="wpforo-ai-insights-insufficient">
2433 <?php _e( 'Insufficient credits', 'wpforo' ); ?>
2434 </span>
2435 <?php endif; ?>
2436 </div>
2437 </div>
2438 </div>
2439
2440 <?php endforeach; ?>
2441
2442 <!-- Additional Info Section -->
2443 <div class="wpforo-ai-box wpforo-ai-insights-info-box">
2444 <div class="wpforo-ai-box-body">
2445 <h4><span class="dashicons dashicons-info"></span> <?php _e( 'About AI Insights', 'wpforo' ); ?></h4>
2446 <ul class="wpforo-ai-insights-info-list">
2447 <li><?php _e( 'AI insights analyze your recent forum posts and topics to generate actionable intelligence.', 'wpforo' ); ?></li>
2448 <li><?php _e( 'Results are stored permanently until you click "Refresh" to get updated analysis. A warning will appear after 15 days to indicate data may be outdated.', 'wpforo' ); ?></li>
2449 <li><?php _e( 'The analysis considers the last 100-500 posts depending on forum activity level.', 'wpforo' ); ?></li>
2450 </ul>
2451 </div>
2452 </div>
2453
2454 </div>
2455
2456 <?php
2457 // Output JavaScript data
2458 $js_data = array(
2459 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
2460 'nonce' => wp_create_nonce( 'wpforo_ai_insights_nonce' ),
2461 'boardId' => $board_id,
2462 'creditsRemaining' => $credits_remaining,
2463 'i18n' => array(
2464 'confirmTitle' => __( 'Run AI Analysis', 'wpforo' ),
2465 'confirmMessage' => __( 'This will use %d credit(s) from your balance.', 'wpforo' ),
2466 'confirmButton' => __( 'Run Analysis', 'wpforo' ),
2467 'cancelButton' => __( 'Cancel', 'wpforo' ),
2468 'analyzing' => __( 'Analyzing...', 'wpforo' ),
2469 'error' => __( 'An error occurred. Please try again.', 'wpforo' ),
2470 'insufficientCredits' => __( 'Insufficient credits to run this analysis.', 'wpforo' ),
2471 'cachedJustNow' => __( 'Just now', 'wpforo' ),
2472 ),
2473 );
2474 ?>
2475 <script type="text/javascript">
2476 var wpforoAIInsights = <?php echo wp_json_encode( $js_data ); ?>;
2477 </script>
2478 <?php
2479 }
2480
2481 /**
2482 * Render insight results based on type
2483 *
2484 * @param string $type Insight type
2485 * @param array $data Result data
2486 */
2487 function wpforo_ai_render_insight_results( $type, $data ) {
2488 switch ( $type ) {
2489 case 'sentiment':
2490 wpforo_ai_render_sentiment_results( $data );
2491 break;
2492 case 'trending':
2493 wpforo_ai_render_trending_results( $data );
2494 break;
2495 case 'recommendations':
2496 wpforo_ai_render_recommendations_results( $data );
2497 break;
2498 case 'deep_analysis':
2499 wpforo_ai_render_deep_analysis_results( $data );
2500 break;
2501 case 'sentiment_trend':
2502 wpforo_ai_render_sentiment_trend_results( $data );
2503 break;
2504 }
2505 }
2506
2507 /**
2508 * Render sentiment analysis results with 7 emotion categories
2509 *
2510 * @param array $data Sentiment data
2511 */
2512 function wpforo_ai_render_sentiment_results( $data ) {
2513 // Define 7 emotion categories with their display properties
2514 $emotions = array(
2515 'happy' => array( 'emoji' => '😊', 'label' => __( 'Happy', 'wpforo' ), 'color' => '#4CAF50' ),
2516 'excited' => array( 'emoji' => '🎉', 'label' => __( 'Excited', 'wpforo' ), 'color' => '#8BC34A' ),
2517 'neutral' => array( 'emoji' => '😐', 'label' => __( 'Neutral', 'wpforo' ), 'color' => '#9E9E9E' ),
2518 'confused' => array( 'emoji' => '🤔', 'label' => __( 'Confused', 'wpforo' ), 'color' => '#FF9800' ),
2519 'frustrated' => array( 'emoji' => '😤', 'label' => __( 'Frustrated', 'wpforo' ), 'color' => '#FF5722' ),
2520 'angry' => array( 'emoji' => '😠', 'label' => __( 'Angry', 'wpforo' ), 'color' => '#F44336' ),
2521 'sad' => array( 'emoji' => '😢', 'label' => __( 'Sad', 'wpforo' ), 'color' => '#2196F3' ),
2522 );
2523
2524 // Backwards compatibility: if old format (positive/neutral/negative), convert
2525 if ( isset( $data['positive'] ) && ! isset( $data['happy'] ) ) {
2526 $data['happy'] = isset( $data['positive'] ) ? (int) $data['positive'] : 0;
2527 $data['neutral'] = isset( $data['neutral'] ) ? (int) $data['neutral'] : 0;
2528 $data['angry'] = isset( $data['negative'] ) ? (int) $data['negative'] : 0;
2529 $data['excited'] = 0;
2530 $data['confused'] = 0;
2531 $data['frustrated'] = 0;
2532 $data['sad'] = 0;
2533 }
2534
2535 $summary = isset( $data['summary'] ) ? sanitize_text_field( $data['summary'] ) : '';
2536 ?>
2537 <div class="wpforo-ai-sentiment-results wpforo-ai-sentiment-results-7">
2538 <div class="wpforo-ai-sentiment-bars">
2539 <?php foreach ( $emotions as $key => $emotion ) :
2540 $value = isset( $data[ $key ] ) ? (int) $data[ $key ] : 0;
2541 if ( $value > 0 ) : // Only show emotions with values > 0
2542 ?>
2543 <div class="wpforo-ai-sentiment-bar <?php echo esc_attr( $key ); ?>">
2544 <span class="wpforo-ai-sentiment-emoji"><?php echo esc_html( $emotion['emoji'] ); ?></span>
2545 <span class="wpforo-ai-sentiment-label"><?php echo esc_html( $emotion['label'] ); ?></span>
2546 <div class="wpforo-ai-sentiment-bar-track">
2547 <div class="wpforo-ai-sentiment-bar-fill" style="width: <?php echo esc_attr( $value ); ?>%; background-color: <?php echo esc_attr( $emotion['color'] ); ?>;"></div>
2548 </div>
2549 <span class="wpforo-ai-sentiment-percent"><?php echo esc_html( $value ); ?>%</span>
2550 </div>
2551 <?php endif; ?>
2552 <?php endforeach; ?>
2553 </div>
2554 <?php if ( $summary ) : ?>
2555 <div class="wpforo-ai-sentiment-summary">
2556 <p><?php echo esc_html( $summary ); ?></p>
2557 </div>
2558 <?php endif; ?>
2559 </div>
2560 <?php
2561 }
2562
2563 /**
2564 * Render trending topics results
2565 *
2566 * @param array $data Trending data
2567 */
2568 function wpforo_ai_render_trending_results( $data ) {
2569 $topics = isset( $data['topics'] ) && is_array( $data['topics'] ) ? $data['topics'] : [];
2570 $summary = isset( $data['summary'] ) ? sanitize_text_field( $data['summary'] ) : '';
2571 ?>
2572 <div class="wpforo-ai-trending-results">
2573 <?php if ( ! empty( $topics ) ) : ?>
2574 <ol class="wpforo-ai-trending-list">
2575 <?php foreach ( $topics as $index => $topic ) :
2576 $topic_title = isset( $topic['title'] ) ? $topic['title'] : ( is_string( $topic ) ? $topic : '' );
2577 $topic_heat = isset( $topic['heat'] ) ? (int) $topic['heat'] : 0;
2578 ?>
2579 <li class="wpforo-ai-trending-item">
2580 <span class="wpforo-ai-trending-rank"><?php echo $index + 1; ?></span>
2581 <span class="wpforo-ai-trending-title"><?php echo esc_html( $topic_title ); ?></span>
2582 <?php if ( $topic_heat > 0 ) : ?>
2583 <span class="wpforo-ai-trending-heat" title="<?php esc_attr_e( 'Heat score', 'wpforo' ); ?>">
2584 🔥 <?php echo esc_html( $topic_heat ); ?>
2585 </span>
2586 <?php endif; ?>
2587 </li>
2588 <?php endforeach; ?>
2589 </ol>
2590 <?php else : ?>
2591 <p class="wpforo-ai-no-data"><?php _e( 'No trending topics detected.', 'wpforo' ); ?></p>
2592 <?php endif; ?>
2593 <?php if ( $summary ) : ?>
2594 <div class="wpforo-ai-trending-summary">
2595 <p><?php echo esc_html( $summary ); ?></p>
2596 </div>
2597 <?php endif; ?>
2598 </div>
2599 <?php
2600 }
2601
2602 /**
2603 * Render growth recommendations results
2604 *
2605 * @param array $data Recommendations data
2606 */
2607 function wpforo_ai_render_recommendations_results( $data ) {
2608 $recommendations = isset( $data['recommendations'] ) && is_array( $data['recommendations'] ) ? $data['recommendations'] : [];
2609 $summary = isset( $data['summary'] ) ? sanitize_text_field( $data['summary'] ) : '';
2610 ?>
2611 <div class="wpforo-ai-recommendations-results">
2612 <?php if ( ! empty( $recommendations ) ) : ?>
2613 <ul class="wpforo-ai-recommendations-list">
2614 <?php foreach ( $recommendations as $rec ) :
2615 $rec_title = isset( $rec['title'] ) ? $rec['title'] : ( is_string( $rec ) ? $rec : '' );
2616 $rec_desc = isset( $rec['description'] ) ? $rec['description'] : '';
2617 $rec_priority = isset( $rec['priority'] ) ? strtolower( $rec['priority'] ) : 'medium';
2618 ?>
2619 <li class="wpforo-ai-recommendation-item priority-<?php echo esc_attr( $rec_priority ); ?>">
2620 <div class="wpforo-ai-recommendation-icon">
2621 <span class="dashicons dashicons-lightbulb"></span>
2622 </div>
2623 <div class="wpforo-ai-recommendation-content">
2624 <strong><?php echo esc_html( $rec_title ); ?></strong>
2625 <?php if ( $rec_desc ) : ?>
2626 <p><?php echo esc_html( $rec_desc ); ?></p>
2627 <?php endif; ?>
2628 </div>
2629 <?php if ( $rec_priority !== 'medium' ) : ?>
2630 <span class="wpforo-ai-recommendation-priority">
2631 <?php echo esc_html( ucfirst( $rec_priority ) ); ?>
2632 </span>
2633 <?php endif; ?>
2634 </li>
2635 <?php endforeach; ?>
2636 </ul>
2637 <?php else : ?>
2638 <p class="wpforo-ai-no-data"><?php _e( 'No recommendations available.', 'wpforo' ); ?></p>
2639 <?php endif; ?>
2640 <?php if ( $summary ) : ?>
2641 <div class="wpforo-ai-recommendations-summary">
2642 <p><?php echo esc_html( $summary ); ?></p>
2643 </div>
2644 <?php endif; ?>
2645 </div>
2646 <?php
2647 }
2648
2649 /**
2650 * Render deep analysis results (Community Deep Dive)
2651 *
2652 * @param array $data Deep analysis data
2653 */
2654 function wpforo_ai_render_deep_analysis_results( $data ) {
2655 $user_engagement = isset( $data['user_engagement'] ) ? $data['user_engagement'] : array();
2656 $content_metrics = isset( $data['content_metrics'] ) ? $data['content_metrics'] : array();
2657 $keyword_hotspots = isset( $data['keyword_hotspots'] ) && is_array( $data['keyword_hotspots'] ) ? $data['keyword_hotspots'] : array();
2658 $contributors_mood = isset( $data['top_contributors_mood'] ) ? $data['top_contributors_mood'] : array();
2659 $activity_patterns = isset( $data['activity_patterns'] ) ? $data['activity_patterns'] : array();
2660 $overall_summary = isset( $data['overall_summary'] ) ? sanitize_text_field( $data['overall_summary'] ) : '';
2661 ?>
2662 <div class="wpforo-ai-deep-analysis-results">
2663 <!-- User Engagement Section -->
2664 <div class="wpforo-ai-deep-section">
2665 <h4><span class="dashicons dashicons-groups"></span> <?php _e( 'User Engagement', 'wpforo' ); ?></h4>
2666 <div class="wpforo-ai-deep-metrics">
2667 <div class="wpforo-ai-metric">
2668 <span class="wpforo-ai-metric-value"><?php echo esc_html( number_format( floatval( $user_engagement['avg_replies_per_user'] ?? 0 ), 1 ) ); ?></span>
2669 <span class="wpforo-ai-metric-label"><?php _e( 'Avg Replies/User', 'wpforo' ); ?></span>
2670 </div>
2671 <div class="wpforo-ai-metric">
2672 <span class="wpforo-ai-metric-value"><?php echo esc_html( intval( $user_engagement['active_users_percent'] ?? 0 ) ); ?>%</span>
2673 <span class="wpforo-ai-metric-label"><?php _e( 'Active Users', 'wpforo' ); ?></span>
2674 </div>
2675 <div class="wpforo-ai-metric">
2676 <span class="wpforo-ai-metric-value"><?php echo esc_html( number_format( floatval( $user_engagement['avg_response_time_hours'] ?? 0 ), 1 ) ); ?>h</span>
2677 <span class="wpforo-ai-metric-label"><?php _e( 'Avg Response Time', 'wpforo' ); ?></span>
2678 </div>
2679 </div>
2680 <?php if ( ! empty( $user_engagement['top_repliers'] ) ) : ?>
2681 <div class="wpforo-ai-top-repliers">
2682 <strong><?php _e( 'Top Repliers:', 'wpforo' ); ?></strong>
2683 <?php foreach ( array_slice( $user_engagement['top_repliers'], 0, 5 ) as $replier ) : ?>
2684 <span class="wpforo-ai-replier-badge sentiment-<?php echo esc_attr( strtolower( $replier['sentiment'] ?? 'neutral' ) ); ?>">
2685 <?php echo esc_html( $replier['username'] ?? 'Unknown' ); ?> (<?php echo esc_html( $replier['reply_count'] ?? 0 ); ?>)
2686 </span>
2687 <?php endforeach; ?>
2688 </div>
2689 <?php endif; ?>
2690 <?php if ( ! empty( $user_engagement['summary'] ) ) : ?>
2691 <p class="wpforo-ai-section-summary"><?php echo esc_html( $user_engagement['summary'] ); ?></p>
2692 <?php endif; ?>
2693 </div>
2694
2695 <!-- Content Metrics Section -->
2696 <div class="wpforo-ai-deep-section">
2697 <h4><span class="dashicons dashicons-media-document"></span> <?php _e( 'Content Metrics', 'wpforo' ); ?></h4>
2698 <div class="wpforo-ai-deep-metrics">
2699 <div class="wpforo-ai-metric">
2700 <span class="wpforo-ai-metric-value"><?php echo esc_html( intval( $content_metrics['avg_topic_length_words'] ?? 0 ) ); ?></span>
2701 <span class="wpforo-ai-metric-label"><?php _e( 'Avg Topic Length', 'wpforo' ); ?></span>
2702 </div>
2703 <div class="wpforo-ai-metric">
2704 <span class="wpforo-ai-metric-value"><?php echo esc_html( intval( $content_metrics['avg_reply_length_words'] ?? 0 ) ); ?></span>
2705 <span class="wpforo-ai-metric-label"><?php _e( 'Avg Reply Length', 'wpforo' ); ?></span>
2706 </div>
2707 <div class="wpforo-ai-metric">
2708 <span class="wpforo-ai-metric-value"><?php echo esc_html( intval( $content_metrics['detailed_discussions_percent'] ?? 0 ) ); ?>%</span>
2709 <span class="wpforo-ai-metric-label"><?php _e( 'Detailed Discussions', 'wpforo' ); ?></span>
2710 </div>
2711 </div>
2712 <?php if ( ! empty( $content_metrics['summary'] ) ) : ?>
2713 <p class="wpforo-ai-section-summary"><?php echo esc_html( $content_metrics['summary'] ); ?></p>
2714 <?php endif; ?>
2715 </div>
2716
2717 <!-- Keyword Hotspots Section -->
2718 <?php if ( ! empty( $keyword_hotspots ) ) : ?>
2719 <div class="wpforo-ai-deep-section">
2720 <h4><span class="dashicons dashicons-tag"></span> <?php _e( 'Keyword Sentiment Hotspots', 'wpforo' ); ?></h4>
2721 <div class="wpforo-ai-keyword-hotspots">
2722 <?php foreach ( array_slice( $keyword_hotspots, 0, 10 ) as $hotspot ) :
2723 $heat = isset( $hotspot['heat'] ) ? intval( $hotspot['heat'] ) : 50;
2724 $sentiment = strtolower( $hotspot['sentiment'] ?? 'neutral' );
2725 $sentiment_colors = array(
2726 'positive' => '#4CAF50',
2727 'negative' => '#F44336',
2728 'mixed' => '#FF9800',
2729 'neutral' => '#9E9E9E',
2730 );
2731 $color = isset( $sentiment_colors[ $sentiment ] ) ? $sentiment_colors[ $sentiment ] : '#9E9E9E';
2732 ?>
2733 <div class="wpforo-ai-keyword-tag" style="border-color: <?php echo esc_attr( $color ); ?>; background: <?php echo esc_attr( $color ); ?>20;">
2734 <span class="wpforo-ai-keyword-text"><?php echo esc_html( $hotspot['keyword'] ?? '' ); ?></span>
2735 <span class="wpforo-ai-keyword-heat" style="background: <?php echo esc_attr( $color ); ?>;">
2736 <?php echo esc_html( $heat ); ?>
2737 </span>
2738 </div>
2739 <?php endforeach; ?>
2740 </div>
2741 </div>
2742 <?php endif; ?>
2743
2744 <!-- Top Contributors Mood Section -->
2745 <div class="wpforo-ai-deep-section">
2746 <h4><span class="dashicons dashicons-admin-users"></span> <?php _e( 'Top Contributors Mood', 'wpforo' ); ?></h4>
2747 <p class="wpforo-ai-section-note"><?php _e( 'Sentiment analysis of your most active community members (excluding admins/moderators).', 'wpforo' ); ?></p>
2748 <?php
2749 $overall_sentiment = strtolower( $contributors_mood['overall_sentiment'] ?? 'neutral' );
2750 $positive_pct = intval( $contributors_mood['positive_percent'] ?? 0 );
2751 $negative_pct = intval( $contributors_mood['negative_percent'] ?? 0 );
2752 $neutral_pct = max( 0, 100 - $positive_pct - $negative_pct );
2753 ?>
2754 <div class="wpforo-ai-contributors-mood-grid">
2755 <!-- Positive -->
2756 <div class="wpforo-ai-mood-stat positive">
2757 <div class="wpforo-ai-mood-stat-icon">😊</div>
2758 <div class="wpforo-ai-mood-stat-info">
2759 <div class="wpforo-ai-mood-stat-value"><?php echo esc_html( $positive_pct ); ?>%</div>
2760 <div class="wpforo-ai-mood-stat-label"><?php _e( 'Positive', 'wpforo' ); ?></div>
2761 </div>
2762 <div class="wpforo-ai-mood-stat-bar">
2763 <div class="wpforo-ai-mood-stat-bar-fill positive" style="width: <?php echo esc_attr( $positive_pct ); ?>%;"></div>
2764 </div>
2765 </div>
2766 <!-- Neutral -->
2767 <div class="wpforo-ai-mood-stat neutral">
2768 <div class="wpforo-ai-mood-stat-icon">😐</div>
2769 <div class="wpforo-ai-mood-stat-info">
2770 <div class="wpforo-ai-mood-stat-value"><?php echo esc_html( $neutral_pct ); ?>%</div>
2771 <div class="wpforo-ai-mood-stat-label"><?php _e( 'Neutral', 'wpforo' ); ?></div>
2772 </div>
2773 <div class="wpforo-ai-mood-stat-bar">
2774 <div class="wpforo-ai-mood-stat-bar-fill neutral" style="width: <?php echo esc_attr( $neutral_pct ); ?>%;"></div>
2775 </div>
2776 </div>
2777 <!-- Negative -->
2778 <div class="wpforo-ai-mood-stat negative">
2779 <div class="wpforo-ai-mood-stat-icon">😟</div>
2780 <div class="wpforo-ai-mood-stat-info">
2781 <div class="wpforo-ai-mood-stat-value"><?php echo esc_html( $negative_pct ); ?>%</div>
2782 <div class="wpforo-ai-mood-stat-label"><?php _e( 'Negative', 'wpforo' ); ?></div>
2783 </div>
2784 <div class="wpforo-ai-mood-stat-bar">
2785 <div class="wpforo-ai-mood-stat-bar-fill negative" style="width: <?php echo esc_attr( $negative_pct ); ?>%;"></div>
2786 </div>
2787 </div>
2788 </div>
2789 <?php if ( ! empty( $contributors_mood['influence_summary'] ) ) : ?>
2790 <div class="wpforo-ai-mood-influence">
2791 <strong><?php _e( 'Impact:', 'wpforo' ); ?></strong>
2792 <?php echo esc_html( $contributors_mood['influence_summary'] ); ?>
2793 </div>
2794 <?php endif; ?>
2795 </div>
2796
2797 <!-- Activity Patterns Section -->
2798 <div class="wpforo-ai-deep-section">
2799 <h4><span class="dashicons dashicons-clock"></span> <?php _e( 'Activity Patterns', 'wpforo' ); ?></h4>
2800 <div class="wpforo-ai-activity-patterns">
2801 <?php if ( ! empty( $activity_patterns['peak_hours'] ) ) : ?>
2802 <div class="wpforo-ai-pattern-item">
2803 <span class="wpforo-ai-pattern-label"><?php _e( 'Peak Hours:', 'wpforo' ); ?></span>
2804 <span class="wpforo-ai-pattern-value"><?php echo esc_html( implode( ', ', (array) $activity_patterns['peak_hours'] ) ); ?></span>
2805 </div>
2806 <?php endif; ?>
2807 <?php if ( ! empty( $activity_patterns['peak_days'] ) ) : ?>
2808 <div class="wpforo-ai-pattern-item">
2809 <span class="wpforo-ai-pattern-label"><?php _e( 'Peak Days:', 'wpforo' ); ?></span>
2810 <span class="wpforo-ai-pattern-value"><?php echo esc_html( implode( ', ', (array) $activity_patterns['peak_days'] ) ); ?></span>
2811 </div>
2812 <?php endif; ?>
2813 <?php if ( ! empty( $activity_patterns['trend'] ) ) : ?>
2814 <div class="wpforo-ai-pattern-item">
2815 <span class="wpforo-ai-pattern-label"><?php _e( 'Trend:', 'wpforo' ); ?></span>
2816 <?php
2817 $trend = strtolower( $activity_patterns['trend'] );
2818 $trend_icon = '';
2819 $trend_class = '';
2820 if ( $trend === 'increasing' ) {
2821 $trend_icon = '📈';
2822 $trend_class = 'trend-up';
2823 } elseif ( $trend === 'decreasing' ) {
2824 $trend_icon = '📉';
2825 $trend_class = 'trend-down';
2826 } else {
2827 $trend_icon = '➡️';
2828 $trend_class = 'trend-stable';
2829 }
2830 ?>
2831 <span class="wpforo-ai-pattern-value <?php echo esc_attr( $trend_class ); ?>">
2832 <?php echo esc_html( $trend_icon . ' ' . ucfirst( $trend ) ); ?>
2833 </span>
2834 </div>
2835 <?php endif; ?>
2836 </div>
2837 <?php if ( ! empty( $activity_patterns['summary'] ) ) : ?>
2838 <p class="wpforo-ai-section-summary"><?php echo esc_html( $activity_patterns['summary'] ); ?></p>
2839 <?php endif; ?>
2840 </div>
2841
2842 <!-- Overall Summary -->
2843 <?php if ( $overall_summary ) : ?>
2844 <div class="wpforo-ai-deep-overall-summary">
2845 <p><?php echo esc_html( $overall_summary ); ?></p>
2846 </div>
2847 <?php endif; ?>
2848 </div>
2849 <?php
2850 }
2851
2852 /**
2853 * Render sentiment trend results (Sentiment Trend Over Time)
2854 *
2855 * @param array $data Sentiment trend data
2856 */
2857 function wpforo_ai_render_sentiment_trend_results( $data ) {
2858 $trend_direction = isset( $data['trend_direction'] ) ? sanitize_text_field( $data['trend_direction'] ) : 'stable';
2859 $trend_strength = isset( $data['trend_strength'] ) ? intval( $data['trend_strength'] ) : 5;
2860 $time_series = isset( $data['time_series'] ) && is_array( $data['time_series'] ) ? $data['time_series'] : array();
2861 $mood_shifts = isset( $data['mood_shifts'] ) && is_array( $data['mood_shifts'] ) ? $data['mood_shifts'] : array();
2862 $peaks = isset( $data['peaks'] ) ? $data['peaks'] : array();
2863 $patterns = isset( $data['patterns'] ) ? $data['patterns'] : array();
2864 $summary = isset( $data['summary'] ) ? sanitize_text_field( $data['summary'] ) : '';
2865 $forecast = isset( $data['forecast'] ) ? sanitize_text_field( $data['forecast'] ) : '';
2866
2867 /**
2868 * Format date from "2024-12-21" to "21st Dec 2024" format
2869 *
2870 * @param string $date_str Date string in Y-m-d format
2871 * @param bool $short If true, returns short format like "21 Dec"
2872 * @return string Formatted date
2873 */
2874 $format_date = function( $date_str, $short = false ) {
2875 if ( empty( $date_str ) || $date_str === 'N/A' ) {
2876 return $date_str;
2877 }
2878 $timestamp = strtotime( $date_str );
2879 if ( ! $timestamp ) {
2880 return $date_str;
2881 }
2882 $day = date( 'j', $timestamp );
2883 // Add ordinal suffix (st, nd, rd, th)
2884 $suffix = 'th';
2885 if ( ! in_array( ( $day % 100 ), array( 11, 12, 13 ) ) ) {
2886 switch ( $day % 10 ) {
2887 case 1: $suffix = 'st'; break;
2888 case 2: $suffix = 'nd'; break;
2889 case 3: $suffix = 'rd'; break;
2890 }
2891 }
2892 if ( $short ) {
2893 return $day . ' ' . date( 'M', $timestamp );
2894 }
2895 return $day . $suffix . ' ' . date( 'M Y', $timestamp );
2896 };
2897
2898 // Trend direction SVG icons and colors
2899 $trend_icons = array(
2900 'improving' => array(
2901 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg>',
2902 'color' => '#4CAF50',
2903 'label' => __( 'Improving', 'wpforo' ),
2904 ),
2905 'declining' => array(
2906 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 18 13.5 8.5 8.5 13.5 1 6"></polyline><polyline points="17 18 23 18 23 12"></polyline></svg>',
2907 'color' => '#F44336',
2908 'label' => __( 'Declining', 'wpforo' ),
2909 ),
2910 'stable' => array(
2911 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>',
2912 'color' => '#9E9E9E',
2913 'label' => __( 'Stable', 'wpforo' ),
2914 ),
2915 'volatile' => array(
2916 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>',
2917 'color' => '#FF9800',
2918 'label' => __( 'Volatile', 'wpforo' ),
2919 ),
2920 );
2921 $trend_info = isset( $trend_icons[ $trend_direction ] ) ? $trend_icons[ $trend_direction ] : $trend_icons['stable'];
2922 ?>
2923 <div class="wpforo-ai-sentiment-trend-results">
2924 <!-- Trend Overview -->
2925 <div class="wpforo-ai-trend-overview">
2926 <div class="wpforo-ai-trend-direction" style="border-color: <?php echo esc_attr( $trend_info['color'] ); ?>;">
2927 <span class="wpforo-ai-trend-icon" style="color: <?php echo esc_attr( $trend_info['color'] ); ?>;"><?php echo $trend_info['icon']; ?></span>
2928 <span class="wpforo-ai-trend-label"><?php echo esc_html( $trend_info['label'] ); ?></span>
2929 <div class="wpforo-ai-trend-strength">
2930 <span class="wpforo-ai-strength-label"><?php _e( 'Strength:', 'wpforo' ); ?></span>
2931 <div class="wpforo-ai-strength-bar">
2932 <div class="wpforo-ai-strength-fill" style="width: <?php echo esc_attr( $trend_strength * 10 ); ?>%; background: <?php echo esc_attr( $trend_info['color'] ); ?>;"></div>
2933 </div>
2934 <span class="wpforo-ai-strength-value"><?php echo esc_html( $trend_strength ); ?>/10</span>
2935 </div>
2936 </div>
2937
2938 <!-- Peaks Info -->
2939 <?php if ( ! empty( $peaks ) ) : ?>
2940 <div class="wpforo-ai-trend-peaks">
2941 <?php if ( ! empty( $peaks['most_positive_period'] ) && $peaks['most_positive_period'] !== 'N/A' ) : ?>
2942 <div class="wpforo-ai-peak-item positive">
2943 <span class="wpforo-ai-peak-emoji">😊</span>
2944 <span class="wpforo-ai-peak-label"><?php _e( 'Most Positive:', 'wpforo' ); ?></span>
2945 <span class="wpforo-ai-peak-value"><?php echo esc_html( $format_date( $peaks['most_positive_period'] ) ); ?></span>
2946 </div>
2947 <?php endif; ?>
2948 <?php if ( ! empty( $peaks['most_negative_period'] ) && $peaks['most_negative_period'] !== 'N/A' ) : ?>
2949 <div class="wpforo-ai-peak-item negative">
2950 <span class="wpforo-ai-peak-emoji">😟</span>
2951 <span class="wpforo-ai-peak-label"><?php _e( 'Most Negative:', 'wpforo' ); ?></span>
2952 <span class="wpforo-ai-peak-value"><?php echo esc_html( $format_date( $peaks['most_negative_period'] ) ); ?></span>
2953 </div>
2954 <?php endif; ?>
2955 <?php if ( ! empty( $peaks['most_active_period'] ) && $peaks['most_active_period'] !== 'N/A' ) : ?>
2956 <div class="wpforo-ai-peak-item active">
2957 <span class="wpforo-ai-peak-emoji">🔥</span>
2958 <span class="wpforo-ai-peak-label"><?php _e( 'Most Active:', 'wpforo' ); ?></span>
2959 <span class="wpforo-ai-peak-value"><?php echo esc_html( $format_date( $peaks['most_active_period'] ) ); ?></span>
2960 </div>
2961 <?php endif; ?>
2962 </div>
2963 <?php endif; ?>
2964 </div>
2965
2966 <!-- Time Series Chart -->
2967 <?php if ( ! empty( $time_series ) ) : ?>
2968 <div class="wpforo-ai-trend-chart-container">
2969 <h4><?php _e( 'Sentiment Over Time', 'wpforo' ); ?> <span class="wpforo-ai-chart-subtitle">(<?php _e( 'posts per day by dominant mood', 'wpforo' ); ?>)</span></h4>
2970 <div class="wpforo-ai-trend-chart" id="wpforo-sentiment-trend-chart">
2971 <?php
2972 $max_posts = max( array_column( $time_series, 'post_count' ) );
2973 $max_posts = max( 1, $max_posts ); // Avoid division by zero
2974 $emotion_colors = array(
2975 'happy' => '#4CAF50',
2976 'excited' => '#8BC34A',
2977 'neutral' => '#9E9E9E',
2978 'confused' => '#FF9800',
2979 'frustrated' => '#FF5722',
2980 'angry' => '#F44336',
2981 'sad' => '#2196F3',
2982 );
2983 $emotion_emojis = array(
2984 'happy' => '😊',
2985 'excited' => '🎉',
2986 'neutral' => '😐',
2987 'confused' => '🤔',
2988 'frustrated' => '😤',
2989 'angry' => '😠',
2990 'sad' => '😢',
2991 );
2992 ?>
2993 <!-- Y-axis labels -->
2994 <div class="wpforo-ai-chart-y-axis">
2995 <span class="wpforo-ai-chart-y-label"><?php echo esc_html( $max_posts ); ?></span>
2996 <span class="wpforo-ai-chart-y-label"><?php echo esc_html( intval( $max_posts * 0.75 ) ); ?></span>
2997 <span class="wpforo-ai-chart-y-label"><?php echo esc_html( intval( $max_posts * 0.5 ) ); ?></span>
2998 <span class="wpforo-ai-chart-y-label"><?php echo esc_html( intval( $max_posts * 0.25 ) ); ?></span>
2999 <span class="wpforo-ai-chart-y-label">0</span>
3000 </div>
3001 <!-- Chart area with grid lines -->
3002 <div class="wpforo-ai-chart-area">
3003 <div class="wpforo-ai-chart-bars">
3004 <?php foreach ( $time_series as $period ) :
3005 $dominant = strtolower( $period['dominant_emotion'] ?? 'neutral' );
3006 $post_count = intval( $period['post_count'] ?? 0 );
3007 $bar_height = ( $post_count / $max_posts ) * 100;
3008 $color = isset( $emotion_colors[ $dominant ] ) ? $emotion_colors[ $dominant ] : '#9E9E9E';
3009 $emoji = isset( $emotion_emojis[ $dominant ] ) ? $emotion_emojis[ $dominant ] : '😐';
3010 $period_date = $period['period'] ?? '';
3011 $formatted_full = $format_date( $period_date );
3012 $formatted_short = $format_date( $period_date, true );
3013 ?>
3014 <div class="wpforo-ai-chart-bar-wrapper" title="<?php echo esc_attr( $formatted_full ); ?>: <?php echo esc_attr( $post_count ); ?> posts (<?php echo esc_attr( ucfirst( $dominant ) ); ?>)">
3015 <div class="wpforo-ai-chart-bar" style="height: <?php echo esc_attr( $bar_height ); ?>%; background: <?php echo esc_attr( $color ); ?>;" data-emoji="<?php echo esc_attr( $emoji ); ?>"></div>
3016 </div>
3017 <?php endforeach; ?>
3018 </div>
3019 </div>
3020 <!-- X-axis labels -->
3021 <div class="wpforo-ai-chart-x-axis">
3022 <?php foreach ( $time_series as $period ) : ?>
3023 <span class="wpforo-ai-chart-label"><?php echo esc_html( $format_date( $period['period'] ?? '', true ) ); ?></span>
3024 <?php endforeach; ?>
3025 </div>
3026 </div>
3027 <div class="wpforo-ai-chart-legend">
3028 <?php
3029 $legend_emotions = array(
3030 'happy' => array( 'emoji' => '😊', 'label' => __( 'Happy', 'wpforo' ), 'color' => '#4CAF50' ),
3031 'excited' => array( 'emoji' => '🎉', 'label' => __( 'Excited', 'wpforo' ), 'color' => '#8BC34A' ),
3032 'neutral' => array( 'emoji' => '😐', 'label' => __( 'Neutral', 'wpforo' ), 'color' => '#9E9E9E' ),
3033 'confused' => array( 'emoji' => '🤔', 'label' => __( 'Confused', 'wpforo' ), 'color' => '#FF9800' ),
3034 'frustrated' => array( 'emoji' => '😤', 'label' => __( 'Frustrated', 'wpforo' ), 'color' => '#FF5722' ),
3035 'angry' => array( 'emoji' => '😠', 'label' => __( 'Angry', 'wpforo' ), 'color' => '#F44336' ),
3036 'sad' => array( 'emoji' => '😢', 'label' => __( 'Sad', 'wpforo' ), 'color' => '#2196F3' ),
3037 );
3038 foreach ( $legend_emotions as $key => $emotion ) : ?>
3039 <span class="wpforo-ai-legend-item">
3040 <span class="wpforo-ai-legend-color" style="background: <?php echo esc_attr( $emotion['color'] ); ?>;"></span>
3041 <?php echo esc_html( $emotion['emoji'] . ' ' . $emotion['label'] ); ?>
3042 </span>
3043 <?php endforeach; ?>
3044 </div>
3045 </div>
3046 <?php endif; ?>
3047
3048 <!-- Mood Shifts -->
3049 <?php if ( ! empty( $mood_shifts ) ) : ?>
3050 <div class="wpforo-ai-mood-shifts">
3051 <h4><?php _e( 'Significant Mood Shifts', 'wpforo' ); ?></h4>
3052 <div class="wpforo-ai-shifts-list">
3053 <?php foreach ( array_slice( $mood_shifts, 0, 5 ) as $shift ) :
3054 $magnitude = strtolower( $shift['magnitude'] ?? 'minor' );
3055 $magnitude_class = 'magnitude-' . $magnitude;
3056 ?>
3057 <div class="wpforo-ai-shift-item <?php echo esc_attr( $magnitude_class ); ?>">
3058 <div class="wpforo-ai-shift-header">
3059 <span class="wpforo-ai-shift-period"><?php echo esc_html( $format_date( $shift['period'] ?? '' ) ); ?></span>
3060 <span class="wpforo-ai-shift-magnitude"><?php echo esc_html( ucfirst( $magnitude ) ); ?></span>
3061 </div>
3062 <div class="wpforo-ai-shift-transition">
3063 <span class="wpforo-ai-shift-from"><?php echo esc_html( ucfirst( $shift['from_mood'] ?? '' ) ); ?></span>
3064 <span class="wpforo-ai-shift-arrow"></span>
3065 <span class="wpforo-ai-shift-to"><?php echo esc_html( ucfirst( $shift['to_mood'] ?? '' ) ); ?></span>
3066 </div>
3067 <?php if ( ! empty( $shift['possible_cause'] ) ) : ?>
3068 <p class="wpforo-ai-shift-cause"><?php echo esc_html( $shift['possible_cause'] ); ?></p>
3069 <?php endif; ?>
3070 </div>
3071 <?php endforeach; ?>
3072 </div>
3073 </div>
3074 <?php endif; ?>
3075
3076 <!-- Patterns -->
3077 <?php if ( ! empty( $patterns ) && ! empty( $patterns['pattern_description'] ) ) : ?>
3078 <div class="wpforo-ai-trend-patterns">
3079 <h4><?php _e( 'Detected Patterns', 'wpforo' ); ?></h4>
3080 <div class="wpforo-ai-pattern-info">
3081 <?php if ( ! empty( $patterns['cyclical'] ) ) : ?>
3082 <span class="wpforo-ai-cyclical-badge"><?php _e( '🔄 Cyclical Pattern Detected', 'wpforo' ); ?></span>
3083 <?php endif; ?>
3084 <p><?php echo esc_html( $patterns['pattern_description'] ); ?></p>
3085 </div>
3086 </div>
3087 <?php endif; ?>
3088
3089 <!-- Summary and Forecast -->
3090 <div class="wpforo-ai-trend-summary-section">
3091 <?php if ( $summary ) : ?>
3092 <div class="wpforo-ai-trend-summary">
3093 <h4><?php _e( 'Summary', 'wpforo' ); ?></h4>
3094 <p><?php echo esc_html( $summary ); ?></p>
3095 </div>
3096 <?php endif; ?>
3097 <?php if ( $forecast ) : ?>
3098 <div class="wpforo-ai-trend-forecast">
3099 <h4><span class="dashicons dashicons-chart-line"></span> <?php _e( 'Forecast', 'wpforo' ); ?></h4>
3100 <p><?php echo esc_html( $forecast ); ?></p>
3101 </div>
3102 <?php endif; ?>
3103 </div>
3104 </div>
3105 <?php
3106 }
3107
3108 /**
3109 * Get cached AI insights
3110 *
3111 * Uses WordPress options for permanent storage (not transients which can be cleared by cache plugins).
3112 * Each insight type is stored separately with board ID suffix for better organization.
3113 *
3114 * @param int $board_id Board ID
3115 * @return array Cached insights keyed by type
3116 */
3117 function wpforo_ai_get_cached_insights( $board_id ) {
3118 $insight_types = array( 'sentiment', 'trending', 'deep_analysis', 'sentiment_trend', 'recommendations' );
3119 $cached = array();
3120
3121 foreach ( $insight_types as $type ) {
3122 $option_key = 'wpforo_ai_insight_' . $type . '_' . intval( $board_id );
3123 $data = get_option( $option_key, false );
3124
3125 if ( false !== $data && is_array( $data ) ) {
3126 $cached[ $type ] = $data;
3127 }
3128 }
3129
3130 return $cached;
3131 }
3132
3133 /**
3134 * Cache AI insight result
3135 *
3136 * Uses WordPress options for permanent storage until user explicitly refreshes.
3137 * Data is stored per insight type with board ID suffix (e.g., wpforo_ai_insight_sentiment_0).
3138 *
3139 * @param int $board_id Board ID
3140 * @param string $type Insight type
3141 * @param array $data Result data
3142 */
3143 function wpforo_ai_cache_insight( $board_id, $type, $data ) {
3144 $option_key = 'wpforo_ai_insight_' . sanitize_key( $type ) . '_' . intval( $board_id );
3145
3146 $insight_data = array(
3147 'data' => $data,
3148 'timestamp' => time(),
3149 );
3150
3151 // Use update_option for permanent storage (survives cache clears)
3152 update_option( $option_key, $insight_data, false ); // autoload = false for performance
3153 }
3154
3155 /**
3156 * Check if cached insight data is outdated (older than 15 days)
3157 *
3158 * @param int $timestamp Unix timestamp when data was cached
3159 * @return bool True if data is older than 15 days
3160 */
3161 function wpforo_ai_is_insight_outdated( $timestamp ) {
3162 $days_old = ( time() - $timestamp ) / DAY_IN_SECONDS;
3163 return $days_old >= 15;
3164 }
3165
3166 /**
3167 * Delete cached AI insight for a specific type and board
3168 *
3169 * @param int $board_id Board ID
3170 * @param string $type Insight type (or 'all' to delete all)
3171 */
3172 function wpforo_ai_delete_cached_insight( $board_id, $type = 'all' ) {
3173 if ( 'all' === $type ) {
3174 $insight_types = array( 'sentiment', 'trending', 'deep_analysis', 'sentiment_trend', 'recommendations' );
3175 foreach ( $insight_types as $t ) {
3176 $option_key = 'wpforo_ai_insight_' . $t . '_' . intval( $board_id );
3177 delete_option( $option_key );
3178 }
3179 } else {
3180 $option_key = 'wpforo_ai_insight_' . sanitize_key( $type ) . '_' . intval( $board_id );
3181 delete_option( $option_key );
3182 }
3183 }
3184