| @@ -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 |