PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.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 All 50 releases
← All changes | includes/seo/class-ai-content-analyzer.php +29 -146 2.2.0 → 2.9.0 View file →
@@ -94,49 +94,24 @@
94 94 ]
95 95 ];
96 96
97 97 /**
98 - * Semantic analysis capabilities
98 + * Content optimization scoring weights
99 99 *
100 - * @since 1.0.0
101 - * @var array
102 - */
103 - private array $semantic_capabilities = [
104 - 'topic_modeling' => [
105 - 'enabled' => true,
106 - 'min_topics' => 3,
107 - 'max_topics' => 10,
108 - 'coherence_threshold' => 0.7
109 - ],
110 - 'entity_recognition' => [
111 - 'enabled' => true,
112 - 'entity_types' => ['PERSON', 'ORGANIZATION', 'LOCATION', 'PRODUCT', 'EVENT'],
113 - 'confidence_threshold' => 0.8
114 - ],
115 - 'sentiment_analysis' => [
116 - 'enabled' => true,
117 - 'scale' => [-1.0, 1.0], // Negative to positive
118 - 'neutral_range' => [-0.1, 0.1]
119 - ],
120 - 'content_classification' => [
121 - 'enabled' => true,
122 - 'categories' => ['informational', 'commercial', 'transactional', 'navigational'],
123 - 'confidence_threshold' => 0.6
124 - ]
125 - ];
126 -
127 - /**
128 - * Content optimization scoring weights
100 + * Semantic relevance used to sit here at 15%, fed by a block that returned
101 + * the same numbers for every input (#538). Removing a factor would have
102 + * capped the score at 85, so the remaining four were rescaled by 100/85 and
103 + * rounded to whole numbers, which preserves their former ratio (25:30:20:10)
104 + * and keeps the array at 100.
129 105 *
130 106 * @since 1.0.0
131 107 * @var array
132 108 */
133 109 private array $scoring_weights = [
134 - 'readability' => 25,
135 - 'keyword_optimization' => 30,
136 - 'content_structure' => 20,
137 - 'semantic_relevance' => 15,
138 - 'technical_seo' => 10
110 + 'readability' => 29,
111 + 'keyword_optimization' => 35,
112 + 'content_structure' => 24,
113 + 'technical_seo' => 12
139 114 ];
140 115
141 116 /**
142 117 * Constructor
@@ -161,9 +136,8 @@
161 136 $analysis = [
162 137 'content_stats' => [],
163 138 'readability' => [],
164 139 'keyword_analysis' => [],
165 - 'semantic_analysis' => [],
166 140 'structure_analysis' => [],
167 141 'optimization_score' => 0,
168 142 'recommendations' => [],
169 143 'ai_confidence' => 0,
@@ -180,11 +154,8 @@
180 154 if (!empty($keywords)) {
181 155 $analysis['keyword_analysis'] = $this->analyze_keywords($content, $keywords, $analysis['content_stats']);
182 156 }
183 157
184 - // Semantic content analysis
185 - $analysis['semantic_analysis'] = $this->analyze_semantic_content($content, $options);
186 -
187 158 // Content structure analysis
188 159 $analysis['structure_analysis'] = $this->analyze_content_structure($content, $options);
189 160
190 161 // Calculate overall optimization score
@@ -330,60 +301,8 @@
330 301 return $keyword_analysis;
331 302 }
332 303
333 304 /**
334 - * Analyze semantic content with topic modeling and entity recognition
335 - *
336 - * @since 1.0.0
337 - *
338 - * @param string $content Content to analyze
339 - * @param array $options Analysis options
340 - * @return array Semantic analysis results
341 - */
342 - public function analyze_semantic_content(string $content, array $options = []): array {
343 - $semantic_analysis = [
344 - 'topic_clusters' => [],
345 - 'entities' => [],
346 - 'sentiment' => [],
347 - 'content_classification' => [],
348 - 'semantic_keywords' => [],
349 - 'coherence_score' => 0,
350 - 'relevance_score' => 0
351 - ];
352 -
353 - // Topic modeling and clustering
354 - if ($this->semantic_capabilities['topic_modeling']['enabled']) {
355 - $semantic_analysis['topic_clusters'] = $this->extract_topic_clusters($content);
356 - }
357 -
358 - // Named entity recognition
359 - if ($this->semantic_capabilities['entity_recognition']['enabled']) {
360 - $semantic_analysis['entities'] = $this->recognize_entities($content);
361 - }
362 -
363 - // Sentiment analysis
364 - if ($this->semantic_capabilities['sentiment_analysis']['enabled']) {
365 - $semantic_analysis['sentiment'] = $this->analyze_sentiment($content);
366 - }
367 -
368 - // Content classification
369 - if ($this->semantic_capabilities['content_classification']['enabled']) {
370 - $semantic_analysis['content_classification'] = $this->classify_content($content);
371 - }
372 -
373 - // Extract semantic keywords
374 - $semantic_analysis['semantic_keywords'] = $this->extract_semantic_keywords($content);
375 -
376 - // Calculate coherence score
377 - $semantic_analysis['coherence_score'] = $this->calculate_content_coherence($semantic_analysis);
378 -
379 - // Calculate relevance score
380 - $semantic_analysis['relevance_score'] = $this->calculate_semantic_relevance($semantic_analysis);
381 -
382 - return $semantic_analysis;
383 - }
384 -
385 - /**
386 305 * Analyze content structure and organization
387 306 *
388 307 * @since 1.0.0
389 308 *
@@ -523,12 +442,14 @@
523 442 $keywords = $this->extract_target_keywords($context_type, $context_id);
524 443
525 444 if (!empty($content)) {
526 445 // Perform comprehensive analysis
446 + // semantic_analysis_enabled is deliberately not passed: the block it
447 + // gated was removed in #538 and nothing downstream reads the option.
448 + // The setting itself stays, so stored rows are not purged (#452).
527 449 $analysis_options = [
528 450 'readability_enabled' => $settings['readability_enabled'] ?? true,
529 451 'keyword_analysis_enabled' => $settings['keyword_analysis_enabled'] ?? true,
530 - 'semantic_analysis_enabled' => $settings['semantic_analysis_enabled'] ?? true,
531 452 'structure_analysis_enabled' => $settings['structure_analysis_enabled'] ?? true
532 453 ];
533 454
534 455 $output['analysis_results'] = $this->analyze_content($content, $keywords, $analysis_options);
@@ -554,8 +475,11 @@
554 475 $defaults = [
555 476 'enabled' => true,
556 477 'readability_enabled' => true,
557 478 'keyword_analysis_enabled' => true,
479 + // Inert since #538; see get_settings_schema(). The default stays
480 + // true so the value this endpoint reports for a site that never
481 + // saved the key does not change, and nothing reads it either way.
558 482 'semantic_analysis_enabled' => true,
559 483 'structure_analysis_enabled' => true,
560 484 'auto_analyze' => true,
561 485 'target_reading_level' => 'high_school',
@@ -611,12 +535,17 @@
611 535 'title' => 'Enable Keyword Analysis',
612 536 'description' => 'Analyze keyword density and optimization',
613 537 'default' => true
614 538 ],
539 + // Retired in #538, but still published so a client that stored the
540 + // key sees it described rather than missing. The title and
541 + // description say plainly that it does nothing: the topic modeling
542 + // and entity recognition they used to promise were a block that
543 + // returned the same answer for every input.
615 544 'semantic_analysis_enabled' => [
616 545 'type' => 'boolean',
617 - 'title' => 'Enable Semantic Analysis',
618 - 'description' => 'Perform topic modeling and entity recognition',
546 + 'title' => 'Semantic Analysis (retired)',
547 + 'description' => 'Has no effect. The topic modeling and entity recognition this gated returned fixed values for every input and were removed. The key is still accepted so existing stored settings are not purged.',
619 548 'default' => true
620 549 ],
621 550 'structure_analysis_enabled' => [
622 551 'type' => 'boolean',
@@ -944,9 +873,9 @@
944 873 // Get recent posts for blog homepage
945 874 $recent_posts = get_posts(['numberposts' => 3]);
946 875 $content_parts = [];
947 876 foreach ($recent_posts as $post) {
948 - $content_parts[] = $post->post_title . ' ' . wp_trim_words($post->post_content, 100);
877 + $content_parts[] = $post->post_title . ' ' . \ThinkRank\Core\Seo_Text::trim_words($post->post_content, 100, '...', 1000);
949 878 }
950 879 $content = implode(' ', $content_parts);
951 880 }
952 881 break;
@@ -1031,29 +960,24 @@
1031 960 */
1032 961 private function calculate_optimization_score(array $analysis): int {
1033 962 $scores = [];
1034 963
1035 - // Readability score (25% weight)
964 + // Readability score (29% weight)
1036 965 if (!empty($analysis['readability']['readability_score'])) {
1037 966 $scores['readability'] = $analysis['readability']['readability_score'] * ($this->scoring_weights['readability'] / 100);
1038 967 }
1039 968
1040 - // Keyword optimization score (30% weight)
969 + // Keyword optimization score (35% weight)
1041 970 if (!empty($analysis['keyword_analysis']['optimization_score'])) {
1042 971 $scores['keyword_optimization'] = $analysis['keyword_analysis']['optimization_score'] * ($this->scoring_weights['keyword_optimization'] / 100);
1043 972 }
1044 973
1045 - // Content structure score (20% weight)
974 + // Content structure score (24% weight)
1046 975 if (!empty($analysis['structure_analysis']['structure_score'])) {
1047 976 $scores['content_structure'] = $analysis['structure_analysis']['structure_score'] * ($this->scoring_weights['content_structure'] / 100);
1048 977 }
1049 978
1050 - // Semantic relevance score (15% weight)
1051 - if (!empty($analysis['semantic_analysis']['relevance_score'])) {
1052 - $scores['semantic_relevance'] = $analysis['semantic_analysis']['relevance_score'] * ($this->scoring_weights['semantic_relevance'] / 100);
1053 - }
1054 -
1055 - // Technical SEO score (10% weight)
979 + // Technical SEO score (12% weight)
1056 980 $technical_score = $this->calculate_technical_seo_score($analysis);
1057 981 $scores['technical_seo'] = $technical_score * ($this->scoring_weights['technical_seo'] / 100);
1058 982
1059 983 return (int) round(array_sum($scores));
@@ -1123,9 +1047,9 @@
1123 1047 }
1124 1048
1125 1049 // Analysis completeness confidence
1126 1050 $completed_analyses = 0;
1127 - $total_analyses = 4; // readability, keyword, semantic, structure
1051 + $total_analyses = 3; // readability, keyword, structure
1128 1052
1129 1053 if (!empty($analysis['readability'])) {
1130 1054 $completed_analyses++;
1131 1055 }
@@ -1131,11 +1055,8 @@
1131 1055 }
1132 1056 if (!empty($analysis['keyword_analysis'])) {
1133 1057 $completed_analyses++;
1134 1058 }
1135 - if (!empty($analysis['semantic_analysis'])) {
1136 - $completed_analyses++;
1137 - }
1138 1059 if (!empty($analysis['structure_analysis'])) {
1139 1060 $completed_analyses++;
1140 1061 }
1141 1062
@@ -1185,46 +1106,8 @@
1185 1106 $score -= 15;
1186 1107 }
1187 1108
1188 1109 return max(0, $score);
1189 - }
1190 -
1191 - /**
1192 - * Simple implementations for semantic analysis methods
1193 - * These would be enhanced with actual AI/ML libraries in production
1194 - */
1195 -
1196 - private function extract_topic_clusters(string $content): array {
1197 - // Simplified topic extraction
1198 - return ['topics' => ['general content'], 'confidence' => 0.7];
1199 - }
1200 -
1201 - private function recognize_entities(string $content): array {
1202 - // Simplified entity recognition
1203 - return ['entities' => [], 'confidence' => 0.6];
1204 - }
1205 -
1206 - private function analyze_sentiment(string $content): array {
1207 - // Simplified sentiment analysis
1208 - return ['sentiment' => 'neutral', 'score' => 0.0, 'confidence' => 0.7];
1209 - }
1210 -
1211 - private function classify_content(string $content): array {
1212 - // Simplified content classification
1213 - return ['category' => 'informational', 'confidence' => 0.8];
1214 - }
1215 -
1216 - private function extract_semantic_keywords(string $content): array {
1217 - // Simplified semantic keyword extraction
1218 - return [];
1219 - }
1220 -
1221 - private function calculate_content_coherence(array $semantic_analysis): float {
1222 - return 0.8; // Simplified coherence score
1223 - }
1224 -
1225 - private function calculate_semantic_relevance(array $semantic_analysis): float {
1226 - return 75.0; // Simplified relevance score
1227 1110 }
1228 1111
1229 1112 private function analyze_heading_structure(string $content): array {
1230 1113 // Extract headings from HTML content