# thinkrank/1.1.0/includes/seo/class-schema-optimizer.php

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.1.0. 456 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.1.0/code/includes/seo/class-schema-optimizer.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.1.0/raw/includes/seo/class-schema-optimizer.php
- Modified: 2025-08-10T07:35:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.1.0/code/includes/seo/class-schema-optimizer.php#L10-L20`.

```php
<?php
/**
 * Schema Optimizer Class
 *
 * Handles SEO optimization and recommendations for schema markup.
 * Extracted from Schema_Generator to follow Single Responsibility Principle.
 * Maintains exact same optimization logic and recommendation formats as original implementation.
 *
 * @package ThinkRank\SEO
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\SEO;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Schema Optimizer Class
 *
 * Provides SEO optimization recommendations and rich snippets enhancement.
 * Preserves all existing optimization logic and recommendation formats.
 *
 * @since 1.0.0
 */
class Schema_Optimizer {

    /**
     * Schema Factory instance for type specifications
     *
     * @since 1.0.0
     * @var Schema_Factory
     */
    private Schema_Factory $schema_factory;

    /**
     * Schema Validator instance for validation checks
     *
     * @since 1.0.0
     * @var Schema_Validator
     */
    private Schema_Validator $schema_validator;

    /**
     * Constructor
     *
     * @since 1.0.0
     */
    public function __construct() {
        // Ensure required classes are loaded
        if (!class_exists('ThinkRank\\SEO\\Schema_Factory')) {
            require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-schema-factory.php';
        }
        if (!class_exists('ThinkRank\\SEO\\Schema_Validator')) {
            require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-schema-validator.php';
        }
        $this->schema_factory = new Schema_Factory();
        $this->schema_validator = new Schema_Validator();
    }

    /**
     * Generate optimization recommendations for schema markup
     * PRESERVED: Maintains exact same optimization logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup to optimize
     * @param string $schema_type Schema type
     * @param array $content_data Original content data
     * @return array Optimization recommendations
     */
    public function generate_optimization_recommendations(array $schema_data, string $schema_type, array $content_data = []): array {
        $recommendations = [
            'seo_score' => 0,
            'completeness_score' => 0,
            'rich_snippets_potential' => 0,
            'recommendations' => [],
            'warnings' => [],
            'improvements' => [],
            'missing_properties' => [],
            'optimization_opportunities' => []
        ];

        // Get validation results first
        $validation = $this->schema_validator->validate_schema($schema_data);
        $recommendations['seo_score'] = $validation['score'];
        $recommendations['warnings'] = $validation['warnings'];

        // Calculate completeness score
        $recommendations['completeness_score'] = $this->calculate_completeness_score($schema_data, $schema_type);

        // Calculate rich snippets potential
        $recommendations['rich_snippets_potential'] = $this->calculate_rich_snippets_potential($schema_data, $schema_type);

        // Generate type-specific recommendations
        $recommendations = $this->add_type_specific_recommendations($schema_data, $schema_type, $recommendations);

        // Add general SEO recommendations
        $recommendations = $this->add_general_seo_recommendations($schema_data, $recommendations);

        // Identify missing properties
        $recommendations['missing_properties'] = $this->identify_missing_properties($schema_data, $schema_type);

        // Generate optimization opportunities
        $recommendations['optimization_opportunities'] = $this->generate_optimization_opportunities($schema_data, $schema_type, $content_data);

        return $recommendations;
    }

    /**
     * Calculate completeness score based on required and recommended properties
     * PRESERVED: Exact same calculation logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param string $schema_type Schema type
     * @return int Completeness score (0-100)
     */
    private function calculate_completeness_score(array $schema_data, string $schema_type): int {
        $spec = $this->schema_factory->get_schema_specification($schema_type);
        if (empty($spec)) {
            return 0;
        }

        $total_properties = count($spec['required']) + count($spec['recommended']);
        if ($total_properties === 0) {
            return 100;
        }

        $present_properties = 0;

        // Check required properties
        foreach ($spec['required'] as $required_prop) {
            if (isset($schema_data[$required_prop]) && !empty($schema_data[$required_prop])) {
                $present_properties++;
            }
        }

        // Check recommended properties
        foreach ($spec['recommended'] as $recommended_prop) {
            if (isset($schema_data[$recommended_prop]) && !empty($schema_data[$recommended_prop])) {
                $present_properties++;
            }
        }

        return (int) round(($present_properties / $total_properties) * 100);
    }

    /**
     * Calculate rich snippets potential score
     * PRESERVED: Exact same calculation logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param string $schema_type Schema type
     * @return int Rich snippets potential score (0-100)
     */
    private function calculate_rich_snippets_potential(array $schema_data, string $schema_type): int {
        $score = 0;

        // Base score for having structured data
        $score += 20;

        // Type-specific scoring
        switch ($schema_type) {
            case 'Article':
            case 'BlogPosting':
                if (isset($schema_data['headline'])) $score += 15;
                if (isset($schema_data['image'])) $score += 15;
                if (isset($schema_data['author'])) $score += 15;
                if (isset($schema_data['datePublished'])) $score += 15;
                if (isset($schema_data['publisher'])) $score += 20;
                break;

            case 'Product':
                if (isset($schema_data['name'])) $score += 15;
                if (isset($schema_data['image'])) $score += 15;
                if (isset($schema_data['offers'])) $score += 25;
                if (isset($schema_data['review']) || isset($schema_data['aggregateRating'])) $score += 25;
                if (isset($schema_data['brand'])) $score += 20;
                break;

            case 'Organization':
            case 'LocalBusiness':
                if (isset($schema_data['name'])) $score += 20;
                if (isset($schema_data['logo'])) $score += 20;
                if (isset($schema_data['address'])) $score += 20;
                if (isset($schema_data['telephone'])) $score += 20;
                if (isset($schema_data['openingHours'])) $score += 20;
                break;

            case 'FAQPage':
                if (isset($schema_data['mainEntity']) && is_array($schema_data['mainEntity'])) {
                    $question_count = count($schema_data['mainEntity']);
                    $score += min(60, $question_count * 15); // Max 60 points for questions
                }
                break;

            default:
                $score += 40; // Default scoring for other types
                break;
        }

        return min(100, $score);
    }

    /**
     * Add type-specific optimization recommendations
     * PRESERVED: Exact same recommendation logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param string $schema_type Schema type
     * @param array $recommendations Current recommendations
     * @return array Updated recommendations
     */
    private function add_type_specific_recommendations(array $schema_data, string $schema_type, array $recommendations): array {
        switch ($schema_type) {
            case 'Article':
            case 'BlogPosting':
                if (!isset($schema_data['image'])) {
                    $recommendations['recommendations'][] = 'Add a featured image to improve article rich snippets appearance';
                }
                if (!isset($schema_data['dateModified'])) {
                    $recommendations['recommendations'][] = 'Add dateModified to show content freshness';
                }
                if (!isset($schema_data['wordCount'])) {
                    $recommendations['recommendations'][] = 'Add word count for better content analysis';
                }
                break;

            case 'Product':
                if (!isset($schema_data['offers'])) {
                    $recommendations['recommendations'][] = 'Add pricing information to enable product rich snippets';
                }
                if (!isset($schema_data['review']) && !isset($schema_data['aggregateRating'])) {
                    $recommendations['recommendations'][] = 'Add customer reviews or ratings to improve product credibility';
                }
                if (!isset($schema_data['brand'])) {
                    $recommendations['recommendations'][] = 'Add brand information to improve product visibility';
                }
                if (!isset($schema_data['sku'])) {
                    $recommendations['recommendations'][] = 'Add SKU for better product identification';
                }
                break;

            case 'Organization':
            case 'LocalBusiness':
                if (!isset($schema_data['logo'])) {
                    $recommendations['recommendations'][] = 'Add a logo to improve brand recognition in search results';
                }
                if (!isset($schema_data['address'])) {
                    $recommendations['recommendations'][] = 'Add business address for better local SEO';
                }
                if (!isset($schema_data['telephone'])) {
                    $recommendations['recommendations'][] = 'Add phone number for better local search visibility';
                }
                if (!isset($schema_data['openingHours'])) {
                    $recommendations['recommendations'][] = 'Add business hours for better local search results';
                }
                break;

            case 'FAQPage':
                if (isset($schema_data['mainEntity']) && count($schema_data['mainEntity']) < 3) {
                    $recommendations['recommendations'][] = 'Add more FAQ questions (minimum 3) for better rich snippets';
                }
                break;

            case 'Event':
                if (!isset($schema_data['location'])) {
                    $recommendations['recommendations'][] = 'Add event location for better event rich snippets';
                }
                if (!isset($schema_data['startDate'])) {
                    $recommendations['recommendations'][] = 'Add event start date for proper event markup';
                }
                break;

            case 'HowTo':
                if (!isset($schema_data['step']) || count($schema_data['step']) < 3) {
                    $recommendations['recommendations'][] = 'Add detailed steps (minimum 3) for better how-to rich snippets';
                }
                if (!isset($schema_data['totalTime'])) {
                    $recommendations['recommendations'][] = 'Add total time estimate for better user experience';
                }
                break;
        }

        return $recommendations;
    }

    /**
     * Add general SEO recommendations
     * PRESERVED: Exact same recommendation logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param array $recommendations Current recommendations
     * @return array Updated recommendations
     */
    private function add_general_seo_recommendations(array $schema_data, array $recommendations): array {
        // Check for description
        if (!isset($schema_data['description'])) {
            $recommendations['recommendations'][] = 'Add a description to improve search result snippets';
        } elseif (strlen($schema_data['description']) < 120) {
            $recommendations['improvements'][] = 'Consider expanding the description (current: ' . strlen($schema_data['description']) . ' characters, recommended: 120-160)';
        }

        // Check for URL
        if (!isset($schema_data['url'])) {
            $recommendations['recommendations'][] = 'Add a URL to help search engines understand the content location';
        }

        // Check for image
        if (!isset($schema_data['image'])) {
            $recommendations['recommendations'][] = 'Add an image to improve visual appeal in search results';
        }

        // Check for name/title length
        if (isset($schema_data['name']) && strlen($schema_data['name']) > 60) {
            $recommendations['warnings'][] = 'Title/name is longer than 60 characters, may be truncated in search results';
        }

        if (isset($schema_data['headline']) && strlen($schema_data['headline']) > 110) {
            $recommendations['warnings'][] = 'Headline is longer than 110 characters, may be truncated in search results';
        }

        return $recommendations;
    }

    /**
     * Identify missing properties for schema type
     * PRESERVED: Exact same identification logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param string $schema_type Schema type
     * @return array Missing properties categorized by importance
     */
    private function identify_missing_properties(array $schema_data, string $schema_type): array {
        $spec = $this->schema_factory->get_schema_specification($schema_type);
        $missing = [
            'required' => [],
            'recommended' => [],
            'optional' => []
        ];

        if (empty($spec)) {
            return $missing;
        }

        // Check required properties
        foreach ($spec['required'] as $required_prop) {
            if (!isset($schema_data[$required_prop]) || empty($schema_data[$required_prop])) {
                $missing['required'][] = $required_prop;
            }
        }

        // Check recommended properties
        foreach ($spec['recommended'] as $recommended_prop) {
            if (!isset($schema_data[$recommended_prop]) || empty($schema_data[$recommended_prop])) {
                $missing['recommended'][] = $recommended_prop;
            }
        }

        // Check optional properties (sample of most important ones)
        $important_optional = array_slice($spec['optional'], 0, 5);
        foreach ($important_optional as $optional_prop) {
            if (!isset($schema_data[$optional_prop]) || empty($schema_data[$optional_prop])) {
                $missing['optional'][] = $optional_prop;
            }
        }

        return $missing;
    }

    /**
     * Generate optimization opportunities based on content analysis
     * PRESERVED: Exact same opportunity generation logic from original Schema_Generator
     *
     * @since 1.0.0
     *
     * @param array $schema_data Schema markup
     * @param string $schema_type Schema type
     * @param array $content_data Original content data
     * @return array Optimization opportunities
     */
    private function generate_optimization_opportunities(array $schema_data, string $schema_type, array $content_data): array {
        $opportunities = [];

        // Content-based opportunities
        if (!empty($content_data['content'])) {
            $content = $content_data['content'];

            // Check for FAQ content that could be structured
            if ($schema_type !== 'FAQPage' && preg_match('/\b(faq|frequently asked|question)\b/i', $content)) {
                $opportunities[] = [
                    'type' => 'schema_addition',
                    'title' => 'FAQ Schema Opportunity',
                    'description' => 'Content contains FAQ-style information that could benefit from FAQ schema markup',
                    'impact' => 'high'
                ];
            }

            // Check for how-to content
            if ($schema_type !== 'HowTo' && preg_match('/\b(how to|step|instruction|tutorial)\b/i', $content)) {
                $opportunities[] = [
                    'type' => 'schema_addition',
                    'title' => 'How-To Schema Opportunity',
                    'description' => 'Content contains instructional information that could benefit from HowTo schema markup',
                    'impact' => 'medium'
                ];
            }

            // Check for product information
            if ($schema_type !== 'Product' && preg_match('/\b(price|buy|purchase|\$[0-9])/i', $content)) {
                $opportunities[] = [
                    'type' => 'schema_addition',
                    'title' => 'Product Schema Opportunity',
                    'description' => 'Content contains product information that could benefit from Product schema markup',
                    'impact' => 'high'
                ];
            }
        }

        // Schema enhancement opportunities
        if ($schema_type === 'Article' && !isset($schema_data['keywords'])) {
            $opportunities[] = [
                'type' => 'property_enhancement',
                'title' => 'Add Keywords',
                'description' => 'Adding relevant keywords can improve content categorization',
                'impact' => 'low'
            ];
        }

        if (in_array($schema_type, ['Product', 'LocalBusiness']) && !isset($schema_data['review'])) {
            $opportunities[] = [
                'type' => 'property_enhancement',
                'title' => 'Add Customer Reviews',
                'description' => 'Customer reviews can significantly improve click-through rates',
                'impact' => 'high'
            ];
        }

        return $opportunities;
    }
}

```
