new option. */ private const OPTIONS = array( 'abl_settings' => 'blocklingo_settings', 'abl_db_version' => 'blocklingo_db_version', ); /** Old post meta key => new key. */ private const POST_META = array( '_abl_old_path' => '_blocklingo_old_path', ); public function __construct( private readonly \wpdb $db ) {} public function needed(): bool { return false !== get_option( 'abl_db_version' ) || false !== get_option( 'abl_settings' ); } public function run(): void { if ( ! $this->needed() ) { return; } // phpcs:disable WordPress.DB.DirectDatabaseQuery -- one-time schema and meta migration. foreach ( self::TABLES as $name ) { $old = $this->db->prefix . self::OLD . $name; $new = $this->db->prefix . self::NEW . $name; if ( $this->table_exists( $old ) && ! $this->table_exists( $new ) ) { $this->db->query( $this->db->prepare( 'RENAME TABLE %i TO %i', $old, $new ) ); } } foreach ( self::POST_META as $old => $new ) { $this->db->update( $this->db->postmeta, array( 'meta_key' => $new ), array( 'meta_key' => $old ) ); // phpcs:ignore WordPress.DB.SlowDBQuery -- one time. } $this->db->query( $this->db->prepare( 'DELETE FROM %i WHERE option_name LIKE %s OR option_name LIKE %s', $this->db->options, $this->db->esc_like( '_transient_' . self::OLD ) . '%', $this->db->esc_like( '_transient_timeout_' . self::OLD ) . '%' ) ); // phpcs:enable wp_cache_delete( 'alloptions', 'options' ); // Options last: while the old DB version option is there, a failed run is retried. foreach ( self::OPTIONS as $old => $new ) { $value = get_option( $old ); if ( false !== $value && false === get_option( $new ) ) { update_option( $new, $value ); } } foreach ( array_keys( self::OPTIONS ) as $old ) { delete_option( $old ); } delete_transient( 'blocklingo_tables_ok' ); } private function table_exists( string $table ): bool { return $table === $this->db->get_var( $this->db->prepare( 'SHOW TABLES LIKE %s', $this->db->esc_like( $table ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-time migration. } }