Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 124 additions & 51 deletions src/wp-includes/class-wpdb.php
Original file line number Diff line number Diff line change
Expand Up @@ -3227,21 +3227,15 @@ protected function get_table_charset( $table ) {
}

$charsets = array();
$columns = array();
$columns = $this->get_cols_meta( $table );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use $tablekey as it convert $table value to strtolower and remove strtolower from get_cols_meta.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with this. Using $tablekey would be fine here, but strtolower shouldn't be removed from get_cols_meta as get_cols_meta is used in more than 1 place and could be used elsewhere in the future. Removing it from there would require a tablekey being passed in all the time instead of just table so you'd always need to make sure strtolower is called first which would make the function less resilient/consistant.


$table_parts = explode( '.', $table );
$table = '`' . implode( '`.`', $table_parts ) . '`';
$results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
if ( ! $results ) {
return new WP_Error( 'wpdb_get_table_charset_failure', __( 'Could not retrieve table charset.' ) );
if ( is_wp_error( $columns ) ) {
return $columns;
}

foreach ( $results as $column ) {
$columns[ strtolower( $column->Field ) ] = $column;
if ( empty( $columns ) ) {
$columns = array();
}

$this->col_meta[ $tablekey ] = $columns;

foreach ( $columns as $column ) {
if ( ! empty( $column->Collation ) ) {
list( $charset ) = explode( '_', $column->Collation );
Expand Down Expand Up @@ -3291,6 +3285,109 @@ protected function get_table_charset( $table ) {
return $charset;
}

/**
* Retrieves the column metadata for the given column.
*
* @since 7.0.0
*
* @param string $table Table name.
* @return object|false|WP_Error Column meta set as an object. False if the column was
* not found. WP_Error object if there was an error.
*/
public function get_cols_meta( $table ) {
$tablekey = strtolower( $table );

/**
* Filters the columns meta value before the DB is checked.
*
* Passing a non-null value to the filter will short-circuit
* checking the DB for the meta, returning that value instead.
*
* @since 7.0.0
*
* @param object[]|null|false|WP_Error $meta The meta to use. Default null.
* @param string $table The name of the table being checked.
*/
$col_meta = apply_filters( 'pre_get_cols_meta', null, $table );
if ( null !== $col_meta ) {
return $col_meta;
}

// Skip this entirely if this isn't a MySQL database.
if ( empty( $this->is_mysql ) ) {
return false;
}

// If no column information, fetch it from the database.
if ( empty( $this->col_meta[ $tablekey ] ) ) {
$columns = array();

$table_parts = explode( '.', $table );
$table = '`' . implode( '`.`', $table_parts ) . '`';
$results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
if ( ! $results ) {
return new WP_Error( 'wpdb_get_cols_meta_failure', __( 'Could not retrieve column metadata.' ) );
}

foreach ( $results as $column ) {
$columns[ strtolower( $column->Field ) ] = $column;
}

$this->col_meta[ $tablekey ] = $columns;
}

// If this table data doesn't exist, return false.
if ( empty( $this->col_meta[ $tablekey ] ) ) {
return false;
}

return $this->col_meta[ $tablekey ];
}

/**
* Retrieves the column metadata for the given column.
*
* @since 7.0.0
*
* @param string $table Table name.
* @param string $column Column name.
* @return object|false|WP_Error Column character set as a string. False if the column has
* no character set. WP_Error object if there was an error.
*/
public function get_col_meta( $table, $column ) {
$columnkey = strtolower( $column );

/**
* Filters the column meta value before the DB is checked.
*
* Passing a non-null value to the filter will short-circuit
* checking the DB for the meta, returning that value instead.
*
* @since 7.0.0
*
* @param object|null|false|WP_Error $meta The meta to use. Default null.
* @param string $table The name of the table being checked.
* @param string $column The name of the column being checked.
*/
$col_meta = apply_filters( 'pre_get_col_meta', null, $table, $column );
if ( null !== $col_meta ) {
return $col_meta;
}

$cols_meta = $this->get_cols_meta( $table );

if ( is_wp_error( $cols_meta ) ) {
return $cols_meta;
}

// If this column doesn't exist, return false.
if ( empty( $cols_meta[ $columnkey ] ) ) {
return false;
}

return $cols_meta[ $columnkey ];
}

/**
* Retrieves the character set for the given column.
*
Expand All @@ -3302,9 +3399,6 @@ protected function get_table_charset( $table ) {
* no character set. WP_Error object if there was an error.
*/
public function get_col_charset( $table, $column ) {
$tablekey = strtolower( $table );
$columnkey = strtolower( $column );

/**
* Filters the column charset value before the DB is checked.
*
Expand All @@ -3322,35 +3416,23 @@ public function get_col_charset( $table, $column ) {
return $charset;
}

// Skip this entirely if this isn't a MySQL database.
if ( empty( $this->is_mysql ) ) {
return false;
}

if ( empty( $this->table_charset[ $tablekey ] ) ) {
// This primes column information for us.
$table_charset = $this->get_table_charset( $table );
if ( is_wp_error( $table_charset ) ) {
return $table_charset;
}
}
$col_meta = $this->get_col_meta( $table, $column );

// If still no column information, return the table charset.
if ( empty( $this->col_meta[ $tablekey ] ) ) {
return $this->table_charset[ $tablekey ];
if ( is_wp_error( $col_meta ) ) {
return $col_meta;
}

// If this column doesn't exist, return the table charset.
if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
return $this->table_charset[ $tablekey ];
if ( empty( $col_meta ) ) {
return $this->get_table_charset( $table );
}

// Return false when it's not a string column.
if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
if ( empty( $col_meta->Collation ) ) {
return false;
}

list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
list( $charset ) = explode( '_', $col_meta->Collation );
return $charset;
}

Expand All @@ -3372,27 +3454,17 @@ public function get_col_charset( $table, $column ) {
* }
*/
public function get_col_length( $table, $column ) {
$tablekey = strtolower( $table );
$columnkey = strtolower( $column );

// Skip this entirely if this isn't a MySQL database.
if ( empty( $this->is_mysql ) ) {
return false;
}
$col_meta = $this->get_col_meta( $table, $column );

if ( empty( $this->col_meta[ $tablekey ] ) ) {
// This primes column information for us.
$table_charset = $this->get_table_charset( $table );
if ( is_wp_error( $table_charset ) ) {
return $table_charset;
}
if ( is_wp_error( $col_meta ) ) {
return $col_meta;
}

if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
if ( empty( $col_meta ) ) {
return false;
}

$typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
$typeinfo = explode( '(', $col_meta->Type );

$type = strtolower( $typeinfo[0] );
if ( ! empty( $typeinfo[1] ) ) {
Expand Down Expand Up @@ -3510,8 +3582,9 @@ protected function check_safe_collation( $query ) {
return true;
}

$table = strtolower( $table );
if ( empty( $this->col_meta[ $table ] ) ) {
$cols_meta = $this->get_cols_meta( $table );

if ( is_wp_error( $cols_meta ) || empty( $cols_meta ) ) {
return false;
}

Expand All @@ -3525,7 +3598,7 @@ protected function check_safe_collation( $query ) {
'utf8mb4_general_ci',
);

foreach ( $this->col_meta[ $table ] as $col ) {
foreach ( $cols_meta as $col ) {
if ( empty( $col->Collation ) ) {
continue;
}
Expand Down
5 changes: 5 additions & 0 deletions tests/phpunit/includes/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,11 @@ public function __construct() {
public function __call( $name, $arguments ) {
return call_user_func_array( array( $this, $name ), $arguments );
}

public function reset_cache_values() {
$this->table_charset = array();
$this->col_meta = array();
}
}

/**
Expand Down
31 changes: 31 additions & 0 deletions tests/phpunit/tests/db.php
Original file line number Diff line number Diff line change
Expand Up @@ -2486,4 +2486,35 @@ public function test_check_connection_returns_true_when_there_is_a_connection()

$this->assertTrue( $wpdb->check_connection( false ) );
}

/**
* Tests if the column charset doesn't throw errors with the `pre_get_table_charset` filter.
*
* @ticket 38921
*/
public function test_get_table_and_column_charset_with_pre_get_table_charset_filter() {
global $wpdb;
$expected_charset = $wpdb->get_col_charset( $wpdb->posts, 'post_content' );

self::$_wpdb->reset_cache_values();

add_filter( 'pre_get_table_charset', array( $this, 'change_table_charset_callback' ), 10, 2 );
$table_charset = self::$_wpdb->get_table_charset( $wpdb->posts );
$column_charset = self::$_wpdb->get_col_charset( $wpdb->posts, 'post_content' );
remove_filter( 'pre_get_table_charset', array( $this, 'change_table_charset_callback' ), 10, 2 );

$this->assertSame( 'fake_charset', $table_charset );
$this->assertSame( $expected_charset, $column_charset );
}

/**
* Callback for the `pre_get_table_charset` filter.
*
* @param string $charset The table's character set.
* @param string $table The name of the table.
* @return string $charset The table's character set.
*/
public function change_table_charset_callback( $charset, $table ) {
return 'fake_charset';
}
}
Loading