← All changes
|
includes/seo/class-content-optimization-manager.php
+43
-21
1.0.1
→
2.10.0
View file →
| @@ -14,8 +14,13 @@ | ||
| 14 | 14 | declare(strict_types=1); |
| 15 | 15 | |
| 16 | 16 | namespace ThinkRank\SEO; |
| 17 | 17 | |
| 18 | +// Prevent direct access | |
| 19 | +if (!defined('ABSPATH')) { | |
| 20 | + exit; | |
| 21 | +} | |
| 22 | + | |
| 18 | 23 | /** |
| 19 | 24 | * Content Optimization Manager Class |
| 20 | 25 | * |
| 21 | 26 | * Provides comprehensive content optimization with real-time scoring, |
| @@ -28,19 +33,28 @@ | ||
| 28 | 33 | |
| 29 | 34 | /** |
| 30 | 35 | * Content optimization scoring weights (2025 SEO standards) |
| 31 | 36 | * |
| 37 | + * Semantic relevance used to sit here at 5%, fed by a block that returned | |
| 38 | + * the same numbers for every input (#538). Removing a factor would have | |
| 39 | + * capped the score at 95, so the remaining six were rescaled by 100/95 and | |
| 40 | + * rounded to whole numbers, which preserves their former ratio | |
| 41 | + * (25:20:15:15:10:10) and keeps the array at 100. | |
| 42 | + * | |
| 43 | + * Rounding left technical_seo and user_experience, which were equal at 10, | |
| 44 | + * one point apart at 11 and 10. That is an artefact of landing the spare | |
| 45 | + * point somewhere, not a judgement that technical SEO matters more. | |
| 46 | + * | |
| 32 | 47 | * @since 1.0.0 |
| 33 | 48 | * @var array |
| 34 | 49 | */ |
| 35 | 50 | private array $optimization_weights = [ |
| 36 | - 'content_quality' => 25, // Content depth, uniqueness, value | |
| 37 | - 'keyword_optimization' => 20, // Keyword usage and distribution | |
| 38 | - 'readability' => 15, // Reading ease and comprehension | |
| 39 | - 'structure' => 15, // Headings, paragraphs, formatting | |
| 40 | - 'technical_seo' => 10, // Meta tags, URLs, schema | |
| 41 | - 'user_experience' => 10, // Engagement signals, CTR optimization | |
| 42 | - 'semantic_relevance' => 5 // Topic relevance and entity coverage | |
| 51 | + 'content_quality' => 26, // Content depth, uniqueness, value | |
| 52 | + 'keyword_optimization' => 21, // Keyword usage and distribution | |
| 53 | + 'readability' => 16, // Reading ease and comprehension | |
| 54 | + 'structure' => 16, // Headings, paragraphs, formatting | |
| 55 | + 'technical_seo' => 11, // Meta tags, URLs, schema | |
| 56 | + 'user_experience' => 10 // Engagement signals, CTR optimization | |
| 43 | 57 | ]; |
| 44 | 58 | |
| 45 | 59 | /** |
| 46 | 60 | * SEO content templates for different content types |
| @@ -707,13 +721,8 @@ | ||
| 707 | 721 | // User experience score (10% weight) |
| 708 | 722 | $ux_score = $this->calculate_user_experience_score($content_analysis, $template_compliance); |
| 709 | 723 | $scores['user_experience'] = $ux_score * ($this->optimization_weights['user_experience'] / 100); |
| 710 | 724 | |
| 711 | - // Semantic relevance score (5% weight) | |
| 712 | - if (!empty($content_analysis['semantic_analysis']['relevance_score'])) { | |
| 713 | - $scores['semantic_relevance'] = $content_analysis['semantic_analysis']['relevance_score'] * ($this->optimization_weights['semantic_relevance'] / 100); | |
| 714 | - } | |
| 715 | - | |
| 716 | 725 | return (int) round(array_sum($scores)); |
| 717 | 726 | } |
| 718 | 727 | |
| 719 | 728 | /** |
| @@ -1044,9 +1053,9 @@ | ||
| 1044 | 1053 | // Get recent posts for blog homepage |
| 1045 | 1054 | $recent_posts = get_posts(['numberposts' => 3]); |
| 1046 | 1055 | $content_parts = []; |
| 1047 | 1056 | foreach ($recent_posts as $post) { |
| 1048 | - $content_parts[] = $post->post_title . ' ' . wp_trim_words($post->post_content, 100); | |
| 1057 | + $content_parts[] = $post->post_title . ' ' . \ThinkRank\Core\Seo_Text::trim_words($post->post_content, 100, '...', 1000); | |
| 1049 | 1058 | } |
| 1050 | 1059 | $content = implode(' ', $content_parts); |
| 1051 | 1060 | } |
| 1052 | 1061 | break; |
| @@ -1137,9 +1146,9 @@ | ||
| 1137 | 1146 | 'recommendations' => wp_json_encode($results['recommendations'] ?? []), |
| 1138 | 1147 | 'analyzed_by' => get_current_user_id() |
| 1139 | 1148 | ]; |
| 1140 | 1149 | |
| 1141 | - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Optimization results storage requires direct database access | |
| 1150 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Optimization results storage requires direct database access | |
| 1142 | 1151 | $result = $wpdb->insert($table_name, $data); |
| 1143 | 1152 | |
| 1144 | 1153 | return $result !== false; |
| 1145 | 1154 | } |
| @@ -1397,9 +1406,9 @@ | ||
| 1397 | 1406 | // Word count recommendations |
| 1398 | 1407 | if (!empty($compliance['word_count_compliance']) && |
| 1399 | 1408 | $compliance['word_count_compliance']['status'] === 'non_compliant') { |
| 1400 | 1409 | $needed = $compliance['word_count_compliance']['min_required'] - |
| 1401 | - $compliance['word_count_compliance']['current_count']; | |
| 1410 | + $compliance['word_count_compliance']['current_count']; | |
| 1402 | 1411 | $recommendations[] = [ |
| 1403 | 1412 | 'type' => 'word_count', |
| 1404 | 1413 | 'priority' => 'high', |
| 1405 | 1414 | 'message' => "Add {$needed} more words to meet minimum requirements", |
| @@ -1441,11 +1450,14 @@ | ||
| 1441 | 1450 | |
| 1442 | 1451 | return max(0, $score); |
| 1443 | 1452 | } |
| 1444 | 1453 | |
| 1445 | - // Placeholder implementations for methods referenced but not yet implemented | |
| 1454 | + // Placeholder implementations for methods referenced but not yet implemented. | |
| 1455 | + // Anything here that cannot measure its subject returns null or an empty | |
| 1456 | + // set, never a plausible-looking number: a consumer can branch on "not | |
| 1457 | + // measured", but not on an 80 that was typed rather than computed (#538). | |
| 1446 | 1458 | private function calculate_performance_metrics(array $content_analysis, array $template_compliance): array { |
| 1447 | - return ['performance_score' => 80, 'metrics' => []]; | |
| 1459 | + return ['performance_score' => null, 'metrics' => []]; | |
| 1448 | 1460 | } |
| 1449 | 1461 | |
| 1450 | 1462 | private function generate_optimization_recommendations(array $content_analysis, array $template_compliance, string $content_type): array { |
| 1451 | 1463 | $recommendations = []; |
| @@ -1462,9 +1474,12 @@ | ||
| 1462 | 1474 | return $recommendations; |
| 1463 | 1475 | } |
| 1464 | 1476 | |
| 1465 | 1477 | private function identify_optimization_opportunities(array $content_analysis, array $template_compliance, string $content_type): array { |
| 1466 | - return ['opportunities' => [], 'potential_impact' => 'medium']; | |
| 1478 | + // No potential_impact: with no opportunities found there is nothing to | |
| 1479 | + // rate, and 'medium' was a verdict on an empty list. The sibling in | |
| 1480 | + // Performance_Monitoring_Manager already returns just this shape. | |
| 1481 | + return ['opportunities' => []]; | |
| 1467 | 1482 | } |
| 1468 | 1483 | |
| 1469 | 1484 | private function generate_content_suggestions(array $content_analysis, string $content_type, array $keywords): array { |
| 1470 | 1485 | return ['suggestions' => [], 'content_ideas' => []]; |
| @@ -1493,10 +1508,13 @@ | ||
| 1493 | 1508 | private function calculate_improvement_rate(array $performance_trends): float { |
| 1494 | 1509 | return 0.0; |
| 1495 | 1510 | } |
| 1496 | 1511 | |
| 1497 | - private function calculate_performance_score(array $metrics): int { | |
| 1498 | - return 80; | |
| 1512 | + private function calculate_performance_score(array $metrics): ?int { | |
| 1513 | + // Nothing here scores $metrics, so there is no score to report. Null | |
| 1514 | + // travels into track_performance()'s 'performance_score' and reads as | |
| 1515 | + // "not measured"; an 80 read as a healthy page (#538). | |
| 1516 | + return null; | |
| 1499 | 1517 | } |
| 1500 | 1518 | |
| 1501 | 1519 | private function compare_with_benchmarks(array $metrics, string $context_type): array { |
| 1502 | 1520 | return ['benchmark_comparison' => []]; |
| @@ -1502,7 +1520,11 @@ | ||
| 1502 | 1520 | return ['benchmark_comparison' => []]; |
| 1503 | 1521 | } |
| 1504 | 1522 | |
| 1505 | 1523 | private function store_performance_data(string $context_type, ?int $context_id, array $tracking): bool { |
| 1506 | - return true; | |
| 1524 | + // Reports failure because it stores nothing. The single caller discards | |
| 1525 | + // the return, so this changes no behaviour today, but a caller added | |
| 1526 | + // later must not read "stored successfully" from a method with no | |
| 1527 | + // storage in it (#538). | |
| 1528 | + return false; | |
| 1507 | 1529 | } |
| 1508 | 1530 | } |