# thinkrank/1.0.0/includes/database/class-database-installer.php

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

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.0/code/includes/database/class-database-installer.php
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.0/raw/includes/database/class-database-installer.php
- Modified: 2025-08-10T07:57:44+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.0.0/code/includes/database/class-database-installer.php#L10-L20`.

```php
<?php
/**
 * Database Installer Class
 *
 * Handles database installation, updates, and management for ThinkRank SEO plugin.
 * Provides fresh installation approach for development with proper WordPress integration.
 *
 * @package ThinkRank
 * @subpackage Database
 * @since 1.0.0
 */

declare(strict_types=1);

namespace ThinkRank\Database;

/**
 * Database Installer Class
 *
 * Manages database installation and updates with WordPress hooks integration.
 * Implements fresh installation approach suitable for development environment.
 *
 * @since 1.0.0
 */
class Database_Installer {

    /**
     * Database schema instance
     *
     * @since 1.0.0
     * @var Database_Schema
     */
    private Database_Schema $schema;

    /**
     * Installation options
     *
     * @since 1.0.0
     * @var array
     */
    private array $options = [
        'preserve_data' => false, // Development mode: fresh install
        'backup_before_update' => false, // Development mode: no backup needed
        'auto_optimize' => true,
        'create_indexes' => true,
        'validate_schema' => true
    ];

    /**
     * Constructor
     *
     * @since 1.0.0
     */
    public function __construct() {
        $this->schema = new Database_Schema();
    }

    /**
     * Install database schema
     *
     * @since 1.0.0
     *
     * @param array $options Installation options
     * @return array Installation results
     */
    public function install(array $options = []): array {
        $this->options = array_merge($this->options, $options);

        $results = [
            'success' => false,
            'action' => 'install',
            'tables_created' => [],
            'tables_failed' => [],
            'errors' => [],
            'warnings' => [],
            'execution_time' => 0,
            'started_at' => current_time('mysql')
        ];

        $start_time = microtime(true);

        try {
            // Check if fresh installation is needed
            if ($this->needs_fresh_installation()) {
                $results['action'] = 'fresh_install';

                // Drop existing tables for fresh installation
                if (!$this->options['preserve_data']) {
                    $drop_results = $this->schema->drop_tables();
                    if (!$drop_results['success']) {
                        $results['warnings'][] = 'Some existing tables could not be dropped';
                    }
                }
            }

            // Create all tables
            $creation_results = $this->schema->create_tables();
            $results = array_merge($results, $creation_results);

            if ($creation_results['success']) {
                // Validate schema if enabled
                if ($this->options['validate_schema']) {
                    $validation_results = $this->validate_installation();
                    if (!$validation_results['success']) {
                        $results['warnings'] = array_merge($results['warnings'], $validation_results['errors']);
                    }
                }

                // Optimize tables if enabled
                if ($this->options['auto_optimize']) {
                    $optimization_results = $this->schema->optimize_tables();
                    if (!$optimization_results['success']) {
                        $results['warnings'][] = 'Table optimization failed';
                    }
                }

                // Set installation metadata
                $this->set_installation_metadata();

                $results['success'] = true;
            }

        } catch (\Exception $e) {
            $results['errors'][] = 'Installation failed: ' . $e->getMessage();
            $results['success'] = false;
        }

        $results['execution_time'] = round(microtime(true) - $start_time, 3);
        $results['completed_at'] = current_time('mysql');

        // Log installation results
        $this->log_installation_results($results);

        return $results;
    }

    /**
     * Uninstall database schema
     *
     * @since 1.0.0
     *
     * @return array Uninstallation results
     */
    public function uninstall(): array {
        $results = [
            'success' => false,
            'action' => 'uninstall',
            'tables_dropped' => [],
            'tables_failed' => [],
            'errors' => [],
            'execution_time' => 0,
            'started_at' => current_time('mysql')
        ];

        $start_time = microtime(true);

        try {
            // Drop all tables
            $drop_results = $this->schema->drop_tables();
            $results = array_merge($results, $drop_results);

            if ($drop_results['success']) {
                // Clean up installation metadata
                $this->cleanup_installation_metadata();
                $results['success'] = true;
            }

        } catch (\Exception $e) {
            $results['errors'][] = 'Uninstallation failed: ' . $e->getMessage();
            $results['success'] = false;
        }

        $results['execution_time'] = round(microtime(true) - $start_time, 3);
        $results['completed_at'] = current_time('mysql');

        return $results;
    }

    /**
     * Get installation status
     *
     * @since 1.0.0
     *
     * @return array Installation status
     */
    public function get_installation_status(): array {
        $status = [
            'installed' => false,
            'version' => get_option('thinkrank_seo_db_version', 'Not installed'),
            'needs_update' => false,
            'tables' => [],
            'total_records' => 0,
            'database_size' => 0,
            'last_optimized' => get_option('thinkrank_seo_last_optimized', 'Never'),
            'installation_date' => get_option('thinkrank_seo_db_created', 'Unknown')
        ];

        // Get database status from schema
        $db_status = $this->schema->get_database_status();
        $status = array_merge($status, $db_status);

        // Check if installation is complete based on actual existing tables vs expected total
        $counts = $this->schema->get_table_count_by_category();
        $expected_total = isset($counts['total']) ? (int) $counts['total'] : (int) count($status['tables']);
        $existing_count = 0;
        foreach ($status['tables'] as $info) {
            if (!empty($info['exists'])) {
                $existing_count++;
            }
        }
        $status['installed'] = ($existing_count === $expected_total);

        return $status;
    }

    /**
     * Repair database schema
     *
     * @since 1.0.0
     *
     * @return array Repair results
     */
    public function repair(): array {
        $results = [
            'success' => false,
            'action' => 'repair',
            'tables_repaired' => [],
            'tables_failed' => [],
            'errors' => [],
            'warnings' => [],
            'execution_time' => 0,
            'started_at' => current_time('mysql')
        ];

        $start_time = microtime(true);

        try {
            // Get current status
            $status = $this->get_installation_status();

            if (!$status['installed']) {
                // If not installed, perform fresh installation
                $install_results = $this->install();
                $results = array_merge($results, $install_results);
                $results['action'] = 'repair_install';
            } else {
                // Check each table and repair if needed
                foreach ($status['tables'] as $table_name => $table_info) {
                    if (!$table_info['exists']) {
                        // Recreate missing table
                        $creation_results = $this->recreate_table($table_name);
                        if ($creation_results['success']) {
                            $results['tables_repaired'][] = $table_name;
                        } else {
                            $results['tables_failed'][] = $table_name;
                            $results['errors'] = array_merge($results['errors'], $creation_results['errors']);
                        }
                    }
                }

                // Optimize repaired tables
                $optimization_results = $this->schema->optimize_tables();
                if (!$optimization_results['success']) {
                    $results['warnings'][] = 'Table optimization failed during repair';
                }

                $results['success'] = empty($results['tables_failed']);
            }

        } catch (\Exception $e) {
            $results['errors'][] = 'Repair failed: ' . $e->getMessage();
            $results['success'] = false;
        }

        $results['execution_time'] = round(microtime(true) - $start_time, 3);
        $results['completed_at'] = current_time('mysql');

        return $results;
    }


	    /**
	     * Check explicit allowance for fresh installation (safer than WP_DEBUG)
	     *
	     * @since 1.0.0
	     * @return bool
	     */
	    private function allow_fresh_install(): bool {
	        if (defined('THINKRANK_ALLOW_FRESH_INSTALL') && THINKRANK_ALLOW_FRESH_INSTALL) {
	            return true;
	        }
	        return (bool) get_option('thinkrank_allow_fresh_install', false);
	    }

    /**
     * Check if fresh installation is needed
     *
     * @since 1.0.0
     *
     * @return bool True if fresh installation needed
     */
    private function needs_fresh_installation(): bool {
        // Explicitly allow fresh installation only if opted-in via constant or option
        if ($this->allow_fresh_install()) {
            return true;
        }

        // Check if any tables exist
        $status = $this->get_installation_status();
        return !$status['installed'] || $status['needs_update'];
    }

    /**
     * Validate installation
     *
     * @since 1.0.0
     *
     * @return array Validation results
     */
    private function validate_installation(): array {
        $validation = [
            'success' => true,
            'errors' => [],
            'warnings' => []
        ];

        $status = $this->get_installation_status();

        // Check if all required tables exist
        $counts = $this->schema->get_table_count_by_category();
        $expected_total = isset($counts['total']) ? (int) $counts['total'] : (int) count($status['tables']);
        $existing_count = 0;
        foreach ($status['tables'] as $info) {
            if (!empty($info['exists'])) {
                $existing_count++;
            }
        }
        if ($existing_count !== $expected_total) {
            $validation['errors'][] = 'Not all required tables were created';
            $validation['success'] = false;
        }

        // Check each table
        foreach ($status['tables'] as $table_name => $table_info) {
            if (!$table_info['exists']) {
                $validation['errors'][] = "Table {$table_name} does not exist";
                $validation['success'] = false;
            }
        }

        return $validation;
    }

    /**
     * Recreate a specific table
     *
     * @since 1.0.0
     *
     * @param string $table_name Table name to recreate
     * @return array Recreation results
     */
    private function recreate_table(string $table_name): array {
        $results = [
            'success' => false,
            'errors' => []
        ];

        try {
            // This would require individual table creation logic
            // For now, trigger full recreation
            $creation_results = $this->schema->create_tables();
            $results['success'] = $creation_results['success'];
            $results['errors'] = $creation_results['errors'];
        } catch (\Exception $e) {
            $results['errors'][] = "Failed to recreate table {$table_name}: " . $e->getMessage();
        }

        return $results;
    }

    /**
     * Set installation metadata
     *
     * @since 1.0.0
     */
    private function set_installation_metadata(): void {
        update_option('thinkrank_seo_installed', true);
        update_option('thinkrank_seo_installation_date', current_time('mysql'));
        update_option('thinkrank_seo_last_optimized', current_time('mysql'));
    }

    /**
     * Clean up installation metadata
     *
     * @since 1.0.0
     */
    private function cleanup_installation_metadata(): void {
        delete_option('thinkrank_seo_installed');
        delete_option('thinkrank_seo_installation_date');
        delete_option('thinkrank_seo_last_optimized');
        delete_option('thinkrank_seo_db_version');
        delete_option('thinkrank_seo_db_created');
    }

    /**
     * Log installation results
     *
     * @since 1.0.0
     *
     * @param array $results Installation results
     */
    private function log_installation_results(array $results): void {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $log_entry = [
                'timestamp' => current_time('mysql'),
                'action' => $results['action'],
                'success' => $results['success'],
                'execution_time' => $results['execution_time'],
                'tables_created' => count($results['tables_created']),
                'errors' => count($results['errors'])
            ];

            $existing_log = get_option('thinkrank_seo_installation_log', []);
            $existing_log[] = $log_entry;

            // Keep only last 10 entries
            $existing_log = array_slice($existing_log, -10);

            update_option('thinkrank_seo_installation_log', $existing_log);
        }
    }
}

```
