WordPress 4.2.2
[autoinstalls/wordpress.git] / wp-includes / wp-db.php
index b3eed896ec4d5915b8ad51ac69d7ed6d409c54a4..e749f517c43beac5f953c7bb8141c39a920d34c8 100644 (file)
@@ -43,7 +43,7 @@ define( 'ARRAY_N', 'ARRAY_N' );
  * file to your class. The wpdb class will still be included,
  * so you can extend it or simply use your own.
  *
- * @link http://codex.wordpress.org/Function_Reference/wpdb_Class
+ * @link https://codex.wordpress.org/Function_Reference/wpdb_Class
  *
  * @package WordPress
  * @subpackage Database
@@ -240,7 +240,7 @@ class wpdb {
        var $ready = false;
 
        /**
-        * {@internal Missing Description}}
+        * Blog ID.
         *
         * @since 3.0.0
         * @access public
@@ -249,7 +249,7 @@ class wpdb {
        public $blogid = 0;
 
        /**
-        * {@internal Missing Description}}
+        * Site ID.
         *
         * @since 3.0.0
         * @access public
@@ -634,8 +634,6 @@ class wpdb {
                        }
                }
 
-               $this->init_charset();
-
                $this->dbuser = $dbuser;
                $this->dbpassword = $dbpassword;
                $this->dbname = $dbname;
@@ -727,16 +725,31 @@ class wpdb {
        public function init_charset() {
                if ( function_exists('is_multisite') && is_multisite() ) {
                        $this->charset = 'utf8';
-                       if ( defined( 'DB_COLLATE' ) && DB_COLLATE )
+                       if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
                                $this->collate = DB_COLLATE;
-                       else
+                       } else {
                                $this->collate = 'utf8_general_ci';
+                       }
                } elseif ( defined( 'DB_COLLATE' ) ) {
                        $this->collate = DB_COLLATE;
                }
 
-               if ( defined( 'DB_CHARSET' ) )
+               if ( defined( 'DB_CHARSET' ) ) {
                        $this->charset = DB_CHARSET;
+               }
+
+               if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) )
+                 || ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) ) {
+                       return;
+               }
+
+               if ( 'utf8' === $this->charset && $this->has_cap( 'utf8mb4' ) ) {
+                       $this->charset = 'utf8mb4';
+               }
+
+               if ( 'utf8mb4' === $this->charset && ( ! $this->collate || stripos( $this->collate, 'utf8_' ) === 0 ) ) {
+                       $this->collate = 'utf8mb4_unicode_ci';
+               }
        }
 
        /**
@@ -1376,7 +1389,7 @@ class wpdb {
                        while ( mysqli_more_results( $this->dbh ) ) {
                                mysqli_next_result( $this->dbh );
                        }
-               } else if ( is_resource( $this->result ) ) {
+               } elseif ( is_resource( $this->result ) ) {
                        mysql_free_result( $this->result );
                }
        }
@@ -1445,9 +1458,9 @@ class wpdb {
 
                                if ( $this->has_connected ) {
                                        $attempt_fallback = false;
-                               } else if ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) {
+                               } elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) {
                                        $attempt_fallback = false;
-                               } else if ( ! function_exists( 'mysql_connect' ) ) {
+                               } elseif ( ! function_exists( 'mysql_connect' ) ) {
                                        $attempt_fallback = false;
                                }
 
@@ -1485,11 +1498,17 @@ class wpdb {
 " ), htmlspecialchars( $this->dbhost, ENT_QUOTES ) ), 'db_connect_fail' );
 
                        return false;
-               } else if ( $this->dbh ) {
+               } elseif ( $this->dbh ) {
+                       if ( ! $this->has_connected ) {
+                               $this->init_charset();
+                       }
+
                        $this->has_connected = true;
+
                        $this->set_charset( $this->dbh );
-                       $this->set_sql_mode();
+
                        $this->ready = true;
+                       $this->set_sql_mode();
                        $this->select( $this->dbname, $this->dbh );
 
                        return true;
@@ -1620,6 +1639,7 @@ class wpdb {
                }
 
                $this->check_current_query = true;
+
                // Keep track of the last query for debug..
                $this->last_query = $query;
 
@@ -1685,7 +1705,7 @@ class wpdb {
                                        $this->last_result[$num_rows] = $row;
                                        $num_rows++;
                                }
-                       } else if ( is_resource( $this->result ) ) {
+                       } elseif ( is_resource( $this->result ) ) {
                                while ( $row = @mysql_fetch_object( $this->result ) ) {
                                        $this->last_result[$num_rows] = $row;
                                        $num_rows++;
@@ -1789,6 +1809,8 @@ class wpdb {
         * @return int|false The number of rows affected, or false on error.
         */
        function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
+               $this->insert_id = 0;
+
                if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
                        return false;
                }
@@ -1809,7 +1831,6 @@ class wpdb {
 
                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
 
-               $this->insert_id = 0;
                $this->check_current_query = false;
                return $this->query( $this->prepare( $sql, $values ) );
        }
@@ -1926,11 +1947,20 @@ class wpdb {
         */
        protected function process_fields( $table, $data, $format ) {
                $data = $this->process_field_formats( $data, $format );
+               if ( false === $data ) {
+                       return false;
+               }
+
                $data = $this->process_field_charsets( $data, $table );
                if ( false === $data ) {
                        return false;
                }
 
+               $data = $this->process_field_lengths( $data, $table );
+               if ( false === $data ) {
+                       return false;
+               }
+
                $converted_data = $this->strip_invalid_text( $data );
 
                if ( $data !== $converted_data ) {
@@ -1977,12 +2007,12 @@ class wpdb {
 
        /**
         * Adds field charsets to field/value/format arrays generated by
-        * the {@see wpdb::process_field_formats()} method.
+        * the wpdb::process_field_formats() method.
         *
         * @since 4.2.0
         * @access protected
         *
-        * @param array  $data  As it comes from the {@see wpdb::process_field_formats()} method.
+        * @param array  $data  As it comes from the wpdb::process_field_formats() method.
         * @param string $table Table name.
         * @return The same array as $data with additional 'charset' keys.
         */
@@ -1992,17 +2022,41 @@ class wpdb {
                                // We can skip this field if we know it isn't a string.
                                // This checks %d/%f versus ! %s because it's sprintf() could take more.
                                $value['charset'] = false;
-                       } elseif ( $this->check_ascii( $value['value'] ) ) {
-                               // If it's ASCII, then we don't need the charset. We can skip this field.
-                               $value['charset'] = false;
                        } else {
                                $value['charset'] = $this->get_col_charset( $table, $field );
                                if ( is_wp_error( $value['charset'] ) ) {
                                        return false;
                                }
+                       }
 
-                               // This isn't ASCII. Don't have strip_invalid_text() re-check.
-                               $value['ascii'] = false;
+                       $data[ $field ] = $value;
+               }
+
+               return $data;
+       }
+
+       /**
+        * For string fields, record the maximum string length that field can safely save.
+        *
+        * @since 4.2.1
+        * @access protected
+        *
+        * @param array  $data  As it comes from the wpdb::process_field_charsets() method.
+        * @param string $table Table name.
+        * @return array|False The same array as $data with additional 'length' keys, or false if
+        *                     any of the values were too long for their corresponding field.
+        */
+       protected function process_field_lengths( $data, $table ) {
+               foreach ( $data as $field => $value ) {
+                       if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
+                               // We can skip this field if we know it isn't a string.
+                               // This checks %d/%f versus ! %s because it's sprintf() could take more.
+                               $value['length'] = false;
+                       } else {
+                               $value['length'] = $this->get_col_length( $table, $field );
+                               if ( is_wp_error( $value['length'] ) ) {
+                                       return false;
+                               }
                        }
 
                        $data[ $field ] = $value;
@@ -2186,7 +2240,7 @@ class wpdb {
         * @access protected
         *
         * @param string $table Table name.
-        * @return string|WP_Error Table character set, {@see WP_Error} object if it couldn't be found.
+        * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
         */
        protected function get_table_charset( $table ) {
                $tablekey = strtolower( $table );
@@ -2212,7 +2266,10 @@ class wpdb {
                }
 
                $charsets = $columns = array();
-               $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" );
+
+               $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' );
                }
@@ -2226,6 +2283,12 @@ class wpdb {
                foreach ( $columns as $column ) {
                        if ( ! empty( $column->Collation ) ) {
                                list( $charset ) = explode( '_', $column->Collation );
+
+                               // If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
+                               if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
+                                       $charset = 'utf8';
+                               }
+
                                $charsets[ strtolower( $charset ) ] = true;
                        }
 
@@ -2280,7 +2343,7 @@ class wpdb {
         * @param string $table  Table name.
         * @param string $column Column name.
         * @return mixed Column character set as a string. False if the column has no
-        *               character set. {@see WP_Error} object if there was an error.
+        *               character set. WP_Error object if there was an error.
         */
        public function get_col_charset( $table, $column ) {
                $tablekey = strtolower( $table );
@@ -2335,6 +2398,99 @@ class wpdb {
                return $charset;
        }
 
+       /**
+        * Retrieve the maximum string length allowed in a given column.
+        * The length may either be specified as a byte length or a character length.
+        *
+        * @since 4.2.1
+        * @access public
+        *
+        * @param string $table  Table name.
+        * @param string $column Column name.
+        * @return mixed array( 'length' => (int), 'type' => 'byte' | 'char' )
+        *               false if the column has no length (for example, numeric column)
+        *               WP_Error object if there was an error.
+        */
+       public function get_col_length( $table, $column ) {
+               $tablekey = strtolower( $table );
+               $columnkey = strtolower( $column );
+
+               // Skip this entirely if this isn't a MySQL database.
+               if ( false === $this->is_mysql ) {
+                       return false;
+               }
+
+               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 ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
+                       return false;
+               }
+
+               $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
+
+               $type = strtolower( $typeinfo[0] );
+               if ( ! empty( $typeinfo[1] ) ) {
+                       $length = trim( $typeinfo[1], ')' );
+               } else {
+                       $length = false;
+               }
+
+               switch( $type ) {
+                       case 'char':
+                       case 'varchar':
+                               return array(
+                                       'type'   => 'char',
+                                       'length' => (int) $length,
+                               );
+                               break;
+                       case 'binary':
+                       case 'varbinary':
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => (int) $length,
+                               );
+                               break;
+                       case 'tinyblob':
+                       case 'tinytext':
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 255,        // 2^8 - 1
+                               );
+                               break;
+                       case 'blob':
+                       case 'text':
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 65535,      // 2^16 - 1
+                               );
+                               break;
+                       case 'mediumblob':
+                       case 'mediumtext':
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 16777215,   // 2^24 - 1
+                               );
+                               break;
+                       case 'longblob':
+                       case 'longtext':
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 4294967295, // 2^32 - 1
+                               );
+                               break;
+                       default:
+                               return false;
+               }
+
+               return false;
+       }
+
        /**
         * Check if a string is ASCII.
         *
@@ -2375,7 +2531,7 @@ class wpdb {
 
                // We don't need to check the collation for queries that don't read data.
                $query = ltrim( $query, "\r\n\t (" );
-               if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN)\s/i', $query ) ) {
+               if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
                        return true;
                }
 
@@ -2429,54 +2585,58 @@ class wpdb {
         * @return array|WP_Error The $data parameter, with invalid characters removed from
         *                        each value. This works as a passthrough: any additional keys
         *                        such as 'field' are retained in each value array. If we cannot
-        *                        remove invalid characters, a {@see WP_Error} object is returned.
+        *                        remove invalid characters, a WP_Error object is returned.
         */
-               // If any of the columns don't have one of these collations, it needs more sanity checking.
        protected function strip_invalid_text( $data ) {
-               // Some multibyte character sets that we can check in PHP.
-               $mb_charsets = array(
-                       'ascii'   => 'ASCII',
-                       'big5'    => 'BIG-5',
-                       'eucjpms' => 'eucJP-win',
-                       'gb2312'  => 'EUC-CN',
-                       'ujis'    => 'EUC-JP',
-                       'utf32'   => 'UTF-32',
-               );
-
-               $supported_charsets = array();
-               if ( function_exists( 'mb_list_encodings' ) ) {
-                       $supported_charsets = mb_list_encodings();
-               }
-
                $db_check_string = false;
 
                foreach ( $data as &$value ) {
                        $charset = $value['charset'];
 
-                       // Column isn't a string, or is latin1, which will will happily store anything.
-                       if ( false === $charset || 'latin1' === $charset ) {
+                       if ( is_array( $value['length'] ) ) {
+                               $length = $value['length']['length'];
+                       } else {
+                               $length = false;
+                       }
+
+                       // There's no charset to work with.
+                       if ( false === $charset ) {
                                continue;
                        }
 
+                       // Column isn't a string.
                        if ( ! is_string( $value['value'] ) ) {
                                continue;
                        }
 
-                       // ASCII is always OK.
-                       if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
-                               continue;
+                       $truncate_by_byte_length = 'byte' === $value['length']['type'];
+
+                       $needs_validation = true;
+                       if (
+                               // latin1 can store any byte sequence
+                               'latin1' === $charset
+                       ||
+                               // ASCII is always OK.
+                               ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
+                       ) {
+                               $truncate_by_byte_length = true;
+                               $needs_validation = false;
                        }
 
-                       // Convert the text locally.
-                       if ( $supported_charsets ) {
-                               if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
-                                       $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
+                       if ( $truncate_by_byte_length ) {
+                               mbstring_binary_safe_encoding();
+                               if ( false !== $length && strlen( $value['value'] ) > $length ) {
+                                       $value['value'] = substr( $value['value'], 0, $length );
+                               }
+                               reset_mbstring_encoding();
+
+                               if ( ! $needs_validation ) {
                                        continue;
                                }
                        }
 
                        // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
-                       if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
+                       if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
                                $regex = '/
                                        (
                                                (?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
@@ -2486,7 +2646,7 @@ class wpdb {
                                                |   \xED[\x80-\x9F][\x80-\xBF]
                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
 
-                               if ( 'utf8mb4' === $charset) {
+                               if ( 'utf8mb4' === $charset ) {
                                        $regex .= '
                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
                                                |    [\xF1-\xF3][\x80-\xBF]{3}
@@ -2494,11 +2654,16 @@ class wpdb {
                                        ';
                                }
 
-                               $regex .= '){1,50}                          # ...one or more times
+                               $regex .= '){1,40}                          # ...one or more times
                                        )
                                        | .                                  # anything else
                                        /x';
                                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
+
+
+                               if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
+                                       $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
+                               }
                                continue;
                        }
 
@@ -2515,8 +2680,14 @@ class wpdb {
                                                $queries[ $value['charset'] ] = array();
                                        }
 
-                                       // Split the CONVERT() calls by charset, so we can make sure the connection is right
-                                       $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
+                                       // We're going to need to truncate by characters or bytes, depending on the length value we have.
+                                       if ( 'byte' === $value['length']['type'] ) {
+                                               // Split the CONVERT() calls by charset, so we can make sure the connection is right
+                                               $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] );
+                                       } else {
+                                               $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] );
+                                       }
+
                                        unset( $data[ $col ]['db'] );
                                }
                        }
@@ -2535,16 +2706,19 @@ class wpdb {
 
                                $this->check_current_query = false;
 
-                               $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
+                               $sql = array();
+                               foreach ( $query as $column => $column_query ) {
+                                       $sql[] = $column_query . " AS x_$column";
+                               }
+
+                               $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
                                if ( ! $row ) {
                                        $this->set_charset( $this->dbh, $connection_charset );
                                        return new WP_Error( 'wpdb_strip_invalid_text_failure' );
                                }
 
-                               $cols = array_keys( $query );
-                               $col_count = count( $cols );
-                               for ( $ii = 0; $ii < $col_count; $ii++ ) {
-                                       $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
+                               foreach ( array_keys( $query ) as $column ) {
+                                       $data[ $column ]['value'] = $row["x_$column"];
                                }
                        }
 
@@ -2564,9 +2738,15 @@ class wpdb {
         * @access protected
         *
         * @param string $query Query to convert.
-        * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails.
+        * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
         */
        protected function strip_invalid_text_from_query( $query ) {
+               // We don't need to check the collation for queries that don't read data.
+               $trimmed_query = ltrim( $query, "\r\n\t (" );
+               if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
+                       return $query;
+               }
+
                $table = $this->get_table_from_query( $query );
                if ( $table ) {
                        $charset = $this->get_table_charset( $table );
@@ -2586,6 +2766,7 @@ class wpdb {
                        'value'   => $query,
                        'charset' => $charset,
                        'ascii'   => false,
+                       'length'  => false,
                );
 
                $data = $this->strip_invalid_text( array( $data ) );
@@ -2605,10 +2786,10 @@ class wpdb {
         * @param string $table  Table name.
         * @param string $column Column name.
         * @param string $value  The text to check.
-        * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails.
+        * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails.
         */
        public function strip_invalid_text_for_column( $table, $column, $value ) {
-               if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
+               if ( ! is_string( $value ) ) {
                        return $value;
                }
 
@@ -2625,7 +2806,7 @@ class wpdb {
                        $column => array(
                                'value'   => $value,
                                'charset' => $charset,
-                               'ascii'   => false,
+                               'length'  => $this->get_col_length( $table, $column ),
                        )
                );
 
@@ -2666,16 +2847,16 @@ class wpdb {
                                . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
                                . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
                                . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?'
-                               . ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\s+((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)/is', $query, $maybe ) ) {
+                       return str_replace( '`', '', $maybe[1] );
                }
 
                // SHOW TABLE STATUS and SHOW TABLES
                if ( preg_match( '/^\s*(?:'
                                . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
                                . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
-                               . ')\W([\w-]+)\W/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\W((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\W/is', $query, $maybe ) ) {
+                       return str_replace( '`', '', $maybe[1] );
                }
 
                // Big pattern for the rest of the table-related queries.
@@ -2693,8 +2874,8 @@ class wpdb {
                                . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
                                . '|(?:GRANT|REVOKE).*ON\s+TABLE'
                                . '|SHOW\s+(?:.*FROM|.*TABLE)'
-                               . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) {
+                       return str_replace( '`', '', $maybe[1] );
                }
 
                return false;
@@ -2868,7 +3049,25 @@ class wpdb {
                        case 'set_charset' :
                                return version_compare( $version, '5.0.7', '>=' );
                        case 'utf8mb4' :      // @since 4.1.0
-                               return version_compare( $version, '5.5.3', '>=' );
+                               if ( version_compare( $version, '5.5.3', '<' ) ) {
+                                       return false;
+                               }
+                               if ( $this->use_mysqli ) {
+                                       $client_version = mysqli_get_client_info();
+                               } else {
+                                       $client_version = mysql_get_client_info();
+                               }
+
+                               /*
+                                * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
+                                * mysqlnd has supported utf8mb4 since 5.0.9.
+                                */
+                               if ( false !== strpos( $client_version, 'mysqlnd' ) ) {
+                                       $client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
+                                       return version_compare( $client_version, '5.0.9', '>=' );
+                               } else {
+                                       return version_compare( $client_version, '5.5.3', '>=' );
+                               }
                }
 
                return false;