PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.2.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.2.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / seo / class-seo-insight-generator.php

class-seo-insight-generator.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.2.0, at includes/seo/class-seo-insight-generator.php

632 lines 22.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SEO Insight Generator Class
4 *
5 * Converts complex SEO data into clear, actionable insights and recommendations.
6 * Generates human-readable intelligence from traffic, keyword, content, and
7 * technical performance data to help users understand their SEO performance.
8 *
9 * @package ThinkRank
10 * @subpackage SEO
11 * @since 1.0.0
12 */
13
14 declare(strict_types=1);
15
16 namespace ThinkRank\SEO;
17
18 // Prevent direct access
19 if (!defined('ABSPATH')) {
20 exit;
21 }
22
23 /**
24 * SEO Insight Generator Class
25 *
26 * Single Responsibility: Generate actionable SEO insights from data
27 * Following ThinkRank insight patterns from existing codebase
28 *
29 * @since 1.0.0
30 */
31 class SEO_Insight_Generator {
32
33 /**
34 * Insight significance thresholds
35 *
36 * @var array
37 */
38 private const SIGNIFICANCE_THRESHOLDS = [
39 'high' => 20.0, // 20%+ change is highly significant
40 'medium' => 10.0, // 10%+ change is moderately significant
41 'low' => 5.0 // 5%+ change is somewhat significant
42 ];
43
44 /**
45 * Performance benchmarks for context
46 *
47 * @var array
48 */
49 private const PERFORMANCE_BENCHMARKS = [
50 'excellent_ctr' => 5.0,
51 'good_ctr' => 3.0,
52 'average_ctr' => 2.0,
53 'excellent_position' => 3.0,
54 'good_position' => 10.0,
55 'page_one_position' => 10.0
56 ];
57
58 /**
59 * Generate traffic intelligence insights
60 *
61 * @param array $trend_data Traffic trend data
62 * @param array $context Additional context data
63 * @return array Traffic insights
64 */
65 public function generate_traffic_insights(array $trend_data, array $context = []): array {
66 $insights = [];
67
68 // Analyze overall traffic trends
69 $sessions_trend = $trend_data['sessions'] ?? [];
70 if (!empty($sessions_trend)) {
71 $insights[] = $this->create_traffic_trend_insight($sessions_trend);
72 }
73
74 // Analyze organic traffic specifically
75 $organic_trend = $trend_data['organic_traffic'] ?? [];
76 if (!empty($organic_trend)) {
77 $insights[] = $this->create_organic_traffic_insight($organic_trend);
78 }
79
80 // Analyze engagement metrics
81 $bounce_rate_trend = $trend_data['bounce_rate'] ?? [];
82 if (!empty($bounce_rate_trend)) {
83 $insights[] = $this->create_engagement_insight($bounce_rate_trend);
84 }
85
86 return [
87 'insights' => array_filter($insights),
88 'summary' => $this->generate_traffic_insights_summary($insights),
89 'priority_actions' => $this->extract_priority_actions($insights)
90 ];
91 }
92
93 /**
94 * Generate keyword intelligence insights
95 *
96 * @param array $keyword_trends Keyword trend data
97 * @param array $benchmarks Industry benchmarks
98 * @return array Keyword insights
99 */
100 public function generate_keyword_insights(array $keyword_trends, array $benchmarks = []): array {
101 $insights = [];
102
103 // Analyze keyword performance trends
104 if (!empty($keyword_trends['top_gaining_keywords'])) {
105 $insights[] = $this->create_gaining_keywords_insight($keyword_trends['top_gaining_keywords']);
106 }
107
108 if (!empty($keyword_trends['top_losing_keywords'])) {
109 $insights[] = $this->create_losing_keywords_insight($keyword_trends['top_losing_keywords']);
110 }
111
112 // Analyze overall keyword metrics
113 $average_position = $keyword_trends['average_position'] ?? [];
114 if (!empty($average_position)) {
115 $insights[] = $this->create_position_performance_insight($average_position);
116 }
117
118 $average_ctr = $keyword_trends['average_ctr'] ?? [];
119 if (!empty($average_ctr)) {
120 $insights[] = $this->create_ctr_performance_insight($average_ctr);
121 }
122
123 return [
124 'insights' => array_filter($insights),
125 'summary' => $this->generate_keyword_insights_summary($insights),
126 'optimization_opportunities' => $this->identify_keyword_optimization_opportunities($keyword_trends)
127 ];
128 }
129
130 /**
131 * Generate content performance intelligence
132 *
133 * @param array $content_performance Content performance data
134 * @return array Content insights
135 */
136 public function generate_content_insights(array $content_performance): array {
137 $insights = [];
138
139 // Analyze top performing content
140 $gaining_pages = $content_performance['top_gaining_pages'] ?? [];
141 if (!empty($gaining_pages)) {
142 $insights[] = $this->create_top_content_insight($gaining_pages);
143 }
144
145 // Analyze device performance
146 $device_trends = $content_performance['mobile_vs_desktop'] ?? [];
147 if (!empty($device_trends)) {
148 $insights[] = $this->create_device_performance_insight($device_trends);
149 }
150
151 // Analyze content type performance
152 $content_types = $content_performance['content_type_performance'] ?? [];
153 if (!empty($content_types)) {
154 $insights[] = $this->create_content_type_insight($content_types);
155 }
156
157 return [
158 'insights' => array_filter($insights),
159 'summary' => $this->generate_content_insights_summary($insights),
160 'content_strategy_recommendations' => $this->generate_content_strategy_recommendations($content_performance)
161 ];
162 }
163
164 /**
165 * Generate technical SEO intelligence
166 *
167 * @param array $performance_data Technical performance data
168 * @return array Technical insights
169 */
170 public function generate_technical_insights(array $performance_data): array {
171 $insights = [];
172
173 // Analyze Core Web Vitals
174 $core_web_vitals = $performance_data['core_web_vitals'] ?? [];
175 if (!empty($core_web_vitals)) {
176 $insights[] = $this->create_core_web_vitals_insight($core_web_vitals);
177 }
178
179 // Analyze page speed trends
180 if (isset($performance_data['page_speed_trends'])) {
181 $insights[] = $this->create_page_speed_insight($performance_data['page_speed_trends']);
182 }
183
184 return [
185 'insights' => array_filter($insights),
186 'summary' => $this->generate_technical_insights_summary($insights),
187 'technical_recommendations' => $this->generate_technical_recommendations($performance_data)
188 ];
189 }
190
191 /**
192 * Format insights for display
193 *
194 * @param array $insights Raw insights data
195 * @return array Formatted insights
196 */
197 public function format_insights_for_display(array $insights): array {
198 $formatted = [];
199
200 foreach ($insights as $insight) {
201 $formatted[] = [
202 'id' => $this->generate_insight_id($insight),
203 'type' => $insight['type'] ?? 'general',
204 'title' => $insight['title'] ?? 'SEO Insight',
205 'description' => $insight['description'] ?? '',
206 'impact' => $insight['impact'] ?? 'medium',
207 'confidence' => $insight['confidence'] ?? 0.8,
208 'action_required' => $insight['action_required'] ?? false,
209 'recommended_action' => $insight['recommended_action'] ?? '',
210 'data_points' => $insight['data_points'] ?? [],
211 'trend_direction' => $insight['trend_direction'] ?? 'stable',
212 'significance' => $insight['significance'] ?? 'medium',
213 'timestamp' => current_time('mysql')
214 ];
215 }
216
217 return $formatted;
218 }
219
220 /**
221 * Prioritize insights by impact
222 *
223 * @param array $insights All insights
224 * @return array Prioritized insights
225 */
226 public function prioritize_insights_by_impact(array $insights): array {
227 // Define impact priority order
228 $impact_priority = ['high' => 3, 'medium' => 2, 'low' => 1];
229
230 usort($insights, function($a, $b) use ($impact_priority) {
231 $impact_a = $impact_priority[$a['impact'] ?? 'medium'] ?? 2;
232 $impact_b = $impact_priority[$b['impact'] ?? 'medium'] ?? 2;
233
234 if ($impact_a === $impact_b) {
235 // Secondary sort by confidence
236 $confidence_a = $a['confidence'] ?? 0.5;
237 $confidence_b = $b['confidence'] ?? 0.5;
238 return $confidence_b <=> $confidence_a;
239 }
240
241 return $impact_b <=> $impact_a;
242 });
243
244 return [
245 'prioritized_insights' => $insights,
246 'high_impact_count' => count(array_filter($insights, function($insight) {
247 return ($insight['impact'] ?? 'medium') === 'high';
248 })),
249 'action_required_count' => count(array_filter($insights, function($insight) {
250 return $insight['action_required'] ?? false;
251 }))
252 ];
253 }
254
255 /**
256 * Create traffic trend insight
257 *
258 * @param array $sessions_trend Sessions trend data
259 * @return array Traffic trend insight
260 */
261 private function create_traffic_trend_insight(array $sessions_trend): array {
262 $change = $sessions_trend['percentage_change'] ?? 0;
263 $direction = $sessions_trend['trend_direction'] ?? 'stable';
264 $current = $sessions_trend['current'] ?? 0;
265 $previous = $sessions_trend['previous'] ?? 0;
266
267 $significance = $this->determine_significance(abs($change));
268 $impact = $significance === 'high' ? 'high' : 'medium';
269
270 if ($direction === 'up') {
271 $title = 'Traffic Growth Detected';
272 $description = sprintf(
273 'Website traffic increased by %.1f%% (%d to %d sessions), indicating positive SEO momentum.',
274 abs($change),
275 (int) $previous,
276 (int) $current
277 );
278 $action_required = false;
279 $recommended_action = 'Continue current SEO strategies and monitor for sustained growth.';
280 } elseif ($direction === 'down') {
281 $title = 'Traffic Decline Alert';
282 $description = sprintf(
283 'Website traffic decreased by %.1f%% (%d to %d sessions), requiring investigation.',
284 abs($change),
285 (int) $previous,
286 (int) $current
287 );
288 $action_required = true;
289 $recommended_action = 'Investigate traffic sources and optimize underperforming content.';
290 } else {
291 $title = 'Stable Traffic Performance';
292 $description = sprintf(
293 'Website traffic remained stable with %.1f%% change (%d sessions).',
294 $change,
295 (int) $current
296 );
297 $action_required = false;
298 $recommended_action = 'Focus on growth strategies to increase traffic volume.';
299 }
300
301 return [
302 'type' => 'traffic_trend',
303 'title' => $title,
304 'description' => $description,
305 'impact' => $impact,
306 'confidence' => 0.9,
307 'action_required' => $action_required,
308 'recommended_action' => $recommended_action,
309 'trend_direction' => $direction,
310 'significance' => $significance,
311 'data_points' => [
312 'current_sessions' => (int) $current,
313 'previous_sessions' => (int) $previous,
314 'percentage_change' => round($change, 1)
315 ]
316 ];
317 }
318
319 /**
320 * Create organic traffic insight
321 *
322 * @param array $organic_trend Organic traffic trend data
323 * @return array Organic traffic insight
324 */
325 private function create_organic_traffic_insight(array $organic_trend): array {
326 $change = $organic_trend['percentage_change'] ?? 0;
327 $current = $organic_trend['current'] ?? 0;
328 $significance = $this->determine_significance(abs($change));
329
330 return [
331 'type' => 'organic_traffic',
332 'title' => 'Organic Traffic Performance',
333 'description' => sprintf(
334 'Organic search traffic %s by %.1f%%, representing %d sessions from search engines.',
335 $change > 0 ? 'increased' : 'decreased',
336 abs($change),
337 (int) $current
338 ),
339 'impact' => $significance === 'high' ? 'high' : 'medium',
340 'confidence' => 0.85,
341 'trend_direction' => $change > 0 ? 'up' : ($change < 0 ? 'down' : 'stable'),
342 'significance' => $significance
343 ];
344 }
345
346 /**
347 * Create engagement insight
348 *
349 * @param array $bounce_rate_trend Bounce rate trend data
350 * @return array Engagement insight
351 */
352 private function create_engagement_insight(array $bounce_rate_trend): array {
353 $change = $bounce_rate_trend['percentage_change'] ?? 0;
354 $current = $bounce_rate_trend['current'] ?? 0;
355
356 // For bounce rate, lower is better
357 $is_improving = $change < 0;
358 $title = $is_improving ? 'User Engagement Improving' : 'User Engagement Declining';
359
360 return [
361 'type' => 'engagement',
362 'title' => $title,
363 'description' => sprintf(
364 'Bounce rate %s by %.1f%% to %.1f%%, %s user engagement quality.',
365 $change > 0 ? 'increased' : 'decreased',
366 abs($change),
367 $current,
368 $is_improving ? 'indicating improved' : 'suggesting declining'
369 ),
370 'impact' => 'medium',
371 'confidence' => 0.8,
372 'action_required' => !$is_improving && abs($change) > 10,
373 'recommended_action' => $is_improving ?
374 'Continue optimizing content for user engagement.' :
375 'Improve content quality and page loading speed to reduce bounce rate.'
376 ];
377 }
378
379 /**
380 * Create gaining keywords insight
381 *
382 * @param array $gaining_keywords Top gaining keywords
383 * @return array Gaining keywords insight
384 */
385 private function create_gaining_keywords_insight(array $gaining_keywords): array {
386 $count = count($gaining_keywords);
387 $top_keyword = $gaining_keywords[0] ?? [];
388
389 return [
390 'type' => 'keyword_gains',
391 'title' => 'Keyword Performance Gains',
392 'description' => sprintf(
393 '%d keywords showing strong performance, led by "%s" with %d clicks and position %.1f.',
394 $count,
395 $top_keyword['keyword'] ?? 'N/A',
396 $top_keyword['clicks'] ?? 0,
397 $top_keyword['position'] ?? 0
398 ),
399 'impact' => 'high',
400 'confidence' => 0.9,
401 'action_required' => false,
402 'recommended_action' => 'Optimize content around these performing keywords to maintain momentum.'
403 ];
404 }
405
406 /**
407 * Create losing keywords insight
408 *
409 * @param array $losing_keywords Top losing keywords
410 * @return array Losing keywords insight
411 */
412 private function create_losing_keywords_insight(array $losing_keywords): array {
413 $count = count($losing_keywords);
414 $worst_keyword = $losing_keywords[0] ?? [];
415
416 return [
417 'type' => 'keyword_losses',
418 'title' => 'Keyword Performance Issues',
419 'description' => sprintf(
420 '%d keywords underperforming, including "%s" at position %.1f with %d impressions but only %d clicks.',
421 $count,
422 $worst_keyword['keyword'] ?? 'N/A',
423 $worst_keyword['position'] ?? 0,
424 $worst_keyword['impressions'] ?? 0,
425 $worst_keyword['clicks'] ?? 0
426 ),
427 'impact' => 'high',
428 'confidence' => 0.85,
429 'action_required' => true,
430 'recommended_action' => 'Optimize content and meta descriptions for these underperforming keywords.'
431 ];
432 }
433
434 /**
435 * Create position performance insight
436 *
437 * @param array $average_position Average position data
438 * @return array Position insight
439 */
440 private function create_position_performance_insight(array $average_position): array {
441 $position = $average_position['average_position'] ?? 0;
442 $keyword_count = $average_position['keywords_tracked'] ?? 0;
443
444 if ($position <= self::PERFORMANCE_BENCHMARKS['excellent_position']) {
445 $performance = 'excellent';
446 $impact = 'low'; // Already performing well
447 } elseif ($position <= self::PERFORMANCE_BENCHMARKS['good_position']) {
448 $performance = 'good';
449 $impact = 'medium';
450 } else {
451 $performance = 'needs improvement';
452 $impact = 'high';
453 }
454
455 return [
456 'type' => 'position_performance',
457 'title' => 'Search Position Analysis',
458 'description' => sprintf(
459 'Average search position is %.1f across %d tracked keywords, indicating %s ranking performance.',
460 $position,
461 $keyword_count,
462 $performance
463 ),
464 'impact' => $impact,
465 'confidence' => 0.8,
466 'action_required' => $performance === 'needs improvement',
467 'recommended_action' => $performance === 'needs improvement' ?
468 'Focus on improving content quality and building authority for better rankings.' :
469 'Maintain current SEO strategies to preserve ranking positions.'
470 ];
471 }
472
473 /**
474 * Create CTR performance insight
475 *
476 * @param array $average_ctr Average CTR data
477 * @return array CTR insight
478 */
479 private function create_ctr_performance_insight(array $average_ctr): array {
480 $ctr = $average_ctr['average_ctr'] ?? 0;
481 $clicks = $average_ctr['total_clicks'] ?? 0;
482 $impressions = $average_ctr['total_impressions'] ?? 0;
483
484 if ($ctr >= self::PERFORMANCE_BENCHMARKS['excellent_ctr']) {
485 $performance = 'excellent';
486 $impact = 'low';
487 } elseif ($ctr >= self::PERFORMANCE_BENCHMARKS['good_ctr']) {
488 $performance = 'good';
489 $impact = 'medium';
490 } else {
491 $performance = 'needs improvement';
492 $impact = 'high';
493 }
494
495 return [
496 'type' => 'ctr_performance',
497 'title' => 'Click-Through Rate Analysis',
498 'description' => sprintf(
499 'Average CTR is %.2f%% (%d clicks from %d impressions), indicating %s title and meta optimization.',
500 $ctr,
501 $clicks,
502 $impressions,
503 $performance
504 ),
505 'impact' => $impact,
506 'confidence' => 0.85,
507 'action_required' => $performance === 'needs improvement',
508 'recommended_action' => $performance === 'needs improvement' ?
509 'Optimize titles and meta descriptions to improve click-through rates.' :
510 'Continue monitoring CTR performance and test title variations.'
511 ];
512 }
513
514 /**
515 * Determine significance level
516 *
517 * @param float $change Percentage change
518 * @return string Significance level
519 */
520 private function determine_significance(float $change): string {
521 if ($change >= self::SIGNIFICANCE_THRESHOLDS['high']) {
522 return 'high';
523 } elseif ($change >= self::SIGNIFICANCE_THRESHOLDS['medium']) {
524 return 'medium';
525 } elseif ($change >= self::SIGNIFICANCE_THRESHOLDS['low']) {
526 return 'low';
527 } else {
528 return 'minimal';
529 }
530 }
531
532 /**
533 * Generate insight ID
534 *
535 * @param array $insight Insight data
536 * @return string Unique insight ID
537 */
538 private function generate_insight_id(array $insight): string {
539 $type = $insight['type'] ?? 'general';
540 $timestamp = time();
541 return sprintf('%s_%s', $type, $timestamp);
542 }
543
544 /**
545 * Generate traffic insights summary
546 *
547 * @param array $insights Traffic insights
548 * @return string Summary
549 */
550 private function generate_traffic_insights_summary(array $insights): string {
551 $count = count($insights);
552 $action_required = count(array_filter($insights, function($insight) {
553 return $insight['action_required'] ?? false;
554 }));
555
556 if ($action_required > 0) {
557 return sprintf('Generated %d traffic insights, %d requiring immediate attention.', $count, $action_required);
558 } else {
559 return sprintf('Generated %d traffic insights showing stable performance.', $count);
560 }
561 }
562
563 /**
564 * Extract priority actions from insights
565 *
566 * @param array $insights All insights
567 * @return array Priority actions
568 */
569 private function extract_priority_actions(array $insights): array {
570 $actions = [];
571
572 foreach ($insights as $insight) {
573 if ($insight['action_required'] ?? false) {
574 $actions[] = [
575 'action' => $insight['recommended_action'] ?? 'Review performance data',
576 'priority' => $insight['impact'] ?? 'medium',
577 'type' => $insight['type'] ?? 'general'
578 ];
579 }
580 }
581
582 return $actions;
583 }
584
585 /**
586 * Generate placeholder methods for remaining functionality
587 */
588 private function generate_keyword_insights_summary(array $insights): string {
589 return sprintf('Generated %d keyword insights.', count($insights));
590 }
591
592 private function identify_keyword_optimization_opportunities(array $keyword_trends): array {
593 return ['opportunities' => []];
594 }
595
596 private function create_top_content_insight(array $gaining_pages): array {
597 return ['type' => 'content_performance', 'title' => 'Top Content Performance'];
598 }
599
600 private function create_device_performance_insight(array $device_trends): array {
601 return ['type' => 'device_performance', 'title' => 'Device Performance Analysis'];
602 }
603
604 private function create_content_type_insight(array $content_types): array {
605 return ['type' => 'content_type', 'title' => 'Content Type Performance'];
606 }
607
608 private function generate_content_insights_summary(array $insights): string {
609 return sprintf('Generated %d content insights.', count($insights));
610 }
611
612 private function generate_content_strategy_recommendations(array $content_performance): array {
613 return ['recommendations' => []];
614 }
615
616 private function create_core_web_vitals_insight(array $core_web_vitals): array {
617 return ['type' => 'core_web_vitals', 'title' => 'Core Web Vitals Analysis'];
618 }
619
620 private function create_page_speed_insight(array $page_speed_trends): array {
621 return ['type' => 'page_speed', 'title' => 'Page Speed Performance'];
622 }
623
624 private function generate_technical_insights_summary(array $insights): string {
625 return sprintf('Generated %d technical insights.', count($insights));
626 }
627
628 private function generate_technical_recommendations(array $performance_data): array {
629 return ['recommendations' => []];
630 }
631 }
632