|null $migrations Ordered, immutable migrations. */ public function __construct( private readonly \wpdb $wpdb, private readonly ?array $migrations = null ) {} public function migrate(): void { $wpdb = $this->wpdb; if (!\ABeCe\Booking\Support\DatabaseCompatibility::inspect($wpdb)['supported']) { throw new RuntimeException(\ABeCe\Booking\Support\DatabaseCompatibility::message($wpdb)); } Tables::validate_prefix($wpdb); $layout = DatabaseState::layout($wpdb); if (self::LATEST_VERSION === $this->installedVersion()) { if (DatabaseState::ready($wpdb)) return; throw new RuntimeException('ABECE_BOOKING_INCOMPLETE_SCHEMA13_JOURNAL'); } if ($layout['old'] > 0 || ($layout['state'] !== 'empty' && !get_option(DatabaseState::FRESH))) { throw new RuntimeException(__('ABeCe Booking needs a verified database maintenance upgrade before booking operations can resume. Existing data has been retained. Follow the database upgrade instructions or contact your administrator.','abece-booking')); } if ($this->installedVersion() === 0 && $layout['state'] === 'empty') { if (DatabaseState::has_history($wpdb)) throw new RuntimeException('ABECE_BOOKING_PRIOR_INSTALLATION_REQUIRES_RECOVERY'); update_option(DatabaseState::FRESH, ['database'=>DB_NAME,'prefix'=>$wpdb->prefix], false); } if (!get_option(DatabaseState::FRESH)) throw new RuntimeException('ABECE_BOOKING_UNIDENTIFIED_PARTIAL_SCHEMA'); // Serialize installers per database/site; connection close also releases the lock. $lock = Tables::lock($wpdb, 'schema'); if ( '1' !== (string) $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, 5)', $lock ) ) ) { throw new RuntimeException( __( 'ABeCe Booking could not acquire the database migration lock. Please try again.', 'abece-booking' ) ); } try { wp_cache_delete( self::VERSION_OPTION, 'options' ); wp_cache_delete( 'alloptions', 'options' ); wp_cache_delete( 'notoptions', 'options' ); $version = $this->installedVersion(); // Another installer may have completed while this request waited for its lock. if ( $version === self::LATEST_VERSION && DatabaseState::ready( $wpdb ) ) { return; } if ( $this->migrations === null && get_option( DatabaseState::FRESH ) ) { $this->install_current(); return; } foreach ( $this->migrations ?? [ new InitialMigration(), new OperatingScheduleMigration(), new BookingModelMigration(), new PricingMigration(), new CreditPaymentsMigration(), new CancellationMigration(), new ResourceSurfaceMigration(), new ExternalContactsMigration(), new SettingsPaymentsMigration(), new UsageRestrictionsMigration(), new UsageCountingModeMigration(), new PilotOperationsMigration(), new TablePrefixMigration() ] as $migration ) { if ( $migration->version() <= $version ) { continue; } if ( $migration->version() !== $version + 1 || $migration->version() > self::LATEST_VERSION ) { throw new RuntimeException( __( 'ABeCe Booking encountered an invalid database migration sequence.', 'abece-booking' ) ); } $migration->apply( $wpdb ); update_option( self::VERSION_OPTION, $migration->version(), false ); if ( $this->installedVersion() !== $migration->version() ) { throw new RuntimeException( __( 'ABeCe Booking could not save the database schema version.', 'abece-booking' ) ); } $version = $migration->version(); } if ( self::LATEST_VERSION !== $version ) { throw new RuntimeException( __( 'ABeCe Booking encountered an invalid database migration sequence.', 'abece-booking' ) ); } delete_option(DatabaseState::FRESH); } finally { $wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock ) ); } } private function installedVersion(): int { return DatabaseState::version($this->wpdb); } /** Create final schema13 definitions directly; never create the legacy namespace. */ private function install_current(): void { $wpdb = $this->wpdb; $marker = get_option( DatabaseState::FRESH ); if ( ! is_array( $marker ) || ( $marker['database'] ?? '' ) !== DB_NAME || ( $marker['prefix'] ?? '' ) !== $wpdb->prefix || DatabaseState::layout( $wpdb )['old'] !== 0 ) { throw new RuntimeException( 'ABECE_BOOKING_FRESH_INSTALL_IDENTITY_INVALID' ); } require_once ABSPATH . 'wp-admin/includes/upgrade.php'; foreach ( Schema::current( $wpdb ) as $table => $sql ) { $exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ); if ( $wpdb->last_error ) { throw new RuntimeException( 'ABECE_BOOKING_FRESH_INSTALL_PREFLIGHT_FAILED' ); } if ( ! $exists ) { dbDelta( $sql ); if ( $wpdb->last_error ) { throw new RuntimeException( 'ABECE_BOOKING_FRESH_TABLE_CREATE_FAILED' ); } } Schema::verify( $wpdb, $table, $sql ); } ( new TablePrefixMigration() )->apply( $wpdb ); update_option( self::VERSION_OPTION, self::LATEST_VERSION, false ); if ( $this->installedVersion() !== self::LATEST_VERSION ) { throw new RuntimeException( 'ABECE_BOOKING_FRESH_VERSION_COMMIT_FAILED' ); } delete_option( DatabaseState::FRESH ); } }