*/ private const FALLBACKS = [ 'utf8mb4' => [ 'utf8mb4_unicode_520_ci', 'utf8mb4_unicode_ci', 'utf8mb4_general_ci' ], 'utf8mb3' => [ 'utf8mb3_unicode_520_ci', 'utf8mb3_unicode_ci', 'utf8mb3_general_ci', 'utf8_unicode_520_ci', 'utf8_unicode_ci', 'utf8_general_ci' ], 'utf8' => [ 'utf8_unicode_520_ci', 'utf8_unicode_ci', 'utf8_general_ci', 'utf8mb3_unicode_ci', 'utf8mb3_general_ci' ], ]; /** Rewrites the collations in a CREATE TABLE statement that this server does not support. */ public static function adapt( \wpdb $wpdb, string $sql ): string { if ( ! preg_match_all( '/\bCOLLATE\s*=?\s*`?([A-Za-z0-9_]+)/i', $sql, $found ) ) { return $sql; } $known = self::known( $wpdb ); // If the server will not list its collations, leave the statement as is. if ( [] === $known ) { return $sql; } $map = []; foreach ( array_unique( $found[1] ) as $name ) { if ( isset( $known[ strtolower( $name ) ] ) ) { continue; } $stand_in = self::stand_in( $name, $known ); if ( null !== $stand_in ) { $map[ strtolower( $name ) ] = $stand_in; } } if ( [] === $map ) { return $sql; } return (string) preg_replace_callback( '/\b(COLLATE\s*=?\s*`?)([A-Za-z0-9_]+)/i', static fn( array $m ): string => $m[1] . ( $map[ strtolower( $m[2] ) ] ?? $m[2] ), $sql ); } /** The closest supported collation for an unknown one, or null when none matches its charset. */ private static function stand_in( string $name, array $known ): ?string { $charset = strtolower( (string) strstr( $name, '_', true ) ); $candidates = self::FALLBACKS[ $charset ] ?? [ $charset . '_general_ci', $charset . '_bin' ]; foreach ( $candidates as $candidate ) { if ( isset( $known[ $candidate ] ) ) { return $candidate; } } return null; } /** * Collations this server supports, read once per request. * * @return array */ public static function known( \wpdb $wpdb ): array { static $known = null; if ( null === $known ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- server metadata, nothing to cache $names = $wpdb->get_col( 'SHOW COLLATION' ); $known = is_array( $names ) && [] !== $names ? array_fill_keys( array_map( 'strtolower', array_map( 'strval', $names ) ), true ) : []; } return $known; } }