| @@ -30,9 +30,9 @@ | ||
| 30 | 30 | * Private variable to hold the database version info. |
| 31 | 31 | * |
| 32 | 32 | * @var string DB version info. |
| 33 | 33 | */ |
| 34 | - protected $mainwp_db_version = '9.0.2.4'; // NOSONAR - no IP. 9.0.2.4 drops the stray unique index on backup progress task_id. Bumped for MWP-1566 sibling-dir chmod migration (MWP-1558 follow-up). Original 9.0.2.0 bump for MWP-1557/1558. | |
| 34 | + protected $mainwp_db_version = '9.0.2.5'; // NOSONAR - no IP. 9.0.2.4 drops the stray unique index on backup progress task_id. Bumped for MWP-1566 sibling-dir chmod migration (MWP-1558 follow-up). Original 9.0.2.0 bump for MWP-1557/1558. | |
| 35 | 35 | |
| 36 | 36 | /** |
| 37 | 37 | * Protected variable to hold the database option name. |
| 38 | 38 | * |
| @@ -45,8 +45,13 @@ | ||
| 45 | 45 | */ |
| 46 | 46 | const BACKUP_PROGRESS_INDEX_REPAIR_PENDING = 'mainwp_backup_progress_index_repair_pending'; |
| 47 | 47 | |
| 48 | 48 | /** |
| 49 | + * Network-scoped like mainwp_db_version, so every blog on a multisite sees the pending repair. | |
| 50 | + */ | |
| 51 | + const WP_GROUP_MISSING_PRIMARY_KEY_REPAIR_PENDING = 'mainwp_wp_group_missing_primary_key_repair_pending'; | |
| 52 | + | |
| 53 | + /** | |
| 49 | 54 | * Private static variable to hold the single instance of the class. |
| 50 | 55 | * |
| 51 | 56 | * @static |
| 52 | 57 | * |
| @@ -107,8 +112,10 @@ | ||
| 107 | 112 | // The index repair keeps its own marker so a failed DROP is retried at |
| 108 | 113 | // most hourly, without holding the DB version back and re-running every |
| 109 | 114 | // older migration on each load. |
| 110 | 115 | $this->maybe_retry_backup_progress_index_repair(); |
| 116 | + | |
| 117 | + $this->maybe_retry_repair_wp_group_primary_key(); | |
| 111 | 118 | return; |
| 112 | 119 | } |
| 113 | 120 | |
| 114 | 121 | $this->pre_update_tables(); |
| @@ -289,14 +296,12 @@ | ||
| 289 | 296 | wp_group_id int(11) NOT NULL auto_increment, |
| 290 | 297 | wpid int(11) NOT NULL, |
| 291 | 298 | groupid int(11) NOT NULL, |
| 292 | 299 | KEY idx_wpid (wpid), |
| 293 | - KEY idx_groupid (groupid)'; | |
| 294 | - if ( empty( $currentVersion ) || version_compare( $currentVersion, '8.57', '<=' ) ) { | |
| 295 | - $tbl .= ', | |
| 296 | - PRIMARY KEY (wp_group_id) '; | |
| 297 | - } | |
| 298 | - $tbl .= ') ' . $charset_collate; | |
| 300 | + KEY idx_groupid (groupid), | |
| 301 | + PRIMARY KEY (wp_group_id) | |
| 302 | + ) ' . $charset_collate; | |
| 303 | + | |
| 299 | 304 | $sql[] = $tbl; |
| 300 | 305 | |
| 301 | 306 | $tbl = 'CREATE TABLE ' . $this->table_name( 'lookup_item_objects' ) . ' ( |
| 302 | 307 | lookup_id bigint(20) unsigned NOT NULL auto_increment, |
| @@ -579,8 +584,12 @@ | ||
| 579 | 584 | if ( version_compare( $currentVersion, '9.0.2.4', '<' ) ) { // NOSONAR - no ip. |
| 580 | 585 | $this->repair_backup_progress_index(); |
| 581 | 586 | } |
| 582 | 587 | |
| 588 | + if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '9.0.2.5', '<' ) ) { // NOSONAR - no ip. | |
| 589 | + $this->repair_wp_group_primary_key(); | |
| 590 | + } | |
| 591 | + | |
| 583 | 592 | $this->wpdb->suppress_errors( $suppress ); |
| 584 | 593 | MainWP_DB_Client::instance()->check_to_updates_reports_data_861( $currentVersion ); |
| 585 | 594 | } |
| 586 | 595 | |
| @@ -627,8 +636,77 @@ | ||
| 627 | 636 | update_option( 'mainwp_notice_consumer_secret_migration_failed', current_time( 'mysql' ) ); |
| 628 | 637 | } else { |
| 629 | 638 | delete_option( 'mainwp_notice_consumer_secret_migration_failed' ); |
| 630 | 639 | } |
| 640 | + } | |
| 641 | + } | |
| 642 | + | |
| 643 | + /** | |
| 644 | + * Retry a pending backup progress index repair, no more than once an hour. | |
| 645 | + * | |
| 646 | + * A DB user without ALTER never clears the marker, so an unthrottled retry | |
| 647 | + * would run two SHOW INDEX plus a failing ALTER on every single request. | |
| 648 | + * | |
| 649 | + * @return bool True when a repair attempt was made. | |
| 650 | + */ | |
| 651 | + public function maybe_retry_repair_wp_group_primary_key() { | |
| 652 | + $pending = (int) get_site_option( self::WP_GROUP_MISSING_PRIMARY_KEY_REPAIR_PENDING ); | |
| 653 | + if ( empty( $pending ) || time() - $pending < HOUR_IN_SECONDS ) { | |
| 654 | + return false; | |
| 655 | + } | |
| 656 | + | |
| 657 | + $suppress = $this->wpdb->suppress_errors(); | |
| 658 | + $this->repair_wp_group_primary_key(); | |
| 659 | + $this->wpdb->suppress_errors( $suppress ); | |
| 660 | + | |
| 661 | + return true; | |
| 662 | + } | |
| 663 | + | |
| 664 | + /** | |
| 665 | + * Repair the wp_group table primary key for legacy installations. | |
| 666 | + * | |
| 667 | + * To fix MWP-1794. | |
| 668 | + * | |
| 669 | + * @return void | |
| 670 | + */ | |
| 671 | + private function repair_wp_group_primary_key() { | |
| 672 | + $wp_group_table = esc_sql( $this->table_name( 'wp_group' ) ); | |
| 673 | + | |
| 674 | + $column_exists = $this->wpdb->get_var( | |
| 675 | + "SHOW COLUMNS FROM {$wp_group_table} LIKE 'wp_group_id'" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped and query contains no user input. | |
| 676 | + ); | |
| 677 | + | |
| 678 | + if ( empty( $column_exists ) ) { | |
| 679 | + $this->wpdb->query( | |
| 680 | + "ALTER TABLE {$wp_group_table} | |
| 681 | + ADD COLUMN wp_group_id int(11) NOT NULL auto_increment PRIMARY KEY FIRST" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped and query contains no user input. | |
| 682 | + ); | |
| 683 | + } else { | |
| 684 | + $primary_key = $this->wpdb->get_var( | |
| 685 | + "SHOW INDEX FROM {$wp_group_table} | |
| 686 | + WHERE Key_name = 'PRIMARY' | |
| 687 | + AND Column_name = 'wp_group_id'" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped and query contains no user input. | |
| 688 | + ); | |
| 689 | + | |
| 690 | + if ( empty( $primary_key ) ) { | |
| 691 | + $this->wpdb->query( | |
| 692 | + "ALTER TABLE {$wp_group_table} | |
| 693 | + ADD PRIMARY KEY (wp_group_id)" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped and query contains no user input. | |
| 694 | + ); | |
| 695 | + } | |
| 696 | + } | |
| 697 | + | |
| 698 | + $primary_key = $this->wpdb->get_var( | |
| 699 | + "SHOW INDEX FROM {$wp_group_table} | |
| 700 | + WHERE Key_name = 'PRIMARY' | |
| 701 | + AND Column_name = 'wp_group_id'" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped and query contains no user input. | |
| 702 | + ); | |
| 703 | + | |
| 704 | + if ( ! empty( $primary_key ) ) { | |
| 705 | + delete_site_option( self::WP_GROUP_MISSING_PRIMARY_KEY_REPAIR_PENDING ); | |
| 706 | + } else { | |
| 707 | + // A timestamp, not a formatted date: the retry throttle compares it against time(). | |
| 708 | + update_site_option( self::WP_GROUP_MISSING_PRIMARY_KEY_REPAIR_PENDING, time() ); | |
| 631 | 709 | } |
| 632 | 710 | } |
| 633 | 711 | |
| 634 | 712 | /** |