PluginProbe
Redirection / 5.6.0
Redirection v5.6.0
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / database / database-status.php

database-status.php in Redirection 5.6.0, at database/database-status.php

548 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @phpstan-type DatabaseStatus array{
5 * status: string|false,
6 * inProgress: bool,
7 * current?: string,
8 * next?: string,
9 * time?: float,
10 * manual?: array<int, string>,
11 * result?: 'ok'|'error',
12 * reason?: string|false,
13 * debug?: array<int, string>,
14 * complete?: int|float
15 * }
16 */
17 class Red_Database_Status {
18 // Used in < 3.7 versions of Redirection, but since migrated to general settings
19 const OLD_DB_VERSION = 'redirection_version';
20 const DB_UPGRADE_STAGE = 'database_stage';
21
22 const RESULT_OK = 'ok';
23 const RESULT_ERROR = 'error';
24
25 const STATUS_OK = 'ok';
26 const STATUS_NEED_INSTALL = 'need-install';
27 const STATUS_NEED_UPDATING = 'need-update';
28 const STATUS_FINISHED_INSTALL = 'finish-install';
29 const STATUS_FINISHED_UPDATING = 'finish-update';
30
31 /**
32 * Current upgrade stage
33 *
34 * @var string|false
35 */
36 private $stage = false;
37
38 /**
39 * List of all upgrade stages
40 *
41 * @var array<int, string>
42 */
43 private $stages = [];
44
45 /**
46 * Current database status
47 *
48 * @var string|false
49 */
50 private $status = false;
51
52 /**
53 * Result of last operation
54 *
55 * @var string|false
56 */
57 private $result = false;
58
59 /**
60 * Reason for current status
61 *
62 * @var string|false
63 */
64 private $reason = false;
65
66 /**
67 * Debug information
68 *
69 * @var array<int, string>
70 */
71 private $debug = [];
72
73 public function __construct() {
74 $this->status = self::STATUS_OK;
75
76 if ( $this->needs_installing() ) {
77 $this->status = self::STATUS_NEED_INSTALL;
78 }
79
80 $this->load_stage();
81
82 if ( $this->needs_updating() ) {
83 $this->status = self::STATUS_NEED_UPDATING;
84 }
85 }
86
87 /**
88 * Load current upgrade stage from options
89 *
90 * @return void
91 */
92 public function load_stage(): void {
93 $settings = Red_Options::get();
94
95 if ( isset( $settings[ self::DB_UPGRADE_STAGE ] ) ) {
96 $this->stage = isset( $settings[ self::DB_UPGRADE_STAGE ]['stage'] ) ? $settings[ self::DB_UPGRADE_STAGE ]['stage'] : false;
97 $this->stages = isset( $settings[ self::DB_UPGRADE_STAGE ]['stages'] ) ? $settings[ self::DB_UPGRADE_STAGE ]['stages'] : [];
98 $this->status = isset( $settings[ self::DB_UPGRADE_STAGE ]['status'] ) ? $settings[ self::DB_UPGRADE_STAGE ]['status'] : false;
99 }
100 }
101
102 /**
103 * Does the database need install
104 *
105 * @return bool true if needs installing, false otherwise
106 */
107 public function needs_installing(): bool {
108 $settings = Red_Options::get();
109
110 if ( $settings['database'] === '' && $this->get_old_version() === false ) {
111 return true;
112 }
113
114 return false;
115 }
116
117 /**
118 * Does the current database need updating to the target
119 *
120 * @return bool true if needs updating, false otherwise
121 */
122 public function needs_updating(): bool {
123 // We need updating if we don't need to install, and the current version is less than target version
124 if ( $this->needs_installing() === false && version_compare( $this->get_current_version(), REDIRECTION_DB_VERSION, '<' ) ) {
125 return true;
126 }
127
128 // Also if we're still in the process of upgrading
129 if ( $this->get_current_stage() !== false && $this->status !== self::STATUS_NEED_INSTALL ) {
130 return true;
131 }
132
133 return false;
134 }
135
136 /**
137 * Get current database version
138 *
139 * @return string Current database version
140 */
141 public function get_current_version(): string {
142 $settings = Red_Options::get();
143
144 if ( $settings['database'] !== '' ) {
145 if ( $settings['database'] === '+OK' ) {
146 return REDIRECTION_DB_VERSION;
147 }
148
149 return $settings['database'];
150 }
151
152 if ( $this->get_old_version() !== false ) {
153 $version = $this->get_old_version();
154
155 // Upgrade the old value
156 red_set_options( array( 'database' => $version ) );
157 delete_option( self::OLD_DB_VERSION );
158 $this->clear_cache();
159 return $version;
160 }
161
162 return '';
163 }
164
165 /**
166 * Get old database version from legacy option
167 *
168 * @return string|false
169 */
170 private function get_old_version() {
171 return get_option( self::OLD_DB_VERSION );
172 }
173
174 /**
175 * Check if required database tables exist
176 *
177 * @return void
178 */
179 public function check_tables_exist(): void {
180 $latest = Red_Database::get_latest_database();
181 $missing = $latest->get_missing_tables();
182
183 // No tables installed - do a fresh install
184 if ( count( $missing ) === count( $latest->get_all_tables() ) ) {
185 delete_option( self::OLD_DB_VERSION );
186 red_set_options( [ 'database' => '' ] );
187 $this->clear_cache();
188
189 $this->status = self::STATUS_NEED_INSTALL;
190 $this->stop_update();
191 } elseif ( count( $missing ) > 0 && version_compare( $this->get_current_version(), '2.3.3', 'ge' ) ) {
192 // Some tables are missing - try and fill them in
193 $latest->install();
194 }
195 }
196
197 /**
198 * Does the current database support a particular version
199 *
200 * @param string $version Target version
201 * @return bool true if supported, false otherwise
202 */
203 public function does_support( string $version ): bool {
204 return version_compare( $this->get_current_version(), $version, 'ge' );
205 }
206
207 /**
208 * Check if last operation resulted in error
209 *
210 * @return bool
211 */
212 public function is_error(): bool {
213 return $this->result === self::RESULT_ERROR;
214 }
215
216 /**
217 * Set error status
218 *
219 * @param string $error Error message.
220 * @return void
221 */
222 public function set_error( string $error ): void {
223 global $wpdb;
224
225 $this->result = self::RESULT_ERROR;
226 $this->reason = str_replace( "\t", ' ', $error );
227
228 if ( $wpdb->last_error ) {
229 $this->debug[] = $wpdb->last_error;
230
231 if ( strpos( $wpdb->last_error, 'command denied to user' ) !== false ) {
232 $this->reason .= ' - ' . __( 'Insufficient database permissions detected. Please give your database user appropriate permissions.', 'redirection' );
233 }
234 }
235
236 $latest = Red_Database::get_latest_database();
237 $this->debug = array_merge( $this->debug, $latest->get_table_schema() );
238 $this->debug[] = 'Stage: ' . $this->get_current_stage();
239 }
240
241 /**
242 * Set success status
243 *
244 * @param string $reason Success message.
245 * @return void
246 */
247 public function set_ok( string $reason ): void {
248 $this->reason = $reason;
249 $this->result = self::RESULT_OK;
250 $this->debug = [];
251 }
252
253 /**
254 * Stop current upgrade
255 *
256 * @return void
257 */
258 public function stop_update(): void {
259 $this->stage = false;
260 $this->stages = [];
261 $this->debug = [];
262
263 red_set_options( [ self::DB_UPGRADE_STAGE => false ] );
264 $this->clear_cache();
265 }
266
267 /**
268 * Finish upgrade process
269 *
270 * @return void
271 */
272 public function finish(): void {
273 $this->stop_update();
274
275 if ( $this->status === self::STATUS_NEED_INSTALL ) {
276 $this->status = self::STATUS_FINISHED_INSTALL;
277 } elseif ( $this->status === self::STATUS_NEED_UPDATING ) {
278 $this->status = self::STATUS_FINISHED_UPDATING;
279 }
280 }
281
282 /**
283 * Get current upgrade stage
284 *
285 * @return string|false Current stage name, or false if not upgrading
286 */
287 public function get_current_stage() {
288 return $this->stage;
289 }
290
291 /**
292 * Move current stage on to the next
293 *
294 * @return void
295 */
296 public function set_next_stage(): void {
297 $this->debug = [];
298 $stage = $this->get_current_stage();
299
300 if ( $stage !== false ) {
301 $stage = $this->get_next_stage( $stage );
302
303 // Save next position
304 if ( $stage !== false ) {
305 $this->set_stage( $stage );
306 } else {
307 $this->finish();
308 }
309 }
310 }
311
312 /**
313 * Get current upgrade status
314 *
315 * @return DatabaseStatus Database status array
316 */
317 public function get_json() {
318 // Base information
319 $result = [
320 'status' => $this->status,
321 'inProgress' => $this->stage !== false,
322 ];
323
324 // Add on version status
325 if ( $this->status === self::STATUS_NEED_INSTALL || $this->status === self::STATUS_NEED_UPDATING ) {
326 $result = array_merge(
327 $result,
328 $this->get_version_upgrade(),
329 [ 'manual' => $this->get_manual_upgrade() ]
330 );
331 }
332
333 // Add on upgrade status
334 if ( $this->is_error() ) {
335 $result = array_merge( $result, $this->get_version_upgrade(), $this->get_progress_status(), $this->get_error_status() );
336 } elseif ( $result['inProgress'] ) {
337 $result = array_merge( $result, $this->get_progress_status() );
338 } elseif ( $this->status === self::STATUS_FINISHED_INSTALL || $this->status === self::STATUS_FINISHED_UPDATING ) {
339 $result['complete'] = 100;
340 $result['reason'] = $this->reason;
341 }
342
343 return $result;
344 }
345
346 /**
347 * Get error status information
348 *
349 * @phpstan-return array{reason: string|false, result: 'error', debug: array<int, string>}
350 * @return array<string, mixed>
351 */
352 private function get_error_status(): array {
353 return [
354 'reason' => $this->reason,
355 'result' => self::RESULT_ERROR,
356 'debug' => $this->debug,
357 ];
358 }
359
360 /**
361 * Get progress status information
362 *
363 * @return array{complete: int|float, result: 'ok', reason: string|false}
364 */
365 private function get_progress_status() {
366 $complete = 0;
367
368 if ( $this->stage !== false ) {
369 $total = count( $this->stages );
370 $pos = array_search( $this->stage, $this->stages, true );
371
372 if ( $pos !== false && $total > 0 ) {
373 $complete = round( ( $pos / $total ) * 100, 1 );
374 }
375 }
376
377 return [
378 'complete' => $complete,
379 'result' => self::RESULT_OK,
380 'reason' => $this->reason,
381 ];
382 }
383
384 /**
385 * Get version upgrade information
386 *
387 * @phpstan-return array{current: string, next: string, time: float}
388 * @return array<string, mixed>
389 */
390 private function get_version_upgrade(): array {
391 return [
392 'current' => $this->get_current_version() ? $this->get_current_version() : '-',
393 'next' => REDIRECTION_DB_VERSION,
394 'time' => microtime( true ),
395 ];
396 }
397
398 /**
399 * Set the status information for a database upgrade
400 *
401 * @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
402 * @return void
403 */
404 public function start_install( array $upgrades ) {
405 $this->set_stages( $upgrades );
406 $this->status = self::STATUS_NEED_INSTALL;
407 }
408
409 /**
410 * Start database upgrade process
411 *
412 * @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
413 * @return void
414 */
415 public function start_upgrade( array $upgrades ) {
416 $this->set_stages( $upgrades );
417 $this->status = self::STATUS_NEED_UPDATING;
418 }
419
420 /**
421 * Set upgrade stages
422 *
423 * @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
424 * @return void
425 */
426 private function set_stages( array $upgrades ) {
427 $this->stages = [];
428
429 foreach ( $upgrades as $upgrade ) {
430 $upgrader = Red_Database_Upgrader::get( $upgrade );
431 $this->stages = array_merge( $this->stages, array_keys( $upgrader->get_stages() ) );
432 }
433
434 if ( count( $this->stages ) > 0 ) {
435 $this->set_stage( $this->stages[0] );
436 }
437 }
438
439 /**
440 * @param string|false $stage
441 * @return void
442 */
443 public function set_stage( $stage ): void {
444 $this->stage = $stage;
445 $this->save_details();
446 }
447
448 /**
449 * @return void
450 */
451 private function save_details(): void {
452 $stages = [
453 self::DB_UPGRADE_STAGE => [
454 'stage' => $this->stage,
455 'stages' => $this->stages,
456 'status' => $this->status,
457 ],
458 ];
459
460 red_set_options( $stages );
461
462 $this->clear_cache();
463 }
464
465 /**
466 * @return array<int, string>
467 */
468 private function get_manual_upgrade(): array {
469 $queries = [];
470 $database = new Red_Database();
471 $upgraders = $database->get_upgrades_for_version( $this->get_current_version(), false );
472
473 foreach ( $upgraders as $upgrade ) {
474 $upgrade = Red_Database_Upgrader::get( $upgrade );
475
476 $stages = $upgrade->get_stages();
477 foreach ( array_keys( $stages ) as $stage ) {
478 $queries = array_merge( $queries, $upgrade->get_queries_for_stage( $stage ) );
479 }
480 }
481
482 return $queries;
483 }
484
485 /**
486 * @param string $stage
487 * @return string|false
488 */
489 private function get_next_stage( string $stage ) {
490 $database = new Red_Database();
491 $upgraders = $database->get_upgrades_for_version( $this->get_current_version(), $this->get_current_stage() );
492
493 if ( count( $upgraders ) === 0 ) {
494 $upgraders = $database->get_upgrades_for_version( $this->get_current_version(), false );
495 }
496
497 if ( count( $upgraders ) === 0 ) {
498 return false;
499 }
500
501 $upgrader = Red_Database_Upgrader::get( $upgraders[0] );
502
503 // Where are we in this?
504 $pos = is_string( $this->stage ) ? array_search( $this->stage, $this->stages, true ) : false;
505
506 if ( $pos === false ) {
507 return false;
508 }
509
510 if ( $pos === count( $this->stages ) - 1 ) {
511 $this->save_db_version( REDIRECTION_DB_VERSION );
512 return false;
513 }
514
515 // Set current DB version
516 $current_stages = array_keys( $upgrader->get_stages() );
517
518 $current_position = is_string( $this->stage ) ? array_search( $this->stage, $current_stages, true ) : false;
519
520 if ( $current_position !== false && $current_position === count( $current_stages ) - 1 && isset( $upgraders[1] ) ) {
521 $this->save_db_version( $upgraders[1]->get_version() );
522 }
523
524 // Move on to next in current version
525 return $this->stages[ $pos + 1 ];
526 }
527
528 /**
529 * @param string $version
530 * @return void
531 */
532 public function save_db_version( string $version ): void {
533 red_set_options( array( 'database' => $version ) );
534 delete_option( self::OLD_DB_VERSION );
535
536 $this->clear_cache();
537 }
538
539 /**
540 * @return void
541 */
542 private function clear_cache(): void {
543 if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) && function_exists( 'wp_cache_flush' ) ) {
544 wp_cache_flush();
545 }
546 }
547 }
548