| @@ -48,10 +48,12 @@ | ||
| 48 | 48 | * which creates a circular dependency. |
| 49 | 49 | */ |
| 50 | 50 | $GLOBALS['wpdb'] = $this; |
| 51 | 51 | |
| 52 | + // The SQLite driver may use the charset and collation while connecting. | |
| 53 | + $this->init_charset(); | |
| 54 | + | |
| 52 | 55 | parent::__construct( '', '', $dbname, '' ); |
| 53 | - $this->charset = 'utf8mb4'; | |
| 54 | 56 | } |
| 55 | 57 | |
| 56 | 58 | /** |
| 57 | 59 | * Returns the active MySQL-on-SQLite driver. |
| @@ -232,8 +234,38 @@ | ||
| 232 | 234 | return true; |
| 233 | 235 | } |
| 234 | 236 | |
| 235 | 237 | /** |
| 238 | + * Sets $this->charset and $this->collate. | |
| 239 | + * | |
| 240 | + * This overrides wpdb::init_charset(). SQLite stores all text as UTF-8, and | |
| 241 | + * the emulated MySQL connection always uses utf8mb4 (see set_charset()), so | |
| 242 | + * the charset is always utf8mb4, and only a UTF-8 DB_COLLATE applies. | |
| 243 | + * | |
| 244 | + * Unlike in wpdb, the result doesn't depend on the database connection. | |
| 245 | + * This is important, as the SQLite driver may use the charset and collation | |
| 246 | + * while connecting (see the constructor). | |
| 247 | + * | |
| 248 | + * @see wpdb::init_charset() | |
| 249 | + */ | |
| 250 | + public function init_charset() { | |
| 251 | + $collate = defined( 'DB_COLLATE' ) ? strtolower( (string) DB_COLLATE ) : ''; | |
| 252 | + | |
| 253 | + // MySQL 8.0.30 and newer name the utf8 collations "utf8mb3_*". | |
| 254 | + $collate = preg_replace( '/^utf8mb3_/', 'utf8_', $collate ); | |
| 255 | + | |
| 256 | + // Collations of other charsets don't apply to the utf8mb4 connection. | |
| 257 | + if ( ! preg_match( '/^utf8(mb4)?_/', $collate ) ) { | |
| 258 | + $collate = ''; | |
| 259 | + } | |
| 260 | + | |
| 261 | + $charset_collate = $this->resolve_charset( 'utf8mb4', $collate ); | |
| 262 | + | |
| 263 | + $this->charset = $charset_collate['charset']; | |
| 264 | + $this->collate = $charset_collate['collate']; | |
| 265 | + } | |
| 266 | + | |
| 267 | + /** | |
| 236 | 268 | * Determines the best charset and collation to use given a charset and collation. |
| 237 | 269 | * |
| 238 | 270 | * For example, when able, utf8mb4 should be used instead of utf8. |
| 239 | 271 | * |
| @@ -253,27 +285,9 @@ | ||
| 253 | 285 | if ( ! $this->dbh ) { |
| 254 | 286 | return compact( 'charset', 'collate' ); |
| 255 | 287 | } |
| 256 | 288 | |
| 257 | - if ( 'utf8' === $charset ) { | |
| 258 | - $charset = 'utf8mb4'; | |
| 259 | - } | |
| 260 | - | |
| 261 | - if ( 'utf8mb4' === $charset ) { | |
| 262 | - // _general_ is outdated, so we can upgrade it to _unicode_, instead. | |
| 263 | - if ( ! $collate || 'utf8_general_ci' === $collate ) { | |
| 264 | - $collate = 'utf8mb4_unicode_ci'; | |
| 265 | - } else { | |
| 266 | - $collate = str_replace( 'utf8_', 'utf8mb4_', $collate ); | |
| 267 | - } | |
| 268 | - } | |
| 269 | - | |
| 270 | - // _unicode_520_ is a better collation, we should use that when it's available. | |
| 271 | - if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) { | |
| 272 | - $collate = 'utf8mb4_unicode_520_ci'; | |
| 273 | - } | |
| 274 | - | |
| 275 | - return compact( 'charset', 'collate' ); | |
| 289 | + return $this->resolve_charset( $charset, $collate ); | |
| 276 | 290 | } |
| 277 | 291 | |
| 278 | 292 | /** |
| 279 | 293 | * Method to select the database connection. |
| @@ -427,31 +441,8 @@ | ||
| 427 | 441 | E_USER_WARNING |
| 428 | 442 | ); |
| 429 | 443 | } |
| 430 | 444 | |
| 431 | - if ( ! isset( $this->charset ) ) { | |
| 432 | - $this->init_charset(); | |
| 433 | - } | |
| 434 | - | |
| 435 | - // Migrate the database file from a legacy path, if it exists. | |
| 436 | - if ( ! defined( 'DB_FILE' ) && ! file_exists( FQDB ) ) { | |
| 437 | - $old_db_path = FQDBDIR . '.ht.sqlite.php'; | |
| 438 | - | |
| 439 | - if ( file_exists( $old_db_path ) ) { | |
| 440 | - if ( ! rename( $old_db_path, FQDB ) ) { | |
| 441 | - wp_die( 'Failed to rename database file.', 'Error!' ); | |
| 442 | - } | |
| 443 | - | |
| 444 | - foreach ( array( '-wal', '-shm', '-journal' ) as $suffix ) { | |
| 445 | - if ( file_exists( $old_db_path . $suffix ) ) { | |
| 446 | - if ( ! rename( $old_db_path . $suffix, FQDB . $suffix ) ) { | |
| 447 | - wp_die( 'Failed to rename database file.', 'Error!' ); | |
| 448 | - } | |
| 449 | - } | |
| 450 | - } | |
| 451 | - } | |
| 452 | - } | |
| 453 | - | |
| 454 | 445 | if ( null === $this->dbname || '' === $this->dbname ) { |
| 455 | 446 | $this->bail( |
| 456 | 447 | 'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.', |
| 457 | 448 | 'db_connect_fail' |
| @@ -458,10 +449,8 @@ | ||
| 458 | 449 | ); |
| 459 | 450 | return false; |
| 460 | 451 | } |
| 461 | 452 | |
| 462 | - $this->ensure_database_directory( FQDB ); | |
| 463 | - | |
| 464 | 453 | try { |
| 465 | 454 | $options = array( |
| 466 | 455 | 'sqlite_journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null, |
| 467 | 456 | ); |
| @@ -467,9 +456,9 @@ | ||
| 467 | 456 | ); |
| 468 | 457 | $dbh = new WP_MySQL_On_SQLite( |
| 469 | 458 | sprintf( |
| 470 | 459 | 'mysql-on-sqlite:path=%s;dbname=%s', |
| 471 | - str_replace( ';', ';;', FQDB ), | |
| 460 | + str_replace( ';', ';;', DB_PATH ), | |
| 472 | 461 | str_replace( ';', ';;', $this->dbname ) |
| 473 | 462 | ), |
| 474 | 463 | null, |
| 475 | 464 | null, |
| @@ -785,54 +774,45 @@ | ||
| 785 | 774 | return $this->dbh->getAttribute( PDO::ATTR_SERVER_VERSION ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO |
| 786 | 775 | } |
| 787 | 776 | |
| 788 | 777 | /** |
| 789 | - * Make sure the SQLite database directory exists and is writable. | |
| 790 | - * Create .htaccess and index.php files to prevent direct access. | |
| 778 | + * Resolves the charset and collation as wpdb::determine_charset() does. | |
| 791 | 779 | * |
| 792 | - * @param string $database_path The path to the SQLite database file. | |
| 780 | + * Unlike in wpdb, this doesn't need a database connection. The emulated | |
| 781 | + * MySQL server (5.7 or newer) always supports utf8mb4_unicode_520_ci. | |
| 782 | + * | |
| 783 | + * @see wpdb::determine_charset() | |
| 784 | + * | |
| 785 | + * @param string $charset The character set to check. | |
| 786 | + * @param string $collate The collation to check. | |
| 787 | + * @return array { | |
| 788 | + * The most appropriate character set and collation to use. | |
| 789 | + * | |
| 790 | + * @type string $charset Character set. | |
| 791 | + * @type string $collate Collation. | |
| 792 | + * } | |
| 793 | 793 | */ |
| 794 | - private function ensure_database_directory( string $database_path ) { | |
| 795 | - $dir = dirname( $database_path ); | |
| 796 | - | |
| 797 | - // Set the umask to 0000 to apply permissions exactly as specified. | |
| 798 | - // A non-zero umask affects new file and directory permissions. | |
| 799 | - $umask = umask( 0 ); | |
| 800 | - | |
| 801 | - // Ensure database directory. | |
| 802 | - if ( ! is_dir( $dir ) ) { | |
| 803 | - if ( ! @mkdir( $dir, 0700, true ) ) { | |
| 804 | - wp_die( sprintf( 'Failed to create database directory: %s', $dir ), 'Error!' ); | |
| 805 | - } | |
| 794 | + private function resolve_charset( $charset, $collate ) { | |
| 795 | + if ( 'utf8' === $charset ) { | |
| 796 | + $charset = 'utf8mb4'; | |
| 806 | 797 | } |
| 807 | - if ( ! is_writable( $dir ) ) { | |
| 808 | - wp_die( sprintf( 'Database directory is not writable: %s', $dir ), 'Error!' ); | |
| 809 | - } | |
| 810 | 798 | |
| 811 | - // Ensure .htaccess file to prevent direct access. | |
| 812 | - $path = $dir . DIRECTORY_SEPARATOR . '.htaccess'; | |
| 813 | - if ( ! is_file( $path ) ) { | |
| 814 | - $result = file_put_contents( $path, 'DENY FROM ALL', LOCK_EX ); | |
| 815 | - if ( false === $result ) { | |
| 816 | - wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' ); | |
| 799 | + if ( 'utf8mb4' === $charset ) { | |
| 800 | + // _general_ is outdated, so we can upgrade it to _unicode_, instead. | |
| 801 | + if ( ! $collate || 'utf8_general_ci' === $collate ) { | |
| 802 | + $collate = 'utf8mb4_unicode_ci'; | |
| 803 | + } else { | |
| 804 | + $collate = str_replace( 'utf8_', 'utf8mb4_', $collate ); | |
| 817 | 805 | } |
| 818 | - chmod( $path, 0600 ); | |
| 819 | 806 | } |
| 820 | 807 | |
| 821 | - // Ensure index.php file to prevent direct access. | |
| 822 | - $path = $dir . DIRECTORY_SEPARATOR . 'index.php'; | |
| 823 | - if ( ! is_file( $path ) ) { | |
| 824 | - $result = file_put_contents( $path, '<?php // Silence is gold. ?>', LOCK_EX ); | |
| 825 | - if ( false === $result ) { | |
| 826 | - wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' ); | |
| 827 | - } | |
| 828 | - chmod( $path, 0600 ); | |
| 808 | + // _unicode_520_ is a better collation, we should use that when it's available. | |
| 809 | + if ( 'utf8mb4_unicode_ci' === $collate ) { | |
| 810 | + $collate = 'utf8mb4_unicode_520_ci'; | |
| 829 | 811 | } |
| 830 | 812 | |
| 831 | - // Restore the original umask value. | |
| 832 | - umask( $umask ); | |
| 813 | + return compact( 'charset', 'collate' ); | |
| 833 | 814 | } |
| 834 | - | |
| 835 | 815 | |
| 836 | 816 | /** |
| 837 | 817 | * Format MySQL-on-SQLite driver error message. |
| 838 | 818 | * |