prefix. */ public static function renamed( string $name ): ?string { foreach ( self::KEEP as $keep ) { if ( str_starts_with( $name, self::OLD_PREFIX . '_' . $keep ) ) { return null; } } foreach ( array( '_', '' ) as $lead ) { $old = $lead . self::OLD_PREFIX . '_'; if ( str_starts_with( $name, $old ) ) { return $lead . self::NEW_PREFIX . '_' . substr( $name, strlen( $old ) ); } } return null; } /** * Moves everything. */ private static function run(): void { self::options(); self::tables(); self::meta(); self::capabilities(); self::cron(); self::action_scheduler(); wp_cache_flush(); } /** * Settings: renamed. Cached values (transients) are dropped, they are rebuilt on demand. */ private static function options(): void { global $wpdb; $old = $wpdb->esc_like( self::OLD_PREFIX . '_' ) . '%'; foreach ( array( '_transient_', '_transient_timeout_', '_site_transient_', '_site_transient_timeout_' ) as $cache ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time cleanup of our own cached values. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $cache ) . $old ) ); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time rename of our own options. $names = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $old ) ); foreach ( $names as $name ) { $new = self::renamed( (string) $name ); if ( null === $new ) { continue; } // IGNORE keeps an option that already exists under the new name. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time rename of our own options. $wpdb->query( $wpdb->prepare( "UPDATE IGNORE {$wpdb->options} SET option_name = %s WHERE option_name = %s", $new, $name ) ); } } /** * Database tables: renamed unless a table with the new name already exists. */ private static function tables(): void { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time rename of our own tables. $tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $wpdb->prefix . self::OLD_PREFIX . '_' ) . '%' ) ); foreach ( $tables as $table ) { $new = self::renamed( substr( (string) $table, strlen( $wpdb->prefix ) ) ); if ( null === $new ) { continue; } $target = $wpdb->prefix . $new; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time rename of our own tables. if ( null !== $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $target ) ) ) ) { continue; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names cannot be placeholders; both come from SHOW TABLES and our prefix. $wpdb->query( 'RENAME TABLE `' . esc_sql( (string) $table ) . '` TO `' . esc_sql( $target ) . '`' ); } } /** * Post, user, term and comment meta keys. */ private static function meta(): void { global $wpdb; foreach ( array( $wpdb->postmeta, $wpdb->usermeta, $wpdb->termmeta, $wpdb->commentmeta ) as $table ) { foreach ( array( '_', '' ) as $lead ) { $old = $lead . self::OLD_PREFIX . '_'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core meta table names; one-time rename of our own keys. $wpdb->query( $wpdb->prepare( "UPDATE {$table} SET meta_key = CONCAT( %s, SUBSTRING( meta_key, %d ) ) WHERE meta_key LIKE %s", $lead . self::NEW_PREFIX . '_', strlen( $old ) + 1, $wpdb->esc_like( $old ) . '%' ) ); } } } /** * Permissions given to roles and to single users. */ private static function capabilities(): void { foreach ( wp_roles()->role_objects as $role ) { foreach ( array_keys( $role->capabilities ) as $cap ) { $new = self::renamed( (string) $cap ); if ( null !== $new ) { $role->add_cap( $new, (bool) $role->capabilities[ $cap ] ); $role->remove_cap( (string) $cap ); } } } global $wpdb; $users = get_users( array( 'fields' => 'ID', 'meta_key' => $wpdb->get_blog_prefix() . 'capabilities', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- One-time lookup. 'meta_value' => '"' . self::OLD_PREFIX . '_', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- One-time lookup. 'meta_compare' => 'LIKE', ) ); foreach ( $users as $id ) { $user = new \WP_User( (int) $id ); foreach ( $user->caps as $cap => $granted ) { $new = self::renamed( (string) $cap ); if ( null !== $new ) { $user->add_cap( $new, (bool) $granted ); $user->remove_cap( (string) $cap ); } } } } /** * WP-Cron events keep their time and arguments under the new hook name. */ private static function cron(): void { $crons = _get_cron_array(); if ( ! is_array( $crons ) || array() === $crons ) { return; } $changed = false; foreach ( $crons as $time => $hooks ) { if ( ! is_array( $hooks ) ) { continue; } foreach ( $hooks as $hook => $events ) { $new = self::renamed( (string) $hook ); if ( null === $new ) { continue; } $crons[ $time ][ $new ] = array_merge( $crons[ $time ][ $new ] ?? array(), $events ); unset( $crons[ $time ][ $hook ] ); $changed = true; } } if ( $changed ) { _set_cron_array( $crons ); } } /** * Pending Action Scheduler jobs, when that library is used. */ private static function action_scheduler(): void { global $wpdb; $table = $wpdb->prefix . 'actionscheduler_actions'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time check. if ( null === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ) ) { return; } $old = self::OLD_PREFIX . '_'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Library table; one-time rename of our own pending jobs. $wpdb->query( $wpdb->prepare( "UPDATE {$table} SET hook = CONCAT( %s, SUBSTRING( hook, %d ) ) WHERE status = 'pending' AND hook LIKE %s", self::NEW_PREFIX . '_', strlen( $old ) + 1, $wpdb->esc_like( $old ) . '%' ) ); } }