PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.2
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.2
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-schema-optimizer.php

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

456 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Schema Optimizer Class
4 *
5 * Handles SEO optimization and recommendations for schema markup.
6 * Extracted from Schema_Generator to follow Single Responsibility Principle.
7 * Maintains exact same optimization logic and recommendation formats as original implementation.
8 *
9 * @package ThinkRank\SEO
10 * @since 1.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\SEO;
16
17 // Prevent direct access
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * Schema Optimizer Class
24 *
25 * Provides SEO optimization recommendations and rich snippets enhancement.
26 * Preserves all existing optimization logic and recommendation formats.
27 *
28 * @since 1.0.0
29 */
30 class Schema_Optimizer {
31
32 /**
33 * Schema Factory instance for type specifications
34 *
35 * @since 1.0.0
36 * @var Schema_Factory
37 */
38 private Schema_Factory $schema_factory;
39
40 /**
41 * Schema Validator instance for validation checks
42 *
43 * @since 1.0.0
44 * @var Schema_Validator
45 */
46 private Schema_Validator $schema_validator;
47
48 /**
49 * Constructor
50 *
51 * @since 1.0.0
52 */
53 public function __construct() {
54 // Ensure required classes are loaded
55 if (!class_exists('ThinkRank\\SEO\\Schema_Factory')) {
56 require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-schema-factory.php';
57 }
58 if (!class_exists('ThinkRank\\SEO\\Schema_Validator')) {
59 require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-schema-validator.php';
60 }
61 $this->schema_factory = new Schema_Factory();
62 $this->schema_validator = new Schema_Validator();
63 }
64
65 /**
66 * Generate optimization recommendations for schema markup
67 * PRESERVED: Maintains exact same optimization logic from original Schema_Generator
68 *
69 * @since 1.0.0
70 *
71 * @param array $schema_data Schema markup to optimize
72 * @param string $schema_type Schema type
73 * @param array $content_data Original content data
74 * @return array Optimization recommendations
75 */
76 public function generate_optimization_recommendations(array $schema_data, string $schema_type, array $content_data = []): array {
77 $recommendations = [
78 'seo_score' => 0,
79 'completeness_score' => 0,
80 'rich_snippets_potential' => 0,
81 'recommendations' => [],
82 'warnings' => [],
83 'improvements' => [],
84 'missing_properties' => [],
85 'optimization_opportunities' => []
86 ];
87
88 // Get validation results first
89 $validation = $this->schema_validator->validate_schema($schema_data);
90 $recommendations['seo_score'] = $validation['score'];
91 $recommendations['warnings'] = $validation['warnings'];
92
93 // Calculate completeness score
94 $recommendations['completeness_score'] = $this->calculate_completeness_score($schema_data, $schema_type);
95
96 // Calculate rich snippets potential
97 $recommendations['rich_snippets_potential'] = $this->calculate_rich_snippets_potential($schema_data, $schema_type);
98
99 // Generate type-specific recommendations
100 $recommendations = $this->add_type_specific_recommendations($schema_data, $schema_type, $recommendations);
101
102 // Add general SEO recommendations
103 $recommendations = $this->add_general_seo_recommendations($schema_data, $recommendations);
104
105 // Identify missing properties
106 $recommendations['missing_properties'] = $this->identify_missing_properties($schema_data, $schema_type);
107
108 // Generate optimization opportunities
109 $recommendations['optimization_opportunities'] = $this->generate_optimization_opportunities($schema_data, $schema_type, $content_data);
110
111 return $recommendations;
112 }
113
114 /**
115 * Calculate completeness score based on required and recommended properties
116 * PRESERVED: Exact same calculation logic from original Schema_Generator
117 *
118 * @since 1.0.0
119 *
120 * @param array $schema_data Schema markup
121 * @param string $schema_type Schema type
122 * @return int Completeness score (0-100)
123 */
124 private function calculate_completeness_score(array $schema_data, string $schema_type): int {
125 $spec = $this->schema_factory->get_schema_specification($schema_type);
126 if (empty($spec)) {
127 return 0;
128 }
129
130 $total_properties = count($spec['required']) + count($spec['recommended']);
131 if ($total_properties === 0) {
132 return 100;
133 }
134
135 $present_properties = 0;
136
137 // Check required properties
138 foreach ($spec['required'] as $required_prop) {
139 if (isset($schema_data[$required_prop]) && !empty($schema_data[$required_prop])) {
140 $present_properties++;
141 }
142 }
143
144 // Check recommended properties
145 foreach ($spec['recommended'] as $recommended_prop) {
146 if (isset($schema_data[$recommended_prop]) && !empty($schema_data[$recommended_prop])) {
147 $present_properties++;
148 }
149 }
150
151 return (int) round(($present_properties / $total_properties) * 100);
152 }
153
154 /**
155 * Calculate rich snippets potential score
156 * PRESERVED: Exact same calculation logic from original Schema_Generator
157 *
158 * @since 1.0.0
159 *
160 * @param array $schema_data Schema markup
161 * @param string $schema_type Schema type
162 * @return int Rich snippets potential score (0-100)
163 */
164 private function calculate_rich_snippets_potential(array $schema_data, string $schema_type): int {
165 $score = 0;
166
167 // Base score for having structured data
168 $score += 20;
169
170 // Type-specific scoring
171 switch ($schema_type) {
172 case 'Article':
173 case 'BlogPosting':
174 if (isset($schema_data['headline'])) $score += 15;
175 if (isset($schema_data['image'])) $score += 15;
176 if (isset($schema_data['author'])) $score += 15;
177 if (isset($schema_data['datePublished'])) $score += 15;
178 if (isset($schema_data['publisher'])) $score += 20;
179 break;
180
181 case 'Product':
182 if (isset($schema_data['name'])) $score += 15;
183 if (isset($schema_data['image'])) $score += 15;
184 if (isset($schema_data['offers'])) $score += 25;
185 if (isset($schema_data['review']) || isset($schema_data['aggregateRating'])) $score += 25;
186 if (isset($schema_data['brand'])) $score += 20;
187 break;
188
189 case 'Organization':
190 case 'LocalBusiness':
191 if (isset($schema_data['name'])) $score += 20;
192 if (isset($schema_data['logo'])) $score += 20;
193 if (isset($schema_data['address'])) $score += 20;
194 if (isset($schema_data['telephone'])) $score += 20;
195 if (isset($schema_data['openingHours'])) $score += 20;
196 break;
197
198 case 'FAQPage':
199 if (isset($schema_data['mainEntity']) && is_array($schema_data['mainEntity'])) {
200 $question_count = count($schema_data['mainEntity']);
201 $score += min(60, $question_count * 15); // Max 60 points for questions
202 }
203 break;
204
205 default:
206 $score += 40; // Default scoring for other types
207 break;
208 }
209
210 return min(100, $score);
211 }
212
213 /**
214 * Add type-specific optimization recommendations
215 * PRESERVED: Exact same recommendation logic from original Schema_Generator
216 *
217 * @since 1.0.0
218 *
219 * @param array $schema_data Schema markup
220 * @param string $schema_type Schema type
221 * @param array $recommendations Current recommendations
222 * @return array Updated recommendations
223 */
224 private function add_type_specific_recommendations(array $schema_data, string $schema_type, array $recommendations): array {
225 switch ($schema_type) {
226 case 'Article':
227 case 'BlogPosting':
228 if (!isset($schema_data['image'])) {
229 $recommendations['recommendations'][] = 'Add a featured image to improve article rich snippets appearance';
230 }
231 if (!isset($schema_data['dateModified'])) {
232 $recommendations['recommendations'][] = 'Add dateModified to show content freshness';
233 }
234 if (!isset($schema_data['wordCount'])) {
235 $recommendations['recommendations'][] = 'Add word count for better content analysis';
236 }
237 break;
238
239 case 'Product':
240 if (!isset($schema_data['offers'])) {
241 $recommendations['recommendations'][] = 'Add pricing information to enable product rich snippets';
242 }
243 if (!isset($schema_data['review']) && !isset($schema_data['aggregateRating'])) {
244 $recommendations['recommendations'][] = 'Add customer reviews or ratings to improve product credibility';
245 }
246 if (!isset($schema_data['brand'])) {
247 $recommendations['recommendations'][] = 'Add brand information to improve product visibility';
248 }
249 if (!isset($schema_data['sku'])) {
250 $recommendations['recommendations'][] = 'Add SKU for better product identification';
251 }
252 break;
253
254 case 'Organization':
255 case 'LocalBusiness':
256 if (!isset($schema_data['logo'])) {
257 $recommendations['recommendations'][] = 'Add a logo to improve brand recognition in search results';
258 }
259 if (!isset($schema_data['address'])) {
260 $recommendations['recommendations'][] = 'Add business address for better local SEO';
261 }
262 if (!isset($schema_data['telephone'])) {
263 $recommendations['recommendations'][] = 'Add phone number for better local search visibility';
264 }
265 if (!isset($schema_data['openingHours'])) {
266 $recommendations['recommendations'][] = 'Add business hours for better local search results';
267 }
268 break;
269
270 case 'FAQPage':
271 if (isset($schema_data['mainEntity']) && count($schema_data['mainEntity']) < 3) {
272 $recommendations['recommendations'][] = 'Add more FAQ questions (minimum 3) for better rich snippets';
273 }
274 break;
275
276 case 'Event':
277 if (!isset($schema_data['location'])) {
278 $recommendations['recommendations'][] = 'Add event location for better event rich snippets';
279 }
280 if (!isset($schema_data['startDate'])) {
281 $recommendations['recommendations'][] = 'Add event start date for proper event markup';
282 }
283 break;
284
285 case 'HowTo':
286 if (!isset($schema_data['step']) || count($schema_data['step']) < 3) {
287 $recommendations['recommendations'][] = 'Add detailed steps (minimum 3) for better how-to rich snippets';
288 }
289 if (!isset($schema_data['totalTime'])) {
290 $recommendations['recommendations'][] = 'Add total time estimate for better user experience';
291 }
292 break;
293 }
294
295 return $recommendations;
296 }
297
298 /**
299 * Add general SEO recommendations
300 * PRESERVED: Exact same recommendation logic from original Schema_Generator
301 *
302 * @since 1.0.0
303 *
304 * @param array $schema_data Schema markup
305 * @param array $recommendations Current recommendations
306 * @return array Updated recommendations
307 */
308 private function add_general_seo_recommendations(array $schema_data, array $recommendations): array {
309 // Check for description
310 if (!isset($schema_data['description'])) {
311 $recommendations['recommendations'][] = 'Add a description to improve search result snippets';
312 } elseif (strlen($schema_data['description']) < 120) {
313 $recommendations['improvements'][] = 'Consider expanding the description (current: ' . strlen($schema_data['description']) . ' characters, recommended: 120-160)';
314 }
315
316 // Check for URL
317 if (!isset($schema_data['url'])) {
318 $recommendations['recommendations'][] = 'Add a URL to help search engines understand the content location';
319 }
320
321 // Check for image
322 if (!isset($schema_data['image'])) {
323 $recommendations['recommendations'][] = 'Add an image to improve visual appeal in search results';
324 }
325
326 // Check for name/title length
327 if (isset($schema_data['name']) && strlen($schema_data['name']) > 60) {
328 $recommendations['warnings'][] = 'Title/name is longer than 60 characters, may be truncated in search results';
329 }
330
331 if (isset($schema_data['headline']) && strlen($schema_data['headline']) > 110) {
332 $recommendations['warnings'][] = 'Headline is longer than 110 characters, may be truncated in search results';
333 }
334
335 return $recommendations;
336 }
337
338 /**
339 * Identify missing properties for schema type
340 * PRESERVED: Exact same identification logic from original Schema_Generator
341 *
342 * @since 1.0.0
343 *
344 * @param array $schema_data Schema markup
345 * @param string $schema_type Schema type
346 * @return array Missing properties categorized by importance
347 */
348 private function identify_missing_properties(array $schema_data, string $schema_type): array {
349 $spec = $this->schema_factory->get_schema_specification($schema_type);
350 $missing = [
351 'required' => [],
352 'recommended' => [],
353 'optional' => []
354 ];
355
356 if (empty($spec)) {
357 return $missing;
358 }
359
360 // Check required properties
361 foreach ($spec['required'] as $required_prop) {
362 if (!isset($schema_data[$required_prop]) || empty($schema_data[$required_prop])) {
363 $missing['required'][] = $required_prop;
364 }
365 }
366
367 // Check recommended properties
368 foreach ($spec['recommended'] as $recommended_prop) {
369 if (!isset($schema_data[$recommended_prop]) || empty($schema_data[$recommended_prop])) {
370 $missing['recommended'][] = $recommended_prop;
371 }
372 }
373
374 // Check optional properties (sample of most important ones)
375 $important_optional = array_slice($spec['optional'], 0, 5);
376 foreach ($important_optional as $optional_prop) {
377 if (!isset($schema_data[$optional_prop]) || empty($schema_data[$optional_prop])) {
378 $missing['optional'][] = $optional_prop;
379 }
380 }
381
382 return $missing;
383 }
384
385 /**
386 * Generate optimization opportunities based on content analysis
387 * PRESERVED: Exact same opportunity generation logic from original Schema_Generator
388 *
389 * @since 1.0.0
390 *
391 * @param array $schema_data Schema markup
392 * @param string $schema_type Schema type
393 * @param array $content_data Original content data
394 * @return array Optimization opportunities
395 */
396 private function generate_optimization_opportunities(array $schema_data, string $schema_type, array $content_data): array {
397 $opportunities = [];
398
399 // Content-based opportunities
400 if (!empty($content_data['content'])) {
401 $content = $content_data['content'];
402
403 // Check for FAQ content that could be structured
404 if ($schema_type !== 'FAQPage' && preg_match('/\b(faq|frequently asked|question)\b/i', $content)) {
405 $opportunities[] = [
406 'type' => 'schema_addition',
407 'title' => 'FAQ Schema Opportunity',
408 'description' => 'Content contains FAQ-style information that could benefit from FAQ schema markup',
409 'impact' => 'high'
410 ];
411 }
412
413 // Check for how-to content
414 if ($schema_type !== 'HowTo' && preg_match('/\b(how to|step|instruction|tutorial)\b/i', $content)) {
415 $opportunities[] = [
416 'type' => 'schema_addition',
417 'title' => 'How-To Schema Opportunity',
418 'description' => 'Content contains instructional information that could benefit from HowTo schema markup',
419 'impact' => 'medium'
420 ];
421 }
422
423 // Check for product information
424 if ($schema_type !== 'Product' && preg_match('/\b(price|buy|purchase|\$[0-9])/i', $content)) {
425 $opportunities[] = [
426 'type' => 'schema_addition',
427 'title' => 'Product Schema Opportunity',
428 'description' => 'Content contains product information that could benefit from Product schema markup',
429 'impact' => 'high'
430 ];
431 }
432 }
433
434 // Schema enhancement opportunities
435 if ($schema_type === 'Article' && !isset($schema_data['keywords'])) {
436 $opportunities[] = [
437 'type' => 'property_enhancement',
438 'title' => 'Add Keywords',
439 'description' => 'Adding relevant keywords can improve content categorization',
440 'impact' => 'low'
441 ];
442 }
443
444 if (in_array($schema_type, ['Product', 'LocalBusiness']) && !isset($schema_data['review'])) {
445 $opportunities[] = [
446 'type' => 'property_enhancement',
447 'title' => 'Add Customer Reviews',
448 'description' => 'Customer reviews can significantly improve click-through rates',
449 'impact' => 'high'
450 ];
451 }
452
453 return $opportunities;
454 }
455 }
456