Index: src/wp-includes/wp-db.php
===================================================================
--- src/wp-includes/wp-db.php	(revision 41940)
+++ src/wp-includes/wp-db.php	(working copy)
@@ -1196,31 +1196,31 @@ class wpdb {
 	 *
 	 * The following placeholders can be used in the query string:
 	 *   %d (integer)
 	 *   %f (float)
 	 *   %s (string)
 	 *
 	 * All placeholders MUST be left unquoted in the query string. A corresponding argument MUST be passed for each placeholder.
 	 *
 	 * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example,
 	 * to use in LIKE syntax) must be passed via a substitution argument containing the complete LIKE string, these
 	 * cannot be inserted directly in the query string. Also see {@see esc_like()}.
 	 *
 	 * This method DOES NOT support sign, padding, alignment, width or precision specifiers.
 	 * This method DOES NOT support argument numbering or swapping.
 	 *
-	 * Arguments may be passed as individual arguments to the method, or as a single array containing all arguments. A combination 
+	 * Arguments may be passed as individual arguments to the method, or as a single array containing all arguments. A combination
 	 * of the two is not supported.
 	 *
 	 * Examples:
 	 *     $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) );
 	 *     $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
 	 *
 	 * @link https://secure.php.net/sprintf Description of syntax.
 	 * @since 2.3.0
 	 *
 	 * @param string      $query    Query statement with sprintf()-like placeholders
 	 * @param array|mixed $args     The array of variables to substitute into the query's placeholders if being called with an array of arguments,
 	 *                              or the first variable to substitute into the query's placeholders if being called with individual arguments.
 	 * @param mixed       $args,... further variables to substitute into the query's placeholders if being called wih individual arguments.
 	 * @return string|void Sanitized query string, if there is a query to prepare.
 	 */
@@ -1719,31 +1719,31 @@ class wpdb {
 
 		// Call dead_db() if bail didn't die, because this database is no more. It has ceased to be (at least temporarily).
 		dead_db();
 	}
 
 	/**
 	 * Perform a MySQL database query, using current database connection.
 	 *
 	 * More information can be found on the codex page.
 	 *
 	 * @since 0.71
 	 *
 	 * @param string $query Database query
 	 * @return int|false Number of rows affected/selected or false on error
 	 */
-	public function query( $query ) {
+	public function query( $query, $prepare_values = false ) {
 		if ( ! $this->ready ) {
 			$this->check_current_query = true;
 			return false;
 		}
 
 		/**
 		 * Filters the database query.
 		 *
 		 * Some queries are made before the plugins have been loaded,
 		 * and thus cannot be filtered with this method.
 		 *
 		 * @since 2.1.0
 		 *
 		 * @param string $query Database query.
 		 */
@@ -1759,55 +1759,55 @@ class wpdb {
 			$stripped_query = $this->strip_invalid_text_from_query( $query );
 			// strip_invalid_text_from_query() can perform queries, so we need
 			// to flush again, just to make sure everything is clear.
 			$this->flush();
 			if ( $stripped_query !== $query ) {
 				$this->insert_id = 0;
 				return false;
 			}
 		}
 
 		$this->check_current_query = true;
 
 		// Keep track of the last query for debug.
 		$this->last_query = $query;
 
-		$this->_do_query( $query );
+		$this->_do_query( $query, $prepare_values );
 
 		// MySQL server has gone away, try to reconnect.
 		$mysql_errno = 0;
 		if ( ! empty( $this->dbh ) ) {
 			if ( $this->use_mysqli ) {
 				if ( $this->dbh instanceof mysqli ) {
 					$mysql_errno = mysqli_errno( $this->dbh );
 				} else {
 					// $dbh is defined, but isn't a real connection.
 					// Something has gone horribly wrong, let's try a reconnect.
 					$mysql_errno = 2006;
 				}
 			} else {
 				if ( is_resource( $this->dbh ) ) {
 					$mysql_errno = mysql_errno( $this->dbh );
 				} else {
 					$mysql_errno = 2006;
 				}
 			}
 		}
 
 		if ( empty( $this->dbh ) || 2006 == $mysql_errno ) {
 			if ( $this->check_connection() ) {
-				$this->_do_query( $query );
+				$this->_do_query( $query, $prepare_values );
 			} else {
 				$this->insert_id = 0;
 				return false;
 			}
 		}
 
 		// If there is an error then take note of it.
 		if ( $this->use_mysqli ) {
 			if ( $this->dbh instanceof mysqli ) {
 				$this->last_error = mysqli_error( $this->dbh );
 			} else {
 				$this->last_error = __( 'Unable to retrieve the error message from MySQL' );
 			}
 		} else {
 			if ( is_resource( $this->dbh ) ) {
@@ -1834,68 +1834,133 @@ class wpdb {
 			} else {
 				$this->rows_affected = mysql_affected_rows( $this->dbh );
 			}
 			// Take note of the insert_id
 			if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
 				if ( $this->use_mysqli ) {
 					$this->insert_id = mysqli_insert_id( $this->dbh );
 				} else {
 					$this->insert_id = mysql_insert_id( $this->dbh );
 				}
 			}
 			// Return number of rows affected
 			$return_val = $this->rows_affected;
 		} else {
 			$num_rows = 0;
-			if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
+			if ( $this->use_mysqli && $this->result instanceof mysqli_stmt ) {
+				$row = array();
+				$mysqli_stmt_bind_result_args = array( $this->result );
+
+				foreach ( mysqli_fetch_fields( mysqli_stmt_result_metadata( $this->result ) ) as $field ) {
+					$row[ $field->name ] = null;
+					$mysqli_stmt_bind_result_args[] = &$row[ $field->name ];
+				}
+				call_user_func_array( 'mysqli_stmt_bind_result', $mysqli_stmt_bind_result_args );
+
+				while ( mysqli_stmt_fetch( $this->result ) ) {
+					$this->last_result[$num_rows] = $row;
+					$num_rows++;
+				}
+			} elseif ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
 				while ( $row = mysqli_fetch_object( $this->result ) ) {
 					$this->last_result[$num_rows] = $row;
 					$num_rows++;
 				}
 			} elseif ( is_resource( $this->result ) ) {
 				while ( $row = mysql_fetch_object( $this->result ) ) {
 					$this->last_result[$num_rows] = $row;
 					$num_rows++;
 				}
 			}
 
 			// Log number of rows the query returned
 			// and return number of rows selected
 			$this->num_rows = $num_rows;
 			$return_val     = $num_rows;
 		}
 
 		return $return_val;
 	}
 
 	/**
 	 * Internal function to perform the mysql_query() call.
 	 *
 	 * @since 3.9.0
 	 *
 	 * @see wpdb::query()
 	 *
 	 * @param string $query The query to run.
 	 */
-	private function _do_query( $query ) {
+	private function _do_query( $query, $prepared_query_data = false ) {
 		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
 			$this->timer_start();
 		}
 
-		if ( ! empty( $this->dbh ) && $this->use_mysqli ) {
+		if ( ! empty( $this->dbh ) && $this->use_mysqli && $prepared_query_data !== false ) {
+			$prepared_value_types = '';
+			$prepared_values = array();
+			$valid_data_types = array( 's', 'd', 'i' );
+			foreach ( $prepared_query_data as $v ) {
+				if ( is_array( $v ) && isset( $v['type'] ) ) {
+					if ( in_array( $v['type'], $valid_data_types, true ) ) {
+						$prepared_value_types .= $v['type'];
+					} else {
+						$prepared_value_types .= 's';
+					}
+					$prepared_values[] = $v['value'];
+				} else {
+					// Strings can be passed without the data type.
+					$prepared_value_types .= 's';
+					$prepared_values[] = $v;
+				}
+			}
+
+			$prepared_query = mysqli_prepare( $this->dbh, $query );
+			if ( ! $prepared_query ) {
+				// TODO: Handling of a invalid query
+				// $this->result = false;
+			}
+
+			/*if ( $prepared_query->param_count != count( $__raw_prepared_data ) ) {
+				// TODO Catch this before a PHP Warning is hit and yell even louder than a Warning at the developer?
+				_doing_it_completely_wrong( "Incorrect parameter count!" );
+				throw new Exception( 'Incorrect parameter count!' );
+			}*/
+
+			$mysqli_stmt_bind_param_args = array(
+				$prepared_query,
+				$prepared_value_types
+				// ... args by ref:
+			);
+			foreach ( $prepared_values as $i => $v ) {
+				$mysqli_stmt_bind_param_args[] = & $prepared_values[$i];
+			}
+			call_user_func_array( 'mysqli_stmt_bind_param', $mysqli_stmt_bind_param_args );
+
+			mysqli_stmt_execute( $prepared_query );
+
+			// $this->result = mysqli_stmt_get_result( $prepared_query ); // PHP 5.3+ only
+			$this->result = $prepared_query;
+
+		} elseif ( ! empty( $this->dbh ) && $this->use_mysqli ) {
 			$this->result = mysqli_query( $this->dbh, $query );
 		} elseif ( ! empty( $this->dbh ) ) {
+			if ( $prepared_query_data !== false ) {
+				// TODO: Oh noes, it's a prepared query which we don't support!
+				//       form it into a real query before passing to mysql_query() using $this->prepare() or something?
+				//       This will likely require an actual SQL parser rather than regular expressions.
+			}
 			$this->result = mysql_query( $query, $this->dbh );
 		}
 		$this->num_queries++;
 
 		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
 			$this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() );
 		}
 	}
 
 	/**
 	 * Insert a row into a table.
 	 *
 	 *     wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
 	 *     wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
 	 *
@@ -2052,31 +2117,31 @@ class wpdb {
 		}
 		foreach ( $where as $field => $value ) {
 			if ( is_null( $value['value'] ) ) {
 				$conditions[] = "`$field` IS NULL";
 				continue;
 			}
 
 			$conditions[] = "`$field` = " . $value['format'];
 			$values[] = $value['value'];
 		}
 
 		$fields = implode( ', ', $fields );
 		$conditions = implode( ' AND ', $conditions );
 
 		$sql = "UPDATE `$table` SET $fields WHERE $conditions";
-		
+
 		$this->check_current_query = false;
 		return $this->query( $this->prepare( $sql, $values ) );
 	}
 
 	/**
 	 * Delete a row in the table
 	 *
 	 *     wpdb::delete( 'table', array( 'ID' => 1 ) )
 	 *     wpdb::delete( 'table', array( 'ID' => 1 ), array( '%d' ) )
 	 *
 	 * @since 3.4.0
 	 * @see wpdb::prepare()
 	 * @see wpdb::$field_types
 	 * @see wp_set_wpdb_vars()
 	 *
@@ -2260,72 +2325,95 @@ class wpdb {
 
 	/**
 	 * Retrieve one variable from the database.
 	 *
 	 * Executes a SQL query and returns the value from the SQL result.
 	 * If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
 	 * If $query is null, this function returns the value in the specified column and row from the previous SQL result.
 	 *
 	 * @since 0.71
 	 *
 	 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
 	 * @param int         $x     Optional. Column of value to return. Indexed from 0.
 	 * @param int         $y     Optional. Row of value to return. Indexed from 0.
 	 * @return string|null Database query result (as string), or null on failure
 	 */
-	public function get_var( $query = null, $x = 0, $y = 0 ) {
+	public function get_var( $query = null, $prepared_query = false, $x = 0, $y = 0 ) {
+		// Back-compat.
+		if ( ! is_array( $prepared_query ) && func_num_args() > 1 ) {
+			switch( func_num_args() ) { // zero indexed args
+				case 3:
+					$y = func_get_arg( 2 );
+				case 2:
+					$x = func_get_arg( 1 );
+			}
+			$prepared_query = false;
+		}
+
 		$this->func_call = "\$db->get_var(\"$query\", $x, $y)";
 
 		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
 			$this->check_current_query = false;
 		}
 
 		if ( $query ) {
-			$this->query( $query );
+			$this->query( $query, $prepared_query );
 		}
 
 		// Extract var out of cached results based x,y vals
 		if ( !empty( $this->last_result[$y] ) ) {
 			$values = array_values( get_object_vars( $this->last_result[$y] ) );
 		}
 
 		// If there is a value return it else return null
 		return ( isset( $values[$x] ) && $values[$x] !== '' ) ? $values[$x] : null;
 	}
 
 	/**
 	 * Retrieve one row from the database.
 	 *
 	 * Executes a SQL query and returns the row from the SQL result.
 	 *
 	 * @since 0.71
 	 *
 	 * @param string|null $query  SQL query.
 	 * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
 	 *                            an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT.
 	 * @param int         $y      Optional. Row to return. Indexed from 0.
 	 * @return array|object|null|void Database query result in format specified by $output or null on failure
 	 */
-	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
+	public function get_row( $query = null, $prepared_query = false, $output = OBJECT, $y = 0 ) {
+		// Back-compat.
+		if ( ! is_array( $prepared_query ) && func_num_args() > 1 ) {
+			switch( func_num_args() ) { // zero indexed args
+				case 3:
+					$y = func_get_arg( 2 );
+				case 2:
+					$output = func_get_arg( 1 );
+			}
+			$prepared_query = false;
+		}
+
+
 		$this->func_call = "\$db->get_row(\"$query\",$output,$y)";
 
 		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
 			$this->check_current_query = false;
 		}
 
 		if ( $query ) {
-			$this->query( $query );
+			$this->query( $query, $prepared_query );
 		} else {
 			return null;
 		}
 
 		if ( !isset( $this->last_result[$y] ) )
 			return null;
 
 		if ( $output == OBJECT ) {
 			return $this->last_result[$y] ? $this->last_result[$y] : null;
 		} elseif ( $output == ARRAY_A ) {
 			return $this->last_result[$y] ? get_object_vars( $this->last_result[$y] ) : null;
 		} elseif ( $output == ARRAY_N ) {
 			return $this->last_result[$y] ? array_values( get_object_vars( $this->last_result[$y] ) ) : null;
 		} elseif ( strtoupper( $output ) === OBJECT ) {
 			// Back compat for OBJECT being previously case insensitive.
@@ -2336,71 +2424,83 @@ class wpdb {
 	}
 
 	/**
 	 * Retrieve one column from the database.
 	 *
 	 * Executes a SQL query and returns the column from the SQL result.
 	 * If the SQL result contains more than one column, this function returns the column specified.
 	 * If $query is null, this function returns the specified column from the previous SQL result.
 	 *
 	 * @since 0.71
 	 *
 	 * @param string|null $query Optional. SQL query. Defaults to previous query.
 	 * @param int         $x     Optional. Column to return. Indexed from 0.
 	 * @return array Database query result. Array indexed from 0 by SQL result row number.
 	 */
-	public function get_col( $query = null , $x = 0 ) {
+	public function get_col( $query = null, $prepared_query = false, $x = 0 ) {
+		// Back-compat.
+		if ( ! is_array( $prepared_query ) && func_num_args() > 1 ) {
+			$x = func_get_arg( 1 );
+			$prepared_query = false;
+		}
+
 		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
 			$this->check_current_query = false;
 		}
 
 		if ( $query ) {
-			$this->query( $query );
+			$this->query( $query, $prepared_query );
 		}
 
 		$new_array = array();
 		// Extract the column values
 		for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
 			$new_array[$i] = $this->get_var( null, $x, $i );
 		}
 		return $new_array;
 	}
 
 	/**
 	 * Retrieve an entire SQL result set from the database (i.e., many rows)
 	 *
 	 * Executes a SQL query and returns the entire SQL result.
 	 *
 	 * @since 0.71
 	 *
 	 * @param string $query  SQL query.
 	 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
 	 *                       With one of the first three, return an array of rows indexed from 0 by SQL result row number.
 	 *                       Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
 	 *                       With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value.
 	 *                       Duplicate keys are discarded.
 	 * @return array|object|null Database query results
 	 */
-	public function get_results( $query = null, $output = OBJECT ) {
+	public function get_results( $query = null, $prepared_query = false, $output = OBJECT ) {
+		// Back-compat.
+		if ( ! is_array( $prepared_query ) && func_num_args() > 1 ) {
+			$output = func_get_arg( 1 );
+			$prepared_query = false;
+		}
+
 		$this->func_call = "\$db->get_results(\"$query\", $output)";
 
 		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
 			$this->check_current_query = false;
 		}
 
 		if ( $query ) {
-			$this->query( $query );
+			$this->query( $query, $prepared_query );
 		} else {
 			return null;
 		}
 
 		$new_array = array();
 		if ( $output == OBJECT ) {
 			// Return an integer-keyed array of row objects
 			return $this->last_result;
 		} elseif ( $output == OBJECT_K ) {
 			// Return an array of row objects with keys from column 1
 			// (Duplicates are discarded)
 			foreach ( $this->last_result as $row ) {
 				$var_by_ref = get_object_vars( $row );
 				$key = array_shift( $var_by_ref );
 				if ( ! isset( $new_array[ $key ] ) )
