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); } } }