PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.1
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.1
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 / database / class-database-installer.php

class-database-installer.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.1, at includes/database/class-database-installer.php

433 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Database Installer Class
4 *
5 * Handles database installation, updates, and management for ThinkRank SEO plugin.
6 * Provides fresh installation approach for development with proper WordPress integration.
7 *
8 * @package ThinkRank
9 * @subpackage Database
10 * @since 1.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\Database;
16
17 /**
18 * Database Installer Class
19 *
20 * Manages database installation and updates with WordPress hooks integration.
21 * Implements fresh installation approach suitable for development environment.
22 *
23 * @since 1.0.0
24 */
25 class Database_Installer {
26
27 /**
28 * Database schema instance
29 *
30 * @since 1.0.0
31 * @var Database_Schema
32 */
33 private Database_Schema $schema;
34
35 /**
36 * Installation options
37 *
38 * @since 1.0.0
39 * @var array
40 */
41 private array $options = [
42 'preserve_data' => false, // Development mode: fresh install
43 'backup_before_update' => false, // Development mode: no backup needed
44 'auto_optimize' => true,
45 'create_indexes' => true,
46 'validate_schema' => true
47 ];
48
49 /**
50 * Constructor
51 *
52 * @since 1.0.0
53 */
54 public function __construct() {
55 $this->schema = new Database_Schema();
56 }
57
58 /**
59 * Install database schema
60 *
61 * @since 1.0.0
62 *
63 * @param array $options Installation options
64 * @return array Installation results
65 */
66 public function install(array $options = []): array {
67 $this->options = array_merge($this->options, $options);
68
69 $results = [
70 'success' => false,
71 'action' => 'install',
72 'tables_created' => [],
73 'tables_failed' => [],
74 'errors' => [],
75 'warnings' => [],
76 'execution_time' => 0,
77 'started_at' => current_time('mysql')
78 ];
79
80 $start_time = microtime(true);
81
82 try {
83 // Check if fresh installation is needed
84 if ($this->needs_fresh_installation()) {
85 $results['action'] = 'fresh_install';
86
87 // Drop existing tables for fresh installation
88 if (!$this->options['preserve_data']) {
89 $drop_results = $this->schema->drop_tables();
90 if (!$drop_results['success']) {
91 $results['warnings'][] = 'Some existing tables could not be dropped';
92 }
93 }
94 }
95
96 // Create all tables
97 $creation_results = $this->schema->create_tables();
98 $results = array_merge($results, $creation_results);
99
100 if ($creation_results['success']) {
101 // Validate schema if enabled
102 if ($this->options['validate_schema']) {
103 $validation_results = $this->validate_installation();
104 if (!$validation_results['success']) {
105 $results['warnings'] = array_merge($results['warnings'], $validation_results['errors']);
106 }
107 }
108
109 // Optimize tables if enabled
110 if ($this->options['auto_optimize']) {
111 $optimization_results = $this->schema->optimize_tables();
112 if (!$optimization_results['success']) {
113 $results['warnings'][] = 'Table optimization failed';
114 }
115 }
116
117 // Set installation metadata
118 $this->set_installation_metadata();
119
120 $results['success'] = true;
121 }
122
123 } catch (\Exception $e) {
124 $results['errors'][] = 'Installation failed: ' . $e->getMessage();
125 $results['success'] = false;
126 }
127
128 $results['execution_time'] = round(microtime(true) - $start_time, 3);
129 $results['completed_at'] = current_time('mysql');
130
131 // Log installation results
132 $this->log_installation_results($results);
133
134 return $results;
135 }
136
137 /**
138 * Uninstall database schema
139 *
140 * @since 1.0.0
141 *
142 * @return array Uninstallation results
143 */
144 public function uninstall(): array {
145 $results = [
146 'success' => false,
147 'action' => 'uninstall',
148 'tables_dropped' => [],
149 'tables_failed' => [],
150 'errors' => [],
151 'execution_time' => 0,
152 'started_at' => current_time('mysql')
153 ];
154
155 $start_time = microtime(true);
156
157 try {
158 // Drop all tables
159 $drop_results = $this->schema->drop_tables();
160 $results = array_merge($results, $drop_results);
161
162 if ($drop_results['success']) {
163 // Clean up installation metadata
164 $this->cleanup_installation_metadata();
165 $results['success'] = true;
166 }
167
168 } catch (\Exception $e) {
169 $results['errors'][] = 'Uninstallation failed: ' . $e->getMessage();
170 $results['success'] = false;
171 }
172
173 $results['execution_time'] = round(microtime(true) - $start_time, 3);
174 $results['completed_at'] = current_time('mysql');
175
176 return $results;
177 }
178
179 /**
180 * Get installation status
181 *
182 * @since 1.0.0
183 *
184 * @return array Installation status
185 */
186 public function get_installation_status(): array {
187 $status = [
188 'installed' => false,
189 'version' => get_option('thinkrank_seo_db_version', 'Not installed'),
190 'needs_update' => false,
191 'tables' => [],
192 'total_records' => 0,
193 'database_size' => 0,
194 'last_optimized' => get_option('thinkrank_seo_last_optimized', 'Never'),
195 'installation_date' => get_option('thinkrank_seo_db_created', 'Unknown')
196 ];
197
198 // Get database status from schema
199 $db_status = $this->schema->get_database_status();
200 $status = array_merge($status, $db_status);
201
202 // Check if installation is complete based on actual existing tables vs expected total
203 $counts = $this->schema->get_table_count_by_category();
204 $expected_total = isset($counts['total']) ? (int) $counts['total'] : (int) count($status['tables']);
205 $existing_count = 0;
206 foreach ($status['tables'] as $info) {
207 if (!empty($info['exists'])) {
208 $existing_count++;
209 }
210 }
211 $status['installed'] = ($existing_count === $expected_total);
212
213 return $status;
214 }
215
216 /**
217 * Repair database schema
218 *
219 * @since 1.0.0
220 *
221 * @return array Repair results
222 */
223 public function repair(): array {
224 $results = [
225 'success' => false,
226 'action' => 'repair',
227 'tables_repaired' => [],
228 'tables_failed' => [],
229 'errors' => [],
230 'warnings' => [],
231 'execution_time' => 0,
232 'started_at' => current_time('mysql')
233 ];
234
235 $start_time = microtime(true);
236
237 try {
238 // Get current status
239 $status = $this->get_installation_status();
240
241 if (!$status['installed']) {
242 // If not installed, perform fresh installation
243 $install_results = $this->install();
244 $results = array_merge($results, $install_results);
245 $results['action'] = 'repair_install';
246 } else {
247 // Check each table and repair if needed
248 foreach ($status['tables'] as $table_name => $table_info) {
249 if (!$table_info['exists']) {
250 // Recreate missing table
251 $creation_results = $this->recreate_table($table_name);
252 if ($creation_results['success']) {
253 $results['tables_repaired'][] = $table_name;
254 } else {
255 $results['tables_failed'][] = $table_name;
256 $results['errors'] = array_merge($results['errors'], $creation_results['errors']);
257 }
258 }
259 }
260
261 // Optimize repaired tables
262 $optimization_results = $this->schema->optimize_tables();
263 if (!$optimization_results['success']) {
264 $results['warnings'][] = 'Table optimization failed during repair';
265 }
266
267 $results['success'] = empty($results['tables_failed']);
268 }
269
270 } catch (\Exception $e) {
271 $results['errors'][] = 'Repair failed: ' . $e->getMessage();
272 $results['success'] = false;
273 }
274
275 $results['execution_time'] = round(microtime(true) - $start_time, 3);
276 $results['completed_at'] = current_time('mysql');
277
278 return $results;
279 }
280
281
282 /**
283 * Check explicit allowance for fresh installation (safer than WP_DEBUG)
284 *
285 * @since 1.0.0
286 * @return bool
287 */
288 private function allow_fresh_install(): bool {
289 if (defined('THINKRANK_ALLOW_FRESH_INSTALL') && THINKRANK_ALLOW_FRESH_INSTALL) {
290 return true;
291 }
292 return (bool) get_option('thinkrank_allow_fresh_install', false);
293 }
294
295 /**
296 * Check if fresh installation is needed
297 *
298 * @since 1.0.0
299 *
300 * @return bool True if fresh installation needed
301 */
302 private function needs_fresh_installation(): bool {
303 // Explicitly allow fresh installation only if opted-in via constant or option
304 if ($this->allow_fresh_install()) {
305 return true;
306 }
307
308 // Check if any tables exist
309 $status = $this->get_installation_status();
310 return !$status['installed'] || $status['needs_update'];
311 }
312
313 /**
314 * Validate installation
315 *
316 * @since 1.0.0
317 *
318 * @return array Validation results
319 */
320 private function validate_installation(): array {
321 $validation = [
322 'success' => true,
323 'errors' => [],
324 'warnings' => []
325 ];
326
327 $status = $this->get_installation_status();
328
329 // Check if all required tables exist
330 $counts = $this->schema->get_table_count_by_category();
331 $expected_total = isset($counts['total']) ? (int) $counts['total'] : (int) count($status['tables']);
332 $existing_count = 0;
333 foreach ($status['tables'] as $info) {
334 if (!empty($info['exists'])) {
335 $existing_count++;
336 }
337 }
338 if ($existing_count !== $expected_total) {
339 $validation['errors'][] = 'Not all required tables were created';
340 $validation['success'] = false;
341 }
342
343 // Check each table
344 foreach ($status['tables'] as $table_name => $table_info) {
345 if (!$table_info['exists']) {
346 $validation['errors'][] = "Table {$table_name} does not exist";
347 $validation['success'] = false;
348 }
349 }
350
351 return $validation;
352 }
353
354 /**
355 * Recreate a specific table
356 *
357 * @since 1.0.0
358 *
359 * @param string $table_name Table name to recreate
360 * @return array Recreation results
361 */
362 private function recreate_table(string $table_name): array {
363 $results = [
364 'success' => false,
365 'errors' => []
366 ];
367
368 try {
369 // This would require individual table creation logic
370 // For now, trigger full recreation
371 $creation_results = $this->schema->create_tables();
372 $results['success'] = $creation_results['success'];
373 $results['errors'] = $creation_results['errors'];
374 } catch (\Exception $e) {
375 $results['errors'][] = "Failed to recreate table {$table_name}: " . $e->getMessage();
376 }
377
378 return $results;
379 }
380
381 /**
382 * Set installation metadata
383 *
384 * @since 1.0.0
385 */
386 private function set_installation_metadata(): void {
387 update_option('thinkrank_seo_installed', true);
388 update_option('thinkrank_seo_installation_date', current_time('mysql'));
389 update_option('thinkrank_seo_last_optimized', current_time('mysql'));
390 }
391
392 /**
393 * Clean up installation metadata
394 *
395 * @since 1.0.0
396 */
397 private function cleanup_installation_metadata(): void {
398 delete_option('thinkrank_seo_installed');
399 delete_option('thinkrank_seo_installation_date');
400 delete_option('thinkrank_seo_last_optimized');
401 delete_option('thinkrank_seo_db_version');
402 delete_option('thinkrank_seo_db_created');
403 }
404
405 /**
406 * Log installation results
407 *
408 * @since 1.0.0
409 *
410 * @param array $results Installation results
411 */
412 private function log_installation_results(array $results): void {
413 if (defined('WP_DEBUG') && WP_DEBUG) {
414 $log_entry = [
415 'timestamp' => current_time('mysql'),
416 'action' => $results['action'],
417 'success' => $results['success'],
418 'execution_time' => $results['execution_time'],
419 'tables_created' => count($results['tables_created']),
420 'errors' => count($results['errors'])
421 ];
422
423 $existing_log = get_option('thinkrank_seo_installation_log', []);
424 $existing_log[] = $log_entry;
425
426 // Keep only last 10 entries
427 $existing_log = array_slice($existing_log, -10);
428
429 update_option('thinkrank_seo_installation_log', $existing_log);
430 }
431 }
432 }
433